diff --git a/Changelog.md b/Changelog.md index 13af14b75b97..0ebefa6590f1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Compiler Features: Bugfixes: +* General: Fix internal compiler error when requesting IR AST outputs for interfaces and abstract contracts. ### 0.8.28 (2024-10-09) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c68df205f941..e3927d4dfb1b 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -969,46 +969,61 @@ std::string const CompilerStack::filesystemFriendlyName(std::string const& _cont return matchContract.contract->name(); } -std::string const& CompilerStack::yulIR(std::string const& _contractName) const +std::optional const& CompilerStack::yulIR(std::string const& _contractName) const { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); return contract(_contractName).yulIR; } -Json CompilerStack::yulIRAst(std::string const& _contractName) const +std::optional CompilerStack::yulIRAst(std::string const& _contractName) const { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); solUnimplementedAssert(!isExperimentalSolidity()); // NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to // keep it around when compiling a large project containing many contracts. - return loadGeneratedIR(contract(_contractName).yulIR).astJson(); + Contract const& currentContract = contract(_contractName); + yulAssert(currentContract.contract); + yulAssert(currentContract.yulIR.has_value() == currentContract.contract->canBeDeployed()); + if (!currentContract.yulIR) + return std::nullopt; + return loadGeneratedIR(*currentContract.yulIR).astJson(); } -Json CompilerStack::yulCFGJson(std::string const& _contractName) const +std::optional CompilerStack::yulCFGJson(std::string const& _contractName) const { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); solUnimplementedAssert(!isExperimentalSolidity()); // NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to // keep it around when compiling a large project containing many contracts. - return loadGeneratedIR(contract(_contractName).yulIR).cfgJson(); + Contract const& currentContract = contract(_contractName); + yulAssert(currentContract.contract); + yulAssert(currentContract.yulIR.has_value() == currentContract.contract->canBeDeployed()); + if (!currentContract.yulIR) + return std::nullopt; + return loadGeneratedIR(*currentContract.yulIR).cfgJson(); } -std::string const& CompilerStack::yulIROptimized(std::string const& _contractName) const +std::optional const& CompilerStack::yulIROptimized(std::string const& _contractName) const { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); return contract(_contractName).yulIROptimized; } -Json CompilerStack::yulIROptimizedAst(std::string const& _contractName) const +std::optional CompilerStack::yulIROptimizedAst(std::string const& _contractName) const { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); solUnimplementedAssert(!isExperimentalSolidity()); // NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to // keep it around when compiling a large project containing many contracts. - return loadGeneratedIR(contract(_contractName).yulIROptimized).astJson(); + Contract const& currentContract = contract(_contractName); + yulAssert(currentContract.contract); + yulAssert(currentContract.yulIROptimized.has_value() == currentContract.contract->canBeDeployed()); + if (!currentContract.yulIROptimized) + return std::nullopt; + return loadGeneratedIR(*currentContract.yulIROptimized).astJson(); } evmasm::LinkerObject const& CompilerStack::object(std::string const& _contractName) const @@ -1507,8 +1522,11 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti solAssert(m_stackState >= AnalysisSuccessful, ""); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - if (!compiledContract.yulIR.empty()) + if (compiledContract.yulIR) + { + solAssert(!compiledContract.yulIR->empty()); return; + } if (!*_contract.sourceUnit().annotation().useABICoderV2) m_errorReporter.warning( @@ -1527,7 +1545,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti std::map otherYulSources; for (auto const& pair: m_contracts) - otherYulSources.emplace(pair.second.contract, pair.second.yulIR); + otherYulSources.emplace(pair.second.contract, pair.second.yulIR ? *pair.second.yulIR : std::string_view{}); if (m_experimentalAnalysis) { @@ -1564,7 +1582,8 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti ); } - YulStack stack = loadGeneratedIR(compiledContract.yulIR); + yulAssert(compiledContract.yulIR); + YulStack stack = loadGeneratedIR(*compiledContract.yulIR); if (!_unoptimizedOnly) { stack.optimize(); @@ -1580,14 +1599,13 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract) return; Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - solAssert(!compiledContract.yulIROptimized.empty(), ""); + solAssert(compiledContract.yulIROptimized); + solAssert(!compiledContract.yulIROptimized->empty()); if (!compiledContract.object.bytecode.empty()) return; // Re-parse the Yul IR in EVM dialect - YulStack stack = loadGeneratedIR(compiledContract.yulIROptimized); - - //cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl; + YulStack stack = loadGeneratedIR(*compiledContract.yulIROptimized); std::string deployedName = IRNames::deployedObject(_contract); solAssert(!deployedName.empty(), ""); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 8ce2c5d62dfe..009103a498f8 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -321,18 +321,18 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac virtual std::string const filesystemFriendlyName(std::string const& _contractName) const override; /// @returns the IR representation of a contract. - std::string const& yulIR(std::string const& _contractName) const; + std::optional const& yulIR(std::string const& _contractName) const; /// @returns the IR representation of a contract AST in format. - Json yulIRAst(std::string const& _contractName) const; + std::optional yulIRAst(std::string const& _contractName) const; /// @returns the optimized IR representation of a contract. - std::string const& yulIROptimized(std::string const& _contractName) const; + std::optional const& yulIROptimized(std::string const& _contractName) const; /// @returns the optimized IR representation of a contract AST in JSON format. - Json yulIROptimizedAst(std::string const& _contractName) const; + std::optional yulIROptimizedAst(std::string const& _contractName) const; - Json yulCFGJson(std::string const& _contractName) const; + std::optional yulCFGJson(std::string const& _contractName) const; /// @returns the assembled object for a contract. virtual evmasm::LinkerObject const& object(std::string const& _contractName) const override; @@ -445,8 +445,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac std::optional runtimeGeneratedYulUtilityCode; ///< Extra Yul utility code that was used when compiling the deployed assembly evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object). evmasm::LinkerObject runtimeObject; ///< Runtime object. - std::string yulIR; ///< Yul IR code straight from the code generator. - std::string yulIROptimized; ///< Reparsed and possibly optimized Yul IR code. + std::optional yulIR; ///< Yul IR code straight from the code generator. + std::optional yulIROptimized; ///< Reparsed and possibly optimized Yul IR code. util::LazyInit metadata; ///< The metadata json that will be hashed into the chain. util::LazyInit abi; util::LazyInit storageLayout; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index d8ce4f2d0c7a..c5248e42e2f4 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -1482,15 +1482,15 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu // IR if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental)) - contractData["ir"] = compilerStack.yulIR(contractName); + contractData["ir"] = compilerStack.yulIR(contractName).value_or(""); if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irAst", wildcardMatchesExperimental)) - contractData["irAst"] = compilerStack.yulIRAst(contractName); + contractData["irAst"] = compilerStack.yulIRAst(contractName).value_or(Json{}); if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental)) - contractData["irOptimized"] = compilerStack.yulIROptimized(contractName); + contractData["irOptimized"] = compilerStack.yulIROptimized(contractName).value_or(""); if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimizedAst", wildcardMatchesExperimental)) - contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName); + contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName).value_or(Json{}); if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "yulCFGJson", wildcardMatchesExperimental)) - contractData["yulCFGJson"] = compilerStack.yulCFGJson(contractName); + contractData["yulCFGJson"] = compilerStack.yulCFGJson(contractName).value_or(Json{}); // EVM Json evmData; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index b791de3ef361..33aa08b463e4 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -257,12 +257,13 @@ void CommandLineInterface::handleIR(std::string const& _contractName) if (!m_options.compiler.outputs.ir) return; + std::optional const& ir = m_compiler->yulIR(_contractName); if (!m_options.output.dir.empty()) - createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); + createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", ir.value_or("")); else { - sout() << "IR:" << std::endl; - sout() << m_compiler->yulIR(_contractName) << std::endl; + sout() << "IR:\n"; + sout() << ir.value_or("") << std::endl; } } @@ -273,11 +274,12 @@ void CommandLineInterface::handleIRAst(std::string const& _contractName) if (!m_options.compiler.outputs.irAstJson) return; + std::optional const& yulIRAst = m_compiler->yulIRAst(_contractName); if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_yul_ast.json", util::jsonPrint( - m_compiler->yulIRAst(_contractName), + yulIRAst.value_or(Json{}), m_options.formatting.json ) ); @@ -285,7 +287,7 @@ void CommandLineInterface::handleIRAst(std::string const& _contractName) { sout() << "IR AST:" << std::endl; sout() << util::jsonPrint( - m_compiler->yulIRAst(_contractName), + yulIRAst.value_or(Json{}), m_options.formatting.json ) << std::endl; } @@ -298,18 +300,19 @@ void CommandLineInterface::handleYulCFGExport(std::string const& _contractName) if (!m_options.compiler.outputs.yulCFGJson) return; + std::optional const& yulCFGJson = m_compiler->yulCFGJson(_contractName); if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_yul_cfg.json", util::jsonPrint( - m_compiler->yulCFGJson(_contractName), + yulCFGJson.value_or(Json{}), m_options.formatting.json ) ); else { sout() << util::jsonPrint( - m_compiler->yulCFGJson(_contractName), + yulCFGJson.value_or(Json{}), m_options.formatting.json ) << std::endl; } @@ -322,15 +325,16 @@ void CommandLineInterface::handleIROptimized(std::string const& _contractName) if (!m_options.compiler.outputs.irOptimized) return; + std::optional const& irOptimized = m_compiler->yulIROptimized(_contractName); if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", - m_compiler->yulIROptimized(_contractName) + irOptimized.value_or("") ); else { sout() << "Optimized IR:" << std::endl; - sout() << m_compiler->yulIROptimized(_contractName) << std::endl; + sout() << irOptimized.value_or("") << std::endl; } } @@ -341,11 +345,12 @@ void CommandLineInterface::handleIROptimizedAst(std::string const& _contractName if (!m_options.compiler.outputs.irOptimizedAstJson) return; + std::optional const& yulIROptimizedAst = m_compiler->yulIROptimizedAst(_contractName); if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_opt_yul_ast.json", util::jsonPrint( - m_compiler->yulIROptimizedAst(_contractName), + yulIROptimizedAst.value_or(Json{}), m_options.formatting.json ) ); @@ -353,7 +358,7 @@ void CommandLineInterface::handleIROptimizedAst(std::string const& _contractName { sout() << "Optimized IR AST:" << std::endl; sout() << util::jsonPrint( - m_compiler->yulIROptimizedAst(_contractName), + yulIROptimizedAst.value_or(Json{}), m_options.formatting.json ) << std::endl; } diff --git a/test/cmdlineTests/standard_undeployable_contract_all_outputs/in.sol b/test/cmdlineTests/standard_undeployable_contract_all_outputs/in.sol new file mode 100644 index 000000000000..80cc66cd19b7 --- /dev/null +++ b/test/cmdlineTests/standard_undeployable_contract_all_outputs/in.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity *; + +abstract contract C {} +interface I {} diff --git a/test/cmdlineTests/standard_undeployable_contract_all_outputs/input.json b/test/cmdlineTests/standard_undeployable_contract_all_outputs/input.json new file mode 100644 index 000000000000..4d2aea1f147a --- /dev/null +++ b/test/cmdlineTests/standard_undeployable_contract_all_outputs/input.json @@ -0,0 +1,13 @@ +{ + "language": "Solidity", + "sources": { + "C": {"urls": ["standard_undeployable_contract_all_outputs/in.sol"]} + }, + "settings": { + "outputSelection": { + "*": { + "*": ["*", "ir", "irAst", "irOptimized", "irOptimizedAst"] + } + } + } +} diff --git a/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json new file mode 100644 index 000000000000..3869f7c652bd --- /dev/null +++ b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json @@ -0,0 +1,109 @@ +{ + "contracts": { + "C": { + "C": { + "abi": [], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "evm": { + "assembly": "", + "bytecode": { + "functionDebugData": {}, + "generatedSources": [], + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "functionDebugData": {}, + "generatedSources": [], + "immutableReferences": {}, + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "gasEstimates": null, + "legacyAssembly": null, + "methodIdentifiers": {} + }, + "ir": "", + "irAst": null, + "irOptimized": "", + "irOptimizedAst": null, + "metadata": "{\"compiler\":{\"version\":\"\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"C\":\"C\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"C\":{\"keccak256\":\"0xa8b7bfe5eff9112e6573d2860721faef28e8920ee251acb458303a05c1ec7df2\",\"license\":\"GPL-3.0\",\"urls\":[\"bzz-raw://a2333e25b5034de4f98729e0a737309ba8e0db4371016f312e8f991f6f01613f\",\"dweb:/ipfs/QmVLwS2grVYV6qiDvNmeEYWzb6WDne9Ze47NXERSLPM4fJ\"]}},\"version\":1}", + "storageLayout": { + "storage": [], + "types": null + }, + "transientStorageLayout": { + "storage": [], + "types": null + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "I": { + "abi": [], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "evm": { + "assembly": "", + "bytecode": { + "functionDebugData": {}, + "generatedSources": [], + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "functionDebugData": {}, + "generatedSources": [], + "immutableReferences": {}, + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "gasEstimates": null, + "legacyAssembly": null, + "methodIdentifiers": {} + }, + "ir": "", + "irAst": null, + "irOptimized": "", + "irOptimizedAst": null, + "metadata": "{\"compiler\":{\"version\":\"\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"C\":\"I\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"C\":{\"keccak256\":\"0xa8b7bfe5eff9112e6573d2860721faef28e8920ee251acb458303a05c1ec7df2\",\"license\":\"GPL-3.0\",\"urls\":[\"bzz-raw://a2333e25b5034de4f98729e0a737309ba8e0db4371016f312e8f991f6f01613f\",\"dweb:/ipfs/QmVLwS2grVYV6qiDvNmeEYWzb6WDne9Ze47NXERSLPM4fJ\"]}},\"version\":1}", + "storageLayout": { + "storage": [], + "types": null + }, + "transientStorageLayout": { + "storage": [], + "types": null + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + } + } + }, + "sources": { + "C": { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/undeployable_contract_empty_outputs/args b/test/cmdlineTests/undeployable_contract_empty_outputs/args new file mode 100644 index 000000000000..74c8e867fc91 --- /dev/null +++ b/test/cmdlineTests/undeployable_contract_empty_outputs/args @@ -0,0 +1 @@ +--bin --bin-runtime --opcodes --asm --ir --ir-ast-json --ir-optimized --ir-optimized-ast-json --gas --asm-json --abi --hashes --optimize --pretty-json --json-indent 4 diff --git a/test/cmdlineTests/undeployable_contract_empty_outputs/input.sol b/test/cmdlineTests/undeployable_contract_empty_outputs/input.sol new file mode 100644 index 000000000000..06496f4a26e7 --- /dev/null +++ b/test/cmdlineTests/undeployable_contract_empty_outputs/input.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +abstract contract C {} +interface I {} diff --git a/test/cmdlineTests/undeployable_contract_empty_outputs/output b/test/cmdlineTests/undeployable_contract_empty_outputs/output new file mode 100644 index 000000000000..123edc75ddd6 --- /dev/null +++ b/test/cmdlineTests/undeployable_contract_empty_outputs/output @@ -0,0 +1,44 @@ + +======= undeployable_contract_empty_outputs/input.sol:C ======= +EVM assembly: +null +Gas estimation: +Opcodes: + +Binary: + +Binary of the runtime part: + +IR: + +IR AST: +null +Optimized IR: + +Optimized IR AST: +null +Function signatures: +Contract JSON ABI +[] + +======= undeployable_contract_empty_outputs/input.sol:I ======= +EVM assembly: +null +Gas estimation: +Opcodes: + +Binary: + +Binary of the runtime part: + +IR: + +IR AST: +null +Optimized IR: + +Optimized IR AST: +null +Function signatures: +Contract JSON ABI +[] diff --git a/test/libsolidity/MemoryGuardTest.cpp b/test/libsolidity/MemoryGuardTest.cpp index 08a2814c95e3..a98c7a323015 100644 --- a/test/libsolidity/MemoryGuardTest.cpp +++ b/test/libsolidity/MemoryGuardTest.cpp @@ -60,8 +60,10 @@ TestCase::TestResult MemoryGuardTest::run(std::ostream& _stream, std::string con for (std::string contractName: compiler().contractNames()) { ErrorList errors; + std::optional const& ir = compiler().yulIR(contractName); + solAssert(ir); auto [object, analysisInfo] = yul::test::parse( - compiler().yulIR(contractName), + *ir, EVMDialect::strictAssemblyForEVMObjects(CommonOptions::get().evmVersion(), CommonOptions::get().eofVersion()), errors );