diff --git a/Changelog.md b/Changelog.md index 967e548814ef..1884d7aa5c40 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,8 @@ Language Features: Compiler Features: + * Commandline Interface: Add ``--assembly-structure`` option to provide information about assemblies and nested assemblies. + * Standard JSON Interface: Add ``evm.bytecode.assemblyStructure`` output that provides information about assemblies and nested assemblies. Bugfixes: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index a3943ea94da2..19559bf693a5 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -441,6 +441,7 @@ Input Description // transientStorageLayout - Slots, offsets and types of the contract's state variables in transient storage. // evm.assembly - New assembly format // evm.legacyAssembly - Old-style assembly format in JSON + // evm.bytecode.assemblyStructure - Structure of the bytecode, providing offsets, lengths and creation indicators. // evm.bytecode.functionDebugData - Debugging information at function level // evm.bytecode.object - Bytecode object // evm.bytecode.opcodes - Opcodes list @@ -603,6 +604,36 @@ Output Description "legacyAssembly": {}, // Bytecode and related details. "bytecode": { + // Structure tree of the bytecode, providing information about the root (top-level) assembly including + // its nested assemblies. + "assemblyStructure": { + // Indicates whether assembly in question in creation or runtime. + "isCreation": true, + // Size of the assembly in bytes. + "length": 1595, + // Start position of the assembly relatives to its parent. + "start": 0, + // List of nested assemblies (sub assemblies). + "subAssemblies": [ + { + "isCreation": false, + "length": 684, + "start": 877 + }, + { + "isCreation": true, + "length": 34, + "start": 1561, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 26 + } + ] + } + ] + }, // Debugging data at the level of functions. "functionDebugData": { // Now follows a set of functions including compiler-internal and diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 019683c9d4fd..7a1fa0176d06 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1257,12 +1257,11 @@ LinkerObject const& Assembly::assembleLegacy() const solAssert(m_assembledObject.linkReferences.empty()); LinkerObject& ret = m_assembledObject; - size_t subTagSize = 1; std::map immutableReferencesBySub; for (auto const& sub: m_subs) { - auto const& linkerObject = sub->assemble(); + auto const& linkerObject = sub->assembleLegacy(); if (!linkerObject.immutableReferences.empty()) { assertThrow( @@ -1316,7 +1315,7 @@ LinkerObject const& Assembly::assembleLegacy() const unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast(m_auxiliaryData.size()); for (auto const& sub: m_subs) - bytesRequiredIncludingData += static_cast(sub->assemble().bytecode.size()); + bytesRequiredIncludingData += static_cast(sub->assembleLegacy().bytecode.size()); unsigned bytesPerDataRef = numberEncodingSize(bytesRequiredIncludingData); ret.bytecode.reserve(bytesRequiredIncludingData); @@ -1366,7 +1365,7 @@ LinkerObject const& Assembly::assembleLegacy() const case PushSubSize: { assertThrow(item.data() <= std::numeric_limits::max(), AssemblyException, ""); - auto s = subAssemblyById(static_cast(item.data()))->assemble().bytecode.size(); + auto s = subAssemblyById(static_cast(item.data()))->assembleLegacy().bytecode.size(); item.setPushedValue(u256(s)); unsigned b = std::max(1, numberEncodingSize(s)); ret.bytecode.push_back(static_cast(pushInstruction(b))); @@ -1463,7 +1462,7 @@ LinkerObject const& Assembly::assembleLegacy() const std::map subAssemblyOffsets; for (auto const& [subIdPath, bytecodeOffset]: subRefs) { - LinkerObject subObject = subAssemblyById(subIdPath)->assemble(); + LinkerObject subObject = subAssemblyById(subIdPath)->assembleLegacy(); bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef); // In order for de-duplication to kick in, not only must the bytecode be identical, but @@ -1475,6 +1474,12 @@ LinkerObject const& Assembly::assembleLegacy() const toBigEndian(ret.bytecode.size(), r); subAssemblyOffsets[subObject] = ret.bytecode.size(); ret.bytecode += subObject.bytecode; + ret.subAssemblyData.push_back({ + subAssemblyOffsets[subObject], + subObject.bytecode.size(), + subAssemblyById(subIdPath)->isCreation(), + subObject.subAssemblyData + }); } for (auto const& ref: subObject.linkReferences) ret.linkReferences[ref.first + subAssemblyOffsets[subObject]] = ref.second; diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 30b06dd367fb..bf0da161712f 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -264,7 +264,6 @@ class Assembly private: bool m_invalid = false; - Assembly const* subAssemblyById(size_t _subId) const; void encodeAllPossibleSubPathsInAssemblyTree(std::vector _pathFromRoot = {}, std::vector _assembliesOnPath = {}); diff --git a/libevmasm/EVMAssemblyStack.cpp b/libevmasm/EVMAssemblyStack.cpp index bed4ff744ec4..7d524e1fe05e 100644 --- a/libevmasm/EVMAssemblyStack.cpp +++ b/libevmasm/EVMAssemblyStack.cpp @@ -57,6 +57,13 @@ void EVMAssemblyStack::assemble() m_evmAssembly->optimise(m_optimiserSettings); m_object = m_evmAssembly->assemble(); + // Recreate subAssembly data to include parent object + m_object.subAssemblyData = {{ + 0, + m_object.bytecode.size(), + m_evmAssembly->isCreation(), + m_object.subAssemblyData + }}; // TODO: Check for EOF solAssert(m_evmAssembly->codeSections().size() == 1); m_sourceMapping = AssemblyItem::computeSourceMapping(m_evmAssembly->codeSections().front().items, sourceIndices()); diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h index 34e696d4e159..1cae643a5d10 100644 --- a/libevmasm/LinkerObject.h +++ b/libevmasm/LinkerObject.h @@ -89,6 +89,15 @@ struct LinkerObject /// Bytecode offsets of named tags like function entry points. std::map functionDebugData; + struct Structure { + size_t start; + size_t length; + bool isCreation; + std::vector subAssemblies {}; + }; + + std::vector subAssemblyData; + /// Appends the bytecode of @a _other and incorporates its link references. void append(LinkerObject const& _other); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1b0a299f7291..2719c08f6222 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1470,6 +1470,13 @@ void CompilerStack::assembleYul( { // Assemble deployment (incl. runtime) object. compiledContract.object = compiledContract.evmAssembly->assemble(); + // Recreate subAssembly data to include parent object + compiledContract.object.subAssemblyData = {{ + 0, + compiledContract.object.bytecode.size(), + compiledContract.evmAssembly->isCreation(), + compiledContract.object.subAssemblyData + }}; } catch (evmasm::AssemblyException const& error) { diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index d5502228bda4..f046a807a2b5 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -246,7 +246,7 @@ bool isArtifactRequested(Json const& _outputSelection, std::string const& _file, std::vector evmObjectComponents(std::string const& _objectKind) { solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", ""); - std::vector components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences", ".ethdebug"}; + std::vector components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences", ".ethdebug", ".assemblyStructure"}; if (_objectKind == "deployedBytecode") components.push_back(".immutableReferences"); return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; }); @@ -1304,6 +1304,8 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in creationJSON["linkReferences"] = formatLinkReferences(stack.object(sourceName).linkReferences); if (evmCreationArtifactRequested("ethdebug")) creationJSON["ethdebug"] = stack.ethdebug(sourceName); + if (evmCreationArtifactRequested("assemblyStructure")) + creationJSON["assemblyStructure"] = formatAssemblyStructure(stack.object(sourceName).subAssemblyData); evmData["bytecode"] = creationJSON; } @@ -1581,6 +1583,8 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu creationJSON["generatedSources"] = compilerStack.generatedSources(contractName, /* _runtime */ false); if (evmCreationArtifactRequested("ethdebug")) creationJSON["ethdebug"] = compilerStack.ethdebug(contractName); + if (evmCreationArtifactRequested("assemblyStructure")) + creationJSON["assemblyStructure"] = formatAssemblyStructure(compilerStack.object(contractName).subAssemblyData); evmData["bytecode"] = creationJSON; } @@ -1882,3 +1886,34 @@ Json StandardCompiler::formatFunctionDebugData( return ret; } + +Json StandardCompiler::formatAssemblyStructure(std::vector const& _assemblyStructure) +{ + std::function const&)> recursiveHelper = [&](auto const& _assemblyStructure) + { + Json subAssemblies = Json::array(); + for (auto const& subAssembly: _assemblyStructure) + { + Json assemblyStructure = { + {"start", Json::number_unsigned_t(subAssembly.start)}, + {"length", Json::number_unsigned_t(subAssembly.length)}, + {"isCreation", Json::boolean_t(subAssembly.isCreation)} + }; + if (!subAssembly.subAssemblies.empty()) + assemblyStructure["subAssemblies"] = recursiveHelper(subAssembly.subAssemblies); + subAssemblies.emplace_back(assemblyStructure); + } + return subAssemblies; + }; + + Json assemblyStructure = Json::object(); + if (!_assemblyStructure.empty()) + assemblyStructure = { + {"start", Json::number_unsigned_t(_assemblyStructure[0].start)}, + {"length", Json::number_unsigned_t(_assemblyStructure[0].length)}, + {"isCreation", Json::boolean_t(_assemblyStructure[0].isCreation)}, + {"subAssemblies", recursiveHelper(_assemblyStructure[0].subAssemblies)} + }; + + return assemblyStructure; +} diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 665e8b487158..325e4a404ee5 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -67,6 +67,8 @@ class StandardCompiler std::map const& _debugInfo ); + static Json formatAssemblyStructure(std::vector const& _assemblyStructure); + private: struct InputsAndSettings { diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 4a7f34561fbe..a3539671a5cb 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -137,6 +137,7 @@ static std::string const g_strSrcMapRuntime = "srcmap-runtime"; static std::string const g_strStorageLayout = "storage-layout"; static std::string const g_strTransientStorageLayout = "transient-storage-layout"; static std::string const g_strVersion = "version"; +static std::string const g_strAssemblyStructure = "assembly-structure"; static bool needsHumanTargetedStdout(CommandLineOptions const& _options) { @@ -159,7 +160,8 @@ static bool needsHumanTargetedStdout(CommandLineOptions const& _options) _options.compiler.outputs.opcodes || _options.compiler.outputs.signatureHashes || _options.compiler.outputs.storageLayout || - _options.compiler.outputs.transientStorageLayout; + _options.compiler.outputs.transientStorageLayout || + _options.compiler.outputs.assemblyStructure; } static bool coloredOutput(CommandLineOptions const& _options) @@ -210,7 +212,6 @@ void CommandLineInterface::handleBinary(std::string const& _contract) binary = objectWithLinkRefsHex(m_assemblyStack->object(_contract)); if (m_options.compiler.outputs.binaryRuntime) binaryRuntime = objectWithLinkRefsHex(m_assemblyStack->runtimeObject(_contract)); - if (m_options.compiler.outputs.binary) { if (!m_options.output.dir.empty()) @@ -591,6 +592,22 @@ void CommandLineInterface::handleEthdebug(std::string const& _contract) } } +void CommandLineInterface::handleAssemblyStructure(std::string const& _contract) +{ + solAssert(CompilerInputModes.count(m_options.input.mode) == 1); + solAssert(m_compiler->compilationSuccessful()); + + if (!m_options.compiler.outputs.assemblyStructure) + return; + + solAssert(m_assemblyStack); + std::string const data = jsonPrint( + removeNullMembers(StandardCompiler::formatAssemblyStructure(m_assemblyStack->object(_contract).subAssemblyData)), + m_options.formatting.json + ); + sout() << "Assembly structure:" << std::endl << data << std::endl; +} + void CommandLineInterface::readInputFiles() { solAssert(!m_standardJsonInput.has_value()); @@ -960,6 +977,7 @@ void CommandLineInterface::compile() m_options.compiler.outputs.binaryRuntime || m_options.compiler.outputs.ethdebug || m_options.compiler.outputs.ethdebugRuntime || + m_options.compiler.outputs.assemblyStructure || (m_options.compiler.combinedJsonRequests && ( m_options.compiler.combinedJsonRequests->binary || m_options.compiler.combinedJsonRequests->binaryRuntime || @@ -970,7 +988,8 @@ void CommandLineInterface::compile() m_options.compiler.combinedJsonRequests->srcMap || m_options.compiler.combinedJsonRequests->srcMapRuntime || m_options.compiler.combinedJsonRequests->funDebug || - m_options.compiler.combinedJsonRequests->funDebugRuntime + m_options.compiler.combinedJsonRequests->funDebugRuntime || + m_options.compiler.combinedJsonRequests->assemblyStructure )); m_compiler->selectContracts({{"", {{"", pipelineConfig}}}}); @@ -1105,6 +1124,10 @@ void CommandLineInterface::handleCombinedJSON() contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData( m_assemblyStack->runtimeObject(contractName).functionDebugData ); + if (m_options.compiler.combinedJsonRequests->assemblyStructure) + contractData[g_strAssemblyStructure] = StandardCompiler::formatAssemblyStructure( + m_assemblyStack->object(contractName).subAssemblyData + ); } } @@ -1467,6 +1490,7 @@ void CommandLineInterface::outputCompilationResults() handleNatspec(true, contract); handleNatspec(false, contract); handleEthdebug(contract); + handleAssemblyStructure(contract); } // end of contracts iteration } diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index b8a10c372db2..5c02af19fd6d 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -119,6 +119,7 @@ class CommandLineInterface void handleStorageLayout(std::string const& _contract); void handleTransientStorageLayout(std::string const& _contract); void handleEthdebug(std::string const& _contract); + void handleAssemblyStructure(std::string const& _contract); /// Tries to read @ m_sourceCodes as a JSONs holding ASTs /// such that they can be imported into the compiler (importASTs()) diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index a382c5217377..400bb5ae4fa1 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -475,6 +475,7 @@ void CommandLineParser::parseOutputSelection() CompilerOutputs::componentName(&CompilerOutputs::asmJson), CompilerOutputs::componentName(&CompilerOutputs::yulCFGJson), CompilerOutputs::componentName(&CompilerOutputs::ethdebug), + CompilerOutputs::componentName(&CompilerOutputs::assemblyStructure), }; static std::set const evmAssemblyJsonImportModeOutputs = { CompilerOutputs::componentName(&CompilerOutputs::asm_), @@ -482,6 +483,7 @@ void CommandLineParser::parseOutputSelection() CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime), CompilerOutputs::componentName(&CompilerOutputs::opcodes), CompilerOutputs::componentName(&CompilerOutputs::asmJson), + CompilerOutputs::componentName(&CompilerOutputs::assemblyStructure), }; switch (_mode) { @@ -775,6 +777,7 @@ General Information)").c_str(), (CompilerOutputs::componentName(&CompilerOutputs::metadata).c_str(), "Combined Metadata JSON whose IPFS hash is stored on-chain.") (CompilerOutputs::componentName(&CompilerOutputs::storageLayout).c_str(), "Slots, offsets and types of the contract's state variables located in storage.") (CompilerOutputs::componentName(&CompilerOutputs::transientStorageLayout).c_str(), "Slots, offsets and types of the contract's state variables located in transient storage.") + (CompilerOutputs::componentName(&CompilerOutputs::assemblyStructure).c_str(), "Structure of the assembly and its subassemblies providing offsets, lengths and creation indicators.") ; if (!_forHelp) // Note: We intentionally keep this undocumented for now. { @@ -1577,7 +1580,7 @@ void CommandLineParser::parseCombinedJsonOption() &CombinedJsonRequests::natspecUser, &CombinedJsonRequests::signatureHashes, &CombinedJsonRequests::storageLayout, - &CombinedJsonRequests::transientStorageLayout + &CombinedJsonRequests::transientStorageLayout, }; for (auto const invalidOption: invalidOptions) diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index d8141cb4fcf0..87209af7edce 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -90,6 +90,7 @@ struct CompilerOutputs {"yul-cfg-json", &CompilerOutputs::yulCFGJson}, {"ethdebug", &CompilerOutputs::ethdebug}, {"ethdebug-runtime", &CompilerOutputs::ethdebugRuntime}, + {"assembly-structure", &CompilerOutputs::assemblyStructure}, }; return components; } @@ -114,6 +115,7 @@ struct CompilerOutputs bool transientStorageLayout = false; bool ethdebug = false; bool ethdebugRuntime = false; + bool assemblyStructure = false; }; struct CombinedJsonRequests @@ -144,6 +146,7 @@ struct CombinedJsonRequests {"devdoc", &CombinedJsonRequests::natspecDev}, {"userdoc", &CombinedJsonRequests::natspecUser}, {"ast", &CombinedJsonRequests::ast}, + {"assembly-structure", &CombinedJsonRequests::assemblyStructure}, }; return components; } @@ -166,6 +169,7 @@ struct CombinedJsonRequests bool natspecDev = false; bool natspecUser = false; bool ast = false; + bool assemblyStructure = false; }; struct CommandLineOptions diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c66963c190d4..f7b1528a291a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -110,6 +110,7 @@ set(libsolidity_sources libsolidity/SolidityParser.cpp libsolidity/SolidityTypes.cpp libsolidity/StandardCompiler.cpp + libsolidity/SubAssemblyOffsetsTest.cpp libsolidity/SyntaxTest.cpp libsolidity/SyntaxTest.h libsolidity/ViewPureChecker.cpp diff --git a/test/cmdlineTests/assembly_structure/args b/test/cmdlineTests/assembly_structure/args new file mode 100644 index 000000000000..a34b84109897 --- /dev/null +++ b/test/cmdlineTests/assembly_structure/args @@ -0,0 +1 @@ +--assembly-structure --pretty-json --json-indent 4 --no-cbor-metadata diff --git a/test/cmdlineTests/assembly_structure/input.sol b/test/cmdlineTests/assembly_structure/input.sol new file mode 100644 index 000000000000..61a2b0556528 --- /dev/null +++ b/test/cmdlineTests/assembly_structure/input.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity > 0.0; + +contract A {} + +contract Storage { + uint256 public number; + bytes public code = type(A).creationCode; + + function store(uint256 num) public { + number = num; + } +} diff --git a/test/cmdlineTests/assembly_structure/output b/test/cmdlineTests/assembly_structure/output new file mode 100644 index 000000000000..976a10b114cf --- /dev/null +++ b/test/cmdlineTests/assembly_structure/output @@ -0,0 +1,42 @@ + +======= input.sol:A ======= +Assembly structure: +{ + "isCreation": true, + "length": 34, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 26 + } + ] +} + +======= input.sol:Storage ======= +Assembly structure: +{ + "isCreation": true, + "length": 1595, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 684, + "start": 877 + }, + { + "isCreation": true, + "length": 34, + "start": 1561, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 26 + } + ] + } + ] +} diff --git a/test/cmdlineTests/assembly_structure_via_ir/args b/test/cmdlineTests/assembly_structure_via_ir/args new file mode 100644 index 000000000000..db2272d3c96c --- /dev/null +++ b/test/cmdlineTests/assembly_structure_via_ir/args @@ -0,0 +1 @@ +--assembly-structure --pretty-json --json-indent 4 --no-cbor-metadata --via-ir diff --git a/test/cmdlineTests/assembly_structure_via_ir/input.sol b/test/cmdlineTests/assembly_structure_via_ir/input.sol new file mode 100644 index 000000000000..61a2b0556528 --- /dev/null +++ b/test/cmdlineTests/assembly_structure_via_ir/input.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity > 0.0; + +contract A {} + +contract Storage { + uint256 public number; + bytes public code = type(A).creationCode; + + function store(uint256 num) public { + number = num; + } +} diff --git a/test/cmdlineTests/assembly_structure_via_ir/output b/test/cmdlineTests/assembly_structure_via_ir/output new file mode 100644 index 000000000000..b0962f08e777 --- /dev/null +++ b/test/cmdlineTests/assembly_structure_via_ir/output @@ -0,0 +1,42 @@ + +======= input.sol:A ======= +Assembly structure: +{ + "isCreation": true, + "length": 48, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 40 + } + ] +} + +======= input.sol:Storage ======= +Assembly structure: +{ + "isCreation": true, + "length": 1866, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 1036, + "start": 782 + }, + { + "isCreation": true, + "length": 48, + "start": 1818, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 40 + } + ] + } + ] +} diff --git a/test/cmdlineTests/linker_mode_output_selection_invalid/args b/test/cmdlineTests/linker_mode_output_selection_invalid/args index 6c5b1680bc18..81daa642c018 100644 --- a/test/cmdlineTests/linker_mode_output_selection_invalid/args +++ b/test/cmdlineTests/linker_mode_output_selection_invalid/args @@ -1 +1 @@ ---link --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout +--link --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout --assembly-structure diff --git a/test/cmdlineTests/linker_mode_output_selection_invalid/err b/test/cmdlineTests/linker_mode_output_selection_invalid/err index 5ca7497883af..53fdc373c31c 100644 --- a/test/cmdlineTests/linker_mode_output_selection_invalid/err +++ b/test/cmdlineTests/linker_mode_output_selection_invalid/err @@ -1 +1 @@ -Error: The following outputs are not supported in linker mode: --abi, --asm, --asm-json, --bin, --bin-runtime, --devdoc, --hashes, --ir, --ir-optimized, --metadata, --opcodes, --storage-layout, --userdoc. +Error: The following outputs are not supported in linker mode: --abi, --asm, --asm-json, --assembly-structure, --bin, --bin-runtime, --devdoc, --hashes, --ir, --ir-optimized, --metadata, --opcodes, --storage-layout, --userdoc. diff --git a/test/cmdlineTests/standard_cli_output_selection_invalid/args b/test/cmdlineTests/standard_cli_output_selection_invalid/args index 691b7fa54acd..f9a98cec7879 100644 --- a/test/cmdlineTests/standard_cli_output_selection_invalid/args +++ b/test/cmdlineTests/standard_cli_output_selection_invalid/args @@ -1 +1 @@ ---ast-compact-json --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout +--ast-compact-json --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout --assembly-structure diff --git a/test/cmdlineTests/standard_cli_output_selection_invalid/err b/test/cmdlineTests/standard_cli_output_selection_invalid/err index cdbcdd1585f8..a47341d08a3a 100644 --- a/test/cmdlineTests/standard_cli_output_selection_invalid/err +++ b/test/cmdlineTests/standard_cli_output_selection_invalid/err @@ -1 +1 @@ -Error: The following outputs are not supported in standard JSON mode: --abi, --asm, --asm-json, --ast-compact-json, --bin, --bin-runtime, --devdoc, --hashes, --ir, --ir-optimized, --metadata, --opcodes, --storage-layout, --userdoc. +Error: The following outputs are not supported in standard JSON mode: --abi, --asm, --asm-json, --assembly-structure, --ast-compact-json, --bin, --bin-runtime, --devdoc, --hashes, --ir, --ir-optimized, --metadata, --opcodes, --storage-layout, --userdoc. diff --git a/test/cmdlineTests/standard_import_asm_json/output.json b/test/cmdlineTests/standard_import_asm_json/output.json index 1db28796071a..f1acd552ee97 100644 --- a/test/cmdlineTests/standard_import_asm_json/output.json +++ b/test/cmdlineTests/standard_import_asm_json/output.json @@ -7,6 +7,12 @@ 0x00 ", "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 1, + "start": 0, + "subAssemblies": [] + }, "functionDebugData": {}, "linkReferences": {}, "object": "", diff --git a/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json b/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json index f0554d8c602b..bc44e18a8901 100644 --- a/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json +++ b/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json @@ -4,6 +4,12 @@ "": { "evm": { "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 5, + "start": 0, + "subAssemblies": [] + }, "functionDebugData": {}, "linkReferences": {}, "object": "", diff --git a/test/cmdlineTests/standard_import_asm_json_link_references/output.json b/test/cmdlineTests/standard_import_asm_json_link_references/output.json index fed476e620c5..2975c0be2521 100644 --- a/test/cmdlineTests/standard_import_asm_json_link_references/output.json +++ b/test/cmdlineTests/standard_import_asm_json_link_references/output.json @@ -4,6 +4,12 @@ "": { "evm": { "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 1, + "start": 0, + "subAssemblies": [] + }, "functionDebugData": {}, "linkReferences": {}, "object": "", diff --git a/test/cmdlineTests/standard_import_ast_select_bytecode/input.json b/test/cmdlineTests/standard_import_ast_select_bytecode/input.json index 6458cb7e151e..2e599d9582c0 100644 --- a/test/cmdlineTests/standard_import_ast_select_bytecode/input.json +++ b/test/cmdlineTests/standard_import_ast_select_bytecode/input.json @@ -45,6 +45,9 @@ "evm.bytecode.sourceMap" ] } + }, + "metadata": { + "appendCBOR": false } } } diff --git a/test/cmdlineTests/standard_import_ast_select_bytecode/output.json b/test/cmdlineTests/standard_import_ast_select_bytecode/output.json index c060efd971a7..aa11b9a91009 100644 --- a/test/cmdlineTests/standard_import_ast_select_bytecode/output.json +++ b/test/cmdlineTests/standard_import_ast_select_bytecode/output.json @@ -4,6 +4,18 @@ "test": { "evm": { "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 34, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 26 + } + ] + }, "functionDebugData": {}, "generatedSources": [], "linkReferences": {}, diff --git a/test/cmdlineTests/standard_no_append_cbor/output.json b/test/cmdlineTests/standard_no_append_cbor/output.json index c060efd971a7..d689da3a9b9b 100644 --- a/test/cmdlineTests/standard_no_append_cbor/output.json +++ b/test/cmdlineTests/standard_no_append_cbor/output.json @@ -4,6 +4,18 @@ "test": { "evm": { "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 27, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 3, + "start": 24 + } + ] + }, "functionDebugData": {}, "generatedSources": [], "linkReferences": {}, diff --git a/test/cmdlineTests/standard_subassembly_offsets/input.json b/test/cmdlineTests/standard_subassembly_offsets/input.json new file mode 100644 index 000000000000..b5362c21c11c --- /dev/null +++ b/test/cmdlineTests/standard_subassembly_offsets/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": { + "contract.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.7.0 <0.9.0;\ncontract A {}\ncontract Storage { uint256 public number; bytes public code = type(A).creationCode; function store(uint256 num) public { number = num; }}" + } + }, + "settings": { + "evmVersion": "cancun", + "outputSelection": { + "*": { + "Storage": [ + "evm.bytecode.assemblyStructure" + ] + } + }, + "metadata": { + "appendCBOR": false + } + } +} diff --git a/test/cmdlineTests/standard_subassembly_offsets/output.json b/test/cmdlineTests/standard_subassembly_offsets/output.json new file mode 100644 index 000000000000..d08ddd9eb3b5 --- /dev/null +++ b/test/cmdlineTests/standard_subassembly_offsets/output.json @@ -0,0 +1,41 @@ +{ + "contracts": { + "contract.sol": { + "Storage": { + "evm": { + "bytecode": { + "assemblyStructure": { + "isCreation": true, + "length": 1595, + "start": 0, + "subAssemblies": [ + { + "isCreation": false, + "length": 684, + "start": 877 + }, + { + "isCreation": true, + "length": 34, + "start": 1561, + "subAssemblies": [ + { + "isCreation": false, + "length": 8, + "start": 26 + } + ] + } + ] + } + } + } + } + } + }, + "sources": { + "contract.sol": { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json index be87a5176d06..c3ecc7b78ad4 100644 --- a/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json +++ b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json @@ -11,6 +11,7 @@ "evm": { "assembly": "", "bytecode": { + "assemblyStructure": {}, "functionDebugData": {}, "generatedSources": [], "linkReferences": {}, @@ -60,6 +61,7 @@ "evm": { "assembly": "", "bytecode": { + "assemblyStructure": {}, "functionDebugData": {}, "generatedSources": [], "linkReferences": {}, diff --git a/test/cmdlineTests/strict_asm_output_selection_invalid/args b/test/cmdlineTests/strict_asm_output_selection_invalid/args index daeb64aae98b..378ebf46141a 100644 --- a/test/cmdlineTests/strict_asm_output_selection_invalid/args +++ b/test/cmdlineTests/strict_asm_output_selection_invalid/args @@ -1 +1 @@ ---strict-assembly --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout +--strict-assembly --asm --asm-json --opcodes --bin --bin-runtime --abi --ir --ir-optimized --hashes --userdoc --devdoc --metadata --storage-layout --assembly-structure diff --git a/test/libsolidity/SubAssemblyOffsetsTest.cpp b/test/libsolidity/SubAssemblyOffsetsTest.cpp new file mode 100644 index 000000000000..6384fba04787 --- /dev/null +++ b/test/libsolidity/SubAssemblyOffsetsTest.cpp @@ -0,0 +1,157 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +/** + * @date 2025 + * Unit tests for subassembly offset support + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace solidity::frontend::test +{ + +BOOST_AUTO_TEST_SUITE(SubAssemblyOffsets) + +BOOST_AUTO_TEST_CASE(non_empty_contract) +{ + char const* sourceCode = R"( + pragma solidity >=0.0; + contract test { + function g(function(uint) external returns (uint) x) public {} + } + )"; + + for (auto metadataFormat: std::set{ + CompilerStack::MetadataFormat::NoMetadata, + CompilerStack::MetadataFormat::WithReleaseVersionTag, + CompilerStack::MetadataFormat::WithPrereleaseVersionTag + }) + for (auto metadataHash: std::set{ + CompilerStack::MetadataHash::IPFS, + CompilerStack::MetadataHash::Bzzr1, + CompilerStack::MetadataHash::None + }) { + for (auto optimizerSettings: std::vector{ + OptimiserSettings::standard(), + OptimiserSettings::minimal() + }) + { + for (auto viaIR: std::vector{false, true}) + { + CompilerStack compilerStack; + compilerStack.setMetadataFormat(metadataFormat); + compilerStack.setSources({{"", sourceCode}}); + compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + compilerStack.setOptimiserSettings(optimizerSettings); + compilerStack.setMetadataHash(metadataHash); + compilerStack.setViaIR(viaIR); + BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); + evmasm::LinkerObject const& linkerObject = compilerStack.object("test"); + BOOST_REQUIRE(linkerObject.subAssemblyData.size() == 1); + evmasm::LinkerObject::Structure const& rootObject = linkerObject.subAssemblyData[0]; + BOOST_REQUIRE(linkerObject.bytecode.size() == rootObject.length); + BOOST_REQUIRE(rootObject.start == 0); + BOOST_REQUIRE(rootObject.isCreation == true); + BOOST_REQUIRE(rootObject.subAssemblies.size() == 1); + evmasm::LinkerObject::Structure const& subAssemblyObject = rootObject.subAssemblies[0]; + BOOST_REQUIRE(subAssemblyObject.isCreation == false); + BOOST_REQUIRE(subAssemblyObject.start + subAssemblyObject.length == rootObject.length); + BOOST_REQUIRE(subAssemblyObject.subAssemblies.empty()); + } + } + } +} + +BOOST_AUTO_TEST_CASE(contract_with_nested_subassembly) +{ + char const* sourceCode = R"( + contract A {} + + contract Storage { + uint256 public number; + bytes public code = type(A).creationCode; + + function store(uint256 num) public { + number = num; + } + } + )"; + + for (auto metadataFormat: std::set{ + CompilerStack::MetadataFormat::NoMetadata, + CompilerStack::MetadataFormat::WithReleaseVersionTag, + CompilerStack::MetadataFormat::WithPrereleaseVersionTag + }) + for (auto metadataHash: std::set{ + CompilerStack::MetadataHash::IPFS, + CompilerStack::MetadataHash::Bzzr1, + CompilerStack::MetadataHash::None + }) { + for (auto optimizerSettings: std::vector{ + OptimiserSettings::standard(), + OptimiserSettings::minimal() + }) + { + for (auto viaIR: std::vector{true, false}) + { + CompilerStack compilerStack; + compilerStack.setMetadataFormat(metadataFormat); + compilerStack.setSources({{"", sourceCode}}); + compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + compilerStack.setOptimiserSettings(optimizerSettings); + compilerStack.setMetadataHash(metadataHash); + compilerStack.setViaIR(viaIR); + BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); + evmasm::LinkerObject const& linkerObject = compilerStack.object("Storage"); + + BOOST_REQUIRE(linkerObject.subAssemblyData.size() == 1); + evmasm::LinkerObject::Structure const& rootObject = linkerObject.subAssemblyData[0]; + BOOST_REQUIRE(linkerObject.bytecode.size() == rootObject.length); + BOOST_REQUIRE(rootObject.start == 0); + BOOST_REQUIRE(rootObject.isCreation == true); + BOOST_REQUIRE(rootObject.subAssemblies.size() == 2); + + evmasm::LinkerObject::Structure const& firstSubAssemblyObject = rootObject.subAssemblies[0]; + evmasm::LinkerObject::Structure const& secondSubAssemblyObject = rootObject.subAssemblies[1]; + BOOST_REQUIRE(firstSubAssemblyObject.isCreation == false); + BOOST_REQUIRE(firstSubAssemblyObject.start + firstSubAssemblyObject.length == secondSubAssemblyObject.start); + BOOST_REQUIRE(firstSubAssemblyObject.subAssemblies.empty()); + BOOST_REQUIRE(secondSubAssemblyObject.isCreation == true); + BOOST_REQUIRE(secondSubAssemblyObject.start + secondSubAssemblyObject.length == rootObject.length); + BOOST_REQUIRE(secondSubAssemblyObject.subAssemblies.size() == 1); + + evmasm::LinkerObject::Structure const& thirdSubAssemblyObject = secondSubAssemblyObject.subAssemblies[0]; + BOOST_REQUIRE(thirdSubAssemblyObject.isCreation == false); + BOOST_REQUIRE(thirdSubAssemblyObject.start + thirdSubAssemblyObject.length == secondSubAssemblyObject.length); + BOOST_REQUIRE(thirdSubAssemblyObject.subAssemblies.empty()); + } + } + } +} + +BOOST_AUTO_TEST_SUITE_END() + +}