diff --git a/Changelog.md b/Changelog.md index 967e548814ef..18e0deeaf612 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,13 +2,11 @@ Language Features: - Compiler Features: - +* ethdebug: Experimental support for instructions and source locations under EOF. Bugfixes: - ### 0.8.30 (2025-05-07) Compiler Features: diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index a7441b85e264..74aaeb6c293b 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -6,6 +6,8 @@ set(sources AssemblyItem.h Ethdebug.cpp Ethdebug.h + EthdebugSchema.cpp + EthdebugSchema.h EVMAssemblyStack.cpp EVMAssemblyStack.h BlockDeduplicator.cpp diff --git a/libevmasm/Ethdebug.cpp b/libevmasm/Ethdebug.cpp index 6e635ef57952..8b87aadd072a 100644 --- a/libevmasm/Ethdebug.cpp +++ b/libevmasm/Ethdebug.cpp @@ -18,6 +18,10 @@ #include +#include + +#include + using namespace solidity; using namespace solidity::evmasm; using namespace solidity::evmasm::ethdebug; @@ -25,72 +29,124 @@ using namespace solidity::evmasm::ethdebug; namespace { -Json programInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned _sourceId) +schema::program::Instruction::Operation instructionOperation(Assembly const& _assembly, LinkerObject const& _linkerObject, size_t const _start, size_t const _end) { - solUnimplementedAssert(_assembly.eofVersion() == std::nullopt, "ethdebug does not yet support EOF."); - solUnimplementedAssert(_assembly.codeSections().size() == 1, "ethdebug does not yet support multiple code-sections."); - for (auto const& instruction: _assembly.codeSections()[0].items) - solUnimplementedAssert(instruction.type() != VerbatimBytecode, "Verbatim bytecode is currently not supported by ethdebug."); - - solAssert(_linkerObject.codeSectionLocations.size() == 1); - solAssert(_linkerObject.codeSectionLocations[0].end <= _linkerObject.bytecode.size()); - Json instructions = Json::array(); - for (size_t i = 0; i < _linkerObject.codeSectionLocations[0].instructionLocations.size(); ++i) + solAssert(_end <= _linkerObject.bytecode.size()); + solAssert(_start < _end); + schema::program::Instruction::Operation operation; + operation.mnemonic = instructionInfo(static_cast(_linkerObject.bytecode[_start]), _assembly.evmVersion()).name; + static size_t constexpr instructionSize = 1; + if (_start + instructionSize < _end) { - LinkerObject::InstructionLocation currentInstruction = _linkerObject.codeSectionLocations[0].instructionLocations[i]; - size_t start = currentInstruction.start; - size_t end = currentInstruction.end; - size_t assemblyItemIndex = currentInstruction.assemblyItemIndex; - solAssert(end <= _linkerObject.bytecode.size()); - solAssert(start < end); - solAssert(assemblyItemIndex < _assembly.codeSections().at(0).items.size()); - Json operation = Json::object(); - operation["mnemonic"] = instructionInfo(static_cast(_linkerObject.bytecode[start]), _assembly.evmVersion()).name; - static size_t constexpr instructionSize = 1; - if (start + instructionSize < end) - { - bytes const argumentData( - _linkerObject.bytecode.begin() + static_cast(start) + instructionSize, - _linkerObject.bytecode.begin() + static_cast(end) - ); - solAssert(!argumentData.empty()); - operation["arguments"] = Json::array({util::toHex(argumentData, util::HexPrefix::Add)}); - } - langutil::SourceLocation const& location = _assembly.codeSections().at(0).items.at(assemblyItemIndex).location(); - Json instruction = Json::object(); - instruction["offset"] = start; - instruction["operation"] = operation; - - instruction["context"] = Json::object(); - instruction["context"]["code"] = Json::object(); - instruction["context"]["code"]["source"] = Json::object(); - instruction["context"]["code"]["source"]["id"] = static_cast(_sourceId); - - instruction["context"]["code"]["range"] = Json::object(); - instruction["context"]["code"]["range"]["offset"] = location.start; - instruction["context"]["code"]["range"]["length"] = location.end - location.start; - instructions.emplace_back(instruction); + bytes const argumentData( + _linkerObject.bytecode.begin() + static_cast(_start) + instructionSize, + _linkerObject.bytecode.begin() + static_cast(_end) + ); + solAssert(!argumentData.empty()); + operation.arguments = {{schema::data::HexValue{argumentData}}}; } + return operation; +} - return instructions; +schema::materials::SourceRange::Range locationRange(langutil::SourceLocation const& _location) +{ + return { + .length = schema::data::Unsigned{_location.end - _location.start}, + .offset = schema::data::Unsigned{_location.start} + }; } -} // anonymous namespace +schema::materials::Reference sourceReference(unsigned _sourceID) +{ + return { + .id = schema::materials::ID{_sourceID}, + .type = std::nullopt + }; +} -Json ethdebug::program(std::string_view _name, unsigned _sourceId, Assembly const* _assembly, LinkerObject const& _linkerObject) +std::optional instructionContext(Assembly::CodeSection const& _codeSection, size_t _assemblyItemIndex, unsigned _sourceID) { - Json result = Json::object(); - result["contract"] = Json::object(); - result["contract"]["name"] = _name; - result["contract"]["definition"] = Json::object(); - result["contract"]["definition"]["source"] = Json::object(); - result["contract"]["definition"]["source"]["id"] = _sourceId; - if (_assembly) + solAssert(_assemblyItemIndex < _codeSection.items.size()); + langutil::SourceLocation const& location = _codeSection.items.at(_assemblyItemIndex).location(); + if (!location.isValid()) + return std::nullopt; + + return schema::program::Context{ + schema::materials::SourceRange{ + .source = sourceReference(_sourceID), + .range = locationRange(location) + }, + std::nullopt, + std::nullopt + }; +} + +std::vector codeSectionInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned const _sourceID, size_t const _codeSectionIndex) +{ + solAssert(_codeSectionIndex < _linkerObject.codeSectionLocations.size()); + solAssert(_codeSectionIndex < _assembly.codeSections().size()); + auto const& locations = _linkerObject.codeSectionLocations[_codeSectionIndex]; + auto const& codeSection = _assembly.codeSections().at(_codeSectionIndex); + + std::vector instructions; + instructions.reserve(codeSection.items.size()); + + bool const codeSectionContainsVerbatim = ranges::any_of( + codeSection.items, + [](auto const& _instruction) { return _instruction.type() == VerbatimBytecode; } + ); + solUnimplementedAssert(!codeSectionContainsVerbatim, "Verbatim bytecode is currently not supported by ethdebug."); + + for (auto const& currentInstruction: locations.instructionLocations) { - result["environment"] = _assembly->isCreation() ? "create" : "call"; - result["instructions"] = programInstructions(*_assembly, _linkerObject, _sourceId); + size_t const start = currentInstruction.start; + size_t const end = currentInstruction.end; + + // some instructions do not contribute to the bytecode + if (start == end) + continue; + + instructions.emplace_back(schema::program::Instruction{ + .offset = schema::data::Unsigned{start}, + .operation = instructionOperation(_assembly, _linkerObject, start, end), + .context = instructionContext(codeSection, currentInstruction.assemblyItemIndex, _sourceID) + }); } - return result; + + return instructions; +} + +std::vector programInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned const _sourceID) +{ + auto const numCodeSections = _assembly.codeSections().size(); + solAssert(numCodeSections == _linkerObject.codeSectionLocations.size()); + + std::vector instructionInfo; + for (size_t codeSectionIndex = 0; codeSectionIndex < numCodeSections; ++codeSectionIndex) + instructionInfo += codeSectionInstructions(_assembly, _linkerObject, _sourceID, codeSectionIndex); + return instructionInfo; +} + +} // anonymous namespace + +Json ethdebug::program(std::string_view _name, unsigned _sourceID, Assembly const& _assembly, LinkerObject const& _linkerObject) +{ + return schema::Program{ + .compilation = std::nullopt, + .contract = { + .name = std::string{_name}, + .definition = { + .source = { + .id = {_sourceID}, + .type = std::nullopt + }, + .range = std::nullopt + } + }, + .environment = _assembly.isCreation() ? schema::Program::Environment::CREATE : schema::Program::Environment::CALL, + .context = std::nullopt, + .instructions = programInstructions(_assembly, _linkerObject, _sourceID) + }; } Json ethdebug::resources(std::vector const& _sources, std::string const& _version) diff --git a/libevmasm/Ethdebug.h b/libevmasm/Ethdebug.h index 72ac16037969..2e0df3484ba6 100644 --- a/libevmasm/Ethdebug.h +++ b/libevmasm/Ethdebug.h @@ -27,7 +27,7 @@ namespace solidity::evmasm::ethdebug { // returns ethdebug/format/program. -Json program(std::string_view _name, unsigned _sourceId, Assembly const* _assembly, LinkerObject const& _linkerObject); +Json program(std::string_view _name, unsigned _sourceID, Assembly const& _assembly, LinkerObject const& _linkerObject); // returns ethdebug/format/info/resources Json resources(std::vector const& _sources, std::string const& _version); diff --git a/libevmasm/EthdebugSchema.cpp b/libevmasm/EthdebugSchema.cpp new file mode 100644 index 000000000000..c54b7167f53e --- /dev/null +++ b/libevmasm/EthdebugSchema.cpp @@ -0,0 +1,143 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include + +using namespace solidity; +using namespace solidity::evmasm::ethdebug; + +void schema::data::to_json(Json& _json, HexValue const& _hexValue) +{ + _json = util::toHex(_hexValue.value, util::HexPrefix::Add); +} + +void schema::data::to_json(Json& _json, Unsigned const& _unsigned) +{ + std::visit(util::GenericVisitor{ + [&](HexValue const& _hexValue) { _json = _hexValue; }, + [&](std::uint64_t const _value) { _json = _value; } + }, _unsigned.value); +} + +void schema::materials::to_json(Json& _json, ID const& _id) +{ + std::visit(util::GenericVisitor{ + [&](std::string const& _hexValue) { _json = _hexValue; }, + [&](std::uint64_t const _value) { _json = _value; } + }, _id.value); +} + +void schema::materials::to_json(Json& _json, Reference const& _source) +{ + _json["id"] = _source.id; + if (_source.type) + _json["type"] = *_source.type == Reference::Type::Compilation ? "compilation" : "source"; +} + +void schema::materials::to_json(Json& _json, SourceRange::Range const& _range) +{ + _json["length"] = _range.length; + _json["offset"] = _range.offset; +} + + +void schema::materials::to_json(Json& _json, SourceRange const& _sourceRange) +{ + _json["source"] = _sourceRange.source; + if (_sourceRange.range) + _json["range"] = *_sourceRange.range; +} + +void schema::to_json(Json& _json, Program::Contract const& _contract) +{ + if (_contract.name) + _json["name"] = *_contract.name; + _json["definition"] = _contract.definition; +} + +void schema::program::to_json(Json& _json, Context::Variable const& _contextVariable) +{ + auto const numProperties = + _contextVariable.identifier.has_value() + + _contextVariable.declaration.has_value(); + solRequire(numProperties >= 1, EthdebugException, "Context variable has no properties."); + if (_contextVariable.identifier) + { + solRequire(!_contextVariable.identifier->empty(), EthdebugException, "Variable identifier must not be empty."); + _json["identifier"] = *_contextVariable.identifier; + } + if (_contextVariable.declaration) + _json["declaration"] = *_contextVariable.declaration; +} + +void schema::program::to_json(Json& _json, Context const& _context) +{ + solRequire(_context.code.has_value() + _context.remark.has_value() + _context.variables.has_value() >= 1, EthdebugException, "Context needs >=1 properties."); + if (_context.code) + _json["code"] = *_context.code; + if (_context.variables) + { + solRequire(!_context.variables->empty(), EthdebugException, "Context variables must not be empty if provided."); + _json["variables"] = *_context.variables; + } + if (_context.remark) + _json["remark"] = *_context.remark; +} + +void schema::program::to_json(Json& _json, Instruction::Operation const& _operation) +{ + _json = { {"mnemonic", _operation.mnemonic} }; + if (!_operation.arguments.empty()) + _json["arguments"] = _operation.arguments; +} + +void schema::program::to_json(Json& _json, Instruction const& _instruction) +{ + _json["offset"] = _instruction.offset; + if (_instruction.operation) + _json["operation"] = *_instruction.operation; + if (_instruction.context) + _json["context"] = *_instruction.context; +} + +void schema::to_json(Json& _json, Program const& _program) +{ + if (_program.compilation) + _json["compilation"] = *_program.compilation; + _json["contract"] = _program.contract; + _json["environment"] = _program.environment; + if (_program.context) + _json["context"] = *_program.context; + _json["instructions"] = _program.instructions; +} + +void schema::to_json(Json& _json, Program::Environment const& _environment) +{ + switch (_environment) + { + case Program::Environment::CALL: + _json = "call"; + break; + case Program::Environment::CREATE: + _json = "create"; + break; + } +} diff --git a/libevmasm/EthdebugSchema.h b/libevmasm/EthdebugSchema.h new file mode 100644 index 000000000000..5fc8f78fd62e --- /dev/null +++ b/libevmasm/EthdebugSchema.h @@ -0,0 +1,172 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace solidity::evmasm::ethdebug::schema +{ + +struct EthdebugException: virtual util::Exception {}; + +namespace data +{ + +struct HexValue +{ + bytes value; +}; + +struct Unsigned +{ + template + Unsigned(T const _value) + { + solRequire(static_cast(_value) <= std::numeric_limits::max(), EthdebugException, "Too large value."); + value = static_cast(_value); + } + template + Unsigned(T const _value) + { + solRequire(_value >= 0, EthdebugException, "NonNegativeValue got negative value."); + solRequire(static_cast>(_value) <= std::numeric_limits::max(), EthdebugException, "Too large value."); + value = static_cast(_value); + } + Unsigned(HexValue&& _value): value(std::move(_value)) {} + + std::variant value; +}; + +} + +namespace materials +{ + +struct ID +{ + std::variant value; +}; + +struct Reference +{ + enum class Type { Compilation, Source }; + ID id; + std::optional type; +}; + +struct SourceRange +{ + struct Range + { + data::Unsigned length; + data::Unsigned offset; + }; + + Reference source; + std::optional range; +}; + +} + +namespace program +{ + +struct Context +{ + struct Variable + { + std::optional identifier; + std::optional declaration; + // TODO: type + // TODO: pointer according to ethdebug/format/spec/pointer + }; + + std::optional code; + std::optional> variables; + std::optional remark; +}; + +struct Instruction +{ + struct Operation + { + std::string mnemonic; + std::vector arguments; + }; + + data::Unsigned offset; + std::optional operation; + std::optional context; +}; + +} + +struct Program +{ + enum class Environment + { + CALL, CREATE + }; + + struct Contract + { + std::optional name; + materials::SourceRange definition; + }; + + std::optional compilation; + Contract contract; + Environment environment; + std::optional context; + std::vector instructions; +}; + +namespace data +{ +void to_json(Json& _json, HexValue const& _hexValue); +void to_json(Json& _json, Unsigned const& _unsigned); +} + +namespace materials +{ +void to_json(Json& _json, ID const& _id); +void to_json(Json& _json, Reference const& _source); +void to_json(Json& _json, SourceRange::Range const& _range); +void to_json(Json& _json, SourceRange const& _sourceRange); +} + +namespace program +{ +void to_json(Json& _json, Context::Variable const& _contextVariable); +void to_json(Json& _json, Context const& _context); +void to_json(Json& _json, Instruction::Operation const& _operation); +void to_json(Json& _json, Instruction const& _instruction); +} + +void to_json(Json& _json, Program::Contract const& _contract); +void to_json(Json& _json, Program::Environment const& _environment); +void to_json(Json& _json, Program const& _program); + +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1b0a299f7291..975ed3c1982d 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1216,8 +1216,11 @@ Json CompilerStack::ethdebug(Contract const& _contract, bool _runtime) const solUnimplementedAssert(!isExperimentalSolidity()); evmasm::LinkerObject const& object = _runtime ? _contract.runtimeObject : _contract.object; std::shared_ptr const& assembly = _runtime ? _contract.evmRuntimeAssembly : _contract.evmAssembly; + if (!assembly) + return {}; + solAssert(sourceIndices().contains(_contract.contract->sourceUnitName())); - return evmasm::ethdebug::program(_contract.contract->name(), sourceIndices()[_contract.contract->sourceUnitName()], assembly.get(), object); + return evmasm::ethdebug::program(_contract.contract->name(), sourceIndices()[_contract.contract->sourceUnitName()], *assembly, object); } bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 85709b987c59..b59b4781fe19 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -275,14 +275,14 @@ YulStack::assembleWithDeployed(std::optional _deployName) ); } if (debugInfoSelection().ethdebug) - creationObject.ethdebug = evmasm::ethdebug::program(creationObject.assembly->name(), 0, creationObject.assembly.get(), *creationObject.bytecode.get()); + creationObject.ethdebug = evmasm::ethdebug::program(creationObject.assembly->name(), 0, *creationObject.assembly, *creationObject.bytecode); if (deployedAssembly) { deployedObject.bytecode = std::make_shared(deployedAssembly->assemble()); deployedObject.assembly = deployedAssembly; if (debugInfoSelection().ethdebug) - deployedObject.ethdebug = evmasm::ethdebug::program(deployedObject.assembly->name(), 0, deployedObject.assembly.get(), *deployedObject.bytecode.get()); + deployedObject.ethdebug = evmasm::ethdebug::program(deployedObject.assembly->name(), 0, *deployedObject.assembly, *deployedObject.bytecode); solAssert(deployedAssembly->codeSections().size() == 1); deployedObject.sourceMappings = std::make_unique( evmasm::AssemblyItem::computeSourceMapping( diff --git a/test/cmdlineTests/ethdebug_eof_container_osaka/args b/test/cmdlineTests/ethdebug_eof_container_osaka/args index 65974c6287da..16466b842d0e 100644 --- a/test/cmdlineTests/ethdebug_eof_container_osaka/args +++ b/test/cmdlineTests/ethdebug_eof_container_osaka/args @@ -1 +1 @@ - --experimental-eof-version 1 --evm-version osaka --ethdebug --via-ir + --experimental-eof-version 1 --evm-version osaka --ethdebug --via-ir --pretty-json --json-indent 4 diff --git a/test/cmdlineTests/ethdebug_eof_container_osaka/err b/test/cmdlineTests/ethdebug_eof_container_osaka/err deleted file mode 100644 index 7714685971d2..000000000000 --- a/test/cmdlineTests/ethdebug_eof_container_osaka/err +++ /dev/null @@ -1 +0,0 @@ -Error: ethdebug does not yet support EOF. diff --git a/test/cmdlineTests/ethdebug_eof_container_osaka/exit b/test/cmdlineTests/ethdebug_eof_container_osaka/exit deleted file mode 100644 index d00491fd7e5b..000000000000 --- a/test/cmdlineTests/ethdebug_eof_container_osaka/exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/test/cmdlineTests/ethdebug_eof_container_osaka/output b/test/cmdlineTests/ethdebug_eof_container_osaka/output index b29297ee3749..e9b57d885711 100644 --- a/test/cmdlineTests/ethdebug_eof_container_osaka/output +++ b/test/cmdlineTests/ethdebug_eof_container_osaka/output @@ -1,4 +1,253 @@ ======= Debug Data (ethdebug/format/info/resources) ======= -{"compilation":{"compiler":{"name":"solc","version": ""},"sources":[{"id":0,"path":"input.sol"}]}} +{ + "compilation": { + "compiler": { + "name": "solc", + "version": "" + }, + "sources": [ + { + "id": 0, + "path": "input.sol" + } + ] + } +} ======= input.sol:C ======= +Debug Data (ethdebug/format/program): +{ + "contract": { + "definition": { + "source": { + "id": 0 + } + }, + "name": "C" + }, + "environment": "create", + "instructions": [ + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 30, + "operation": { + "arguments": [ + "0x80" + ], + "mnemonic": "PUSH1" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 32, + "operation": { + "arguments": [ + "0x40" + ], + "mnemonic": "PUSH1" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 34, + "operation": { + "mnemonic": "MSTORE" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 35, + "operation": { + "mnemonic": "CALLVALUE" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 36, + "operation": { + "arguments": [ + "0x0005" + ], + "mnemonic": "RJUMPI" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 39, + "operation": { + "mnemonic": "PUSH0" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 40, + "operation": { + "arguments": [ + "0x80" + ], + "mnemonic": "PUSH1" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 42, + "operation": { + "arguments": [ + "0x00" + ], + "mnemonic": "RETURNCONTRACT" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 44, + "operation": { + "arguments": [ + "0x0001" + ], + "mnemonic": "JUMPF" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 47, + "operation": { + "mnemonic": "PUSH0" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 48, + "operation": { + "mnemonic": "DUP1" + } + }, + { + "context": { + "code": { + "range": { + "length": 41, + "offset": 60 + }, + "source": { + "id": 0 + } + } + }, + "offset": 49, + "operation": { + "mnemonic": "REVERT" + } + } + ] +} diff --git a/test/cmdlineTests/ethdebug_on_abstract/args b/test/cmdlineTests/ethdebug_on_abstract/args new file mode 100644 index 000000000000..c6018b329a67 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_abstract/args @@ -0,0 +1 @@ +--ethdebug --via-ir --pretty-json --json-indent 4 diff --git a/test/cmdlineTests/ethdebug_on_abstract/input.sol b/test/cmdlineTests/ethdebug_on_abstract/input.sol new file mode 100644 index 000000000000..29ff21a20ce8 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_abstract/input.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +pragma solidity >=0.0; + +abstract contract C { + function f() public virtual returns (bytes32); +} diff --git a/test/cmdlineTests/ethdebug_on_abstract/output b/test/cmdlineTests/ethdebug_on_abstract/output new file mode 100644 index 000000000000..744ec0e77642 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_abstract/output @@ -0,0 +1,19 @@ +======= Debug Data (ethdebug/format/info/resources) ======= +{ + "compilation": { + "compiler": { + "name": "solc", + "version": "" + }, + "sources": [ + { + "id": 0, + "path": "input.sol" + } + ] + } +} + +======= input.sol:C ======= +Debug Data (ethdebug/format/program): +null diff --git a/test/cmdlineTests/ethdebug_on_interface/args b/test/cmdlineTests/ethdebug_on_interface/args new file mode 100644 index 000000000000..c6018b329a67 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_interface/args @@ -0,0 +1 @@ +--ethdebug --via-ir --pretty-json --json-indent 4 diff --git a/test/cmdlineTests/ethdebug_on_interface/input.sol b/test/cmdlineTests/ethdebug_on_interface/input.sol new file mode 100644 index 000000000000..cd008c45b465 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_interface/input.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +pragma solidity >=0.0; + +interface C { + function f() external; +} diff --git a/test/cmdlineTests/ethdebug_on_interface/output b/test/cmdlineTests/ethdebug_on_interface/output new file mode 100644 index 000000000000..744ec0e77642 --- /dev/null +++ b/test/cmdlineTests/ethdebug_on_interface/output @@ -0,0 +1,19 @@ +======= Debug Data (ethdebug/format/info/resources) ======= +{ + "compilation": { + "compiler": { + "name": "solc", + "version": "" + }, + "sources": [ + { + "id": 0, + "path": "input.sol" + } + ] + } +} + +======= input.sol:C ======= +Debug Data (ethdebug/format/program): +null diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/input.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/input.json new file mode 100644 index 000000000000..06b2d86e96f6 --- /dev/null +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/input.json @@ -0,0 +1,27 @@ +{ + "language": "Solidity", + "sources": { + "a.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0\npragma solidity >=0.0;\n\nabstract contract C {\n function f() public virtual returns (bytes32);\n}\n" + }, + "b.sol": { + "content": "// SPDX-License-Identifier: GPL-2.0\npragma solidity >=0.0;\n\ninterface C {\n function f() external;\n}\n" + } + }, + "settings": { + "viaIR": true, + "debug": { + "debugInfo": [ + "ethdebug" + ] + }, + "outputSelection": { + "*": { + "*": [ + "evm.bytecode.ethdebug", + "evm.deployedBytecode.ethdebug" + ] + } + } + } +} diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/output.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/output.json new file mode 100644 index 000000000000..57b26680146a --- /dev/null +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_with_interfaces_and_abstracts/output.json @@ -0,0 +1,54 @@ +{ + "contracts": { + "a.sol": { + "C": { + "evm": { + "bytecode": { + "ethdebug": null + }, + "deployedBytecode": { + "ethdebug": null + } + } + } + }, + "b.sol": { + "C": { + "evm": { + "bytecode": { + "ethdebug": null + }, + "deployedBytecode": { + "ethdebug": null + } + } + } + } + }, + "ethdebug": { + "compilation": { + "compiler": { + "name": "solc", + "version": "" + }, + "sources": [ + { + "id": 0, + "path": "a.sol" + }, + { + "id": 1, + "path": "b.sol" + } + ] + } + }, + "sources": { + "a.sol": { + "id": 0 + }, + "b.sol": { + "id": 1 + } + } +} diff --git a/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/args b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/args new file mode 100644 index 000000000000..18532c5a6d3f --- /dev/null +++ b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/args @@ -0,0 +1 @@ +--allow-paths . diff --git a/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/in.yul b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/in.yul new file mode 100644 index 000000000000..aa564d00ce86 --- /dev/null +++ b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/in.yul @@ -0,0 +1,16 @@ +/// @use-src 0:"input.sol" +object "C_6_deployed" { + code { + /// @src 0:60:101 "contract C {..." + mstore(64, 128) + + // f() + fun_f_5() + + /// @src 0:77:99 "function f() public {}" + function fun_f_5() { + sstore(0, 42) + } + /// @src 0:60:101 "contract C {..." + } +} diff --git a/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/input.json b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/input.json new file mode 100644 index 000000000000..a8c3436c1a0f --- /dev/null +++ b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/input.json @@ -0,0 +1,18 @@ +{ + "language": "Yul", + "sources": { + "C": { + "urls": [ + "in.yul" + ] + } + }, + "settings": { + "eofVersion": 1, + "evmVersion": "osaka", + "debug": {"debugInfo": ["ethdebug"]}, + "outputSelection": { + "*": {"*": ["ir", "irOptimized", "evm.bytecode.ethdebug"]} + } + } +} diff --git a/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/output.json b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/output.json new file mode 100644 index 000000000000..2b2f395fadd4 --- /dev/null +++ b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/output.json @@ -0,0 +1,42 @@ +{ + "contracts": { + "C": { + "C_6_deployed": { + "evm": { + "bytecode": { + "ethdebug": "" + } + }, + "ir": "/// ethdebug: enabled +/// @use-src 0:\"input.sol\" +object \"C_6_deployed\" { + code { + /// @src 0:60:101 + mstore(64, 128) + fun_f_5() + /// @src 0:77:99 + function fun_f_5() + { sstore(0, 42) } + } +} +", + "irOptimized": "/// ethdebug: enabled +/// @use-src 0:\"input.sol\" +object \"C_6_deployed\" { + code { + { + /// @src 0:60:101 + mstore(64, 128) + fun_f() + } + /// @src 0:77:99 + function fun_f() + { sstore(0, 42) } + } +} +" + } + } + }, + "ethdebug": "" +} diff --git a/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/strip-ethdebug b/test/cmdlineTests/standard_yul_debug_info_ethdebug_compatible_output_eof/strip-ethdebug new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/cmdlineTests/standard_yul_ethdebug_eof/args b/test/cmdlineTests/standard_yul_ethdebug_eof/args new file mode 100644 index 000000000000..18532c5a6d3f --- /dev/null +++ b/test/cmdlineTests/standard_yul_ethdebug_eof/args @@ -0,0 +1 @@ +--allow-paths . diff --git a/test/cmdlineTests/standard_yul_ethdebug_eof/in.yul b/test/cmdlineTests/standard_yul_ethdebug_eof/in.yul new file mode 100644 index 000000000000..920aef8e9dc2 --- /dev/null +++ b/test/cmdlineTests/standard_yul_ethdebug_eof/in.yul @@ -0,0 +1,17 @@ +/// @use-src 0:"input.sol" +object "C_6_deployed" { + code { + /// @src 0:60:101 "contract C {..." + mstore(64, 128) + + // f() + fun_f_5() + + /// @src 0:77:99 "function f() public {}" + function fun_f_5() { + sstore(0, 42) + } + /// @src 0:60:101 "contract C {..." + } +} + diff --git a/test/cmdlineTests/standard_yul_ethdebug_eof/input.json b/test/cmdlineTests/standard_yul_ethdebug_eof/input.json new file mode 100644 index 000000000000..4e33c42670a6 --- /dev/null +++ b/test/cmdlineTests/standard_yul_ethdebug_eof/input.json @@ -0,0 +1,13 @@ +{ + "language": "Yul", + "sources": { + "C": {"urls": ["in.yul"]} + }, + "settings": { + "eofVersion": 1, + "evmVersion": "osaka", + "outputSelection": { + "*": {"*": ["evm.bytecode.ethdebug", "evm.deployedBytecode.ethdebug", "ir", "irOptimized"]} + } + } +} diff --git a/test/cmdlineTests/standard_yul_ethdebug_eof/output.json b/test/cmdlineTests/standard_yul_ethdebug_eof/output.json new file mode 100644 index 000000000000..2b2f395fadd4 --- /dev/null +++ b/test/cmdlineTests/standard_yul_ethdebug_eof/output.json @@ -0,0 +1,42 @@ +{ + "contracts": { + "C": { + "C_6_deployed": { + "evm": { + "bytecode": { + "ethdebug": "" + } + }, + "ir": "/// ethdebug: enabled +/// @use-src 0:\"input.sol\" +object \"C_6_deployed\" { + code { + /// @src 0:60:101 + mstore(64, 128) + fun_f_5() + /// @src 0:77:99 + function fun_f_5() + { sstore(0, 42) } + } +} +", + "irOptimized": "/// ethdebug: enabled +/// @use-src 0:\"input.sol\" +object \"C_6_deployed\" { + code { + { + /// @src 0:60:101 + mstore(64, 128) + fun_f() + } + /// @src 0:77:99 + function fun_f() + { sstore(0, 42) } + } +} +" + } + } + }, + "ethdebug": "" +} diff --git a/test/cmdlineTests/standard_yul_ethdebug_eof/strip-ethdebug b/test/cmdlineTests/standard_yul_ethdebug_eof/strip-ethdebug new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index 6a41f7a7394c..32855cfbc211 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -434,7 +434,7 @@ BOOST_AUTO_TEST_CASE(ethdebug_program_last_instruction_with_immediate_arguments) assembly.append(AssemblyItem{0x11223344}); LinkerObject output = assembly.assemble(); - Json const program = ethdebug::program("", 0, &assembly, output); + Json const program = ethdebug::program("", 0, assembly, output); BOOST_REQUIRE(program["instructions"].size() == 1); BOOST_REQUIRE(program["instructions"][0]["operation"]["mnemonic"] == "PUSH4"); BOOST_REQUIRE(program["instructions"][0]["operation"]["arguments"][0] == "0x11223344"); @@ -445,7 +445,7 @@ BOOST_AUTO_TEST_CASE(ethdebug_program_last_instruction_with_immediate_arguments) assembly.append(AssemblyItem{0x1122334455}); LinkerObject output = assembly.assemble(); - Json const program = ethdebug::program("", 0, &assembly, output); + Json const program = ethdebug::program("", 0, assembly, output); BOOST_REQUIRE(program["instructions"].size() == 2); BOOST_REQUIRE(program["instructions"][0]["operation"]["mnemonic"] == "PUSH0"); BOOST_REQUIRE(!program["instructions"][0]["operation"].contains("arguments")); diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 5360a2778677..539d9195155b 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -2231,9 +2231,12 @@ BOOST_DATA_TEST_CASE(ethdebug_output_instructions_smoketest, boost::unit_test::d BOOST_REQUIRE(instruction.contains("offset")); BOOST_REQUIRE(instruction.contains("operation")); BOOST_REQUIRE(instruction["operation"].contains("mnemonic")); - BOOST_REQUIRE(instruction["context"]["code"]["range"].contains("length")); - BOOST_REQUIRE(instruction["context"]["code"]["range"].contains("offset")); - BOOST_REQUIRE(instruction["context"]["code"]["source"].contains("id")); + if (instruction.contains("context")) + { + BOOST_REQUIRE(instruction["context"]["code"]["range"].contains("length")); + BOOST_REQUIRE(instruction["context"]["code"]["range"].contains("offset")); + BOOST_REQUIRE(instruction["context"]["code"]["source"].contains("id")); + } std::string mnemonic = instruction["operation"]["mnemonic"]; if (mnemonic.find("PUSH") != std::string::npos) {