Skip to content

Commit a9df00c

Browse files
authored
Merge pull request #15994 from ethereum/eof_source_locations_unoptimized
Support ethdebug source locations under EOF
2 parents f1af146 + dd25b24 commit a9df00c

File tree

32 files changed

+979
-72
lines changed

32 files changed

+979
-72
lines changed

Changelog.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
Language Features:
44

5-
65
Compiler Features:
7-
6+
* ethdebug: Experimental support for instructions and source locations under EOF.
87

98
Bugfixes:
109

11-
1210
### 0.8.30 (2025-05-07)
1311

1412
Compiler Features:

libevmasm/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ set(sources
66
AssemblyItem.h
77
Ethdebug.cpp
88
Ethdebug.h
9+
EthdebugSchema.cpp
10+
EthdebugSchema.h
911
EVMAssemblyStack.cpp
1012
EVMAssemblyStack.h
1113
BlockDeduplicator.cpp

libevmasm/Ethdebug.cpp

Lines changed: 112 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,79 +18,135 @@
1818

1919
#include <libevmasm/Ethdebug.h>
2020

21+
#include <libevmasm/EthdebugSchema.h>
22+
23+
#include <range/v3/algorithm/any_of.hpp>
24+
2125
using namespace solidity;
2226
using namespace solidity::evmasm;
2327
using namespace solidity::evmasm::ethdebug;
2428

2529
namespace
2630
{
2731

28-
Json programInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned _sourceId)
32+
schema::program::Instruction::Operation instructionOperation(Assembly const& _assembly, LinkerObject const& _linkerObject, size_t const _start, size_t const _end)
2933
{
30-
solUnimplementedAssert(_assembly.eofVersion() == std::nullopt, "ethdebug does not yet support EOF.");
31-
solUnimplementedAssert(_assembly.codeSections().size() == 1, "ethdebug does not yet support multiple code-sections.");
32-
for (auto const& instruction: _assembly.codeSections()[0].items)
33-
solUnimplementedAssert(instruction.type() != VerbatimBytecode, "Verbatim bytecode is currently not supported by ethdebug.");
34-
35-
solAssert(_linkerObject.codeSectionLocations.size() == 1);
36-
solAssert(_linkerObject.codeSectionLocations[0].end <= _linkerObject.bytecode.size());
37-
Json instructions = Json::array();
38-
for (size_t i = 0; i < _linkerObject.codeSectionLocations[0].instructionLocations.size(); ++i)
34+
solAssert(_end <= _linkerObject.bytecode.size());
35+
solAssert(_start < _end);
36+
schema::program::Instruction::Operation operation;
37+
operation.mnemonic = instructionInfo(static_cast<Instruction>(_linkerObject.bytecode[_start]), _assembly.evmVersion()).name;
38+
static size_t constexpr instructionSize = 1;
39+
if (_start + instructionSize < _end)
3940
{
40-
LinkerObject::InstructionLocation currentInstruction = _linkerObject.codeSectionLocations[0].instructionLocations[i];
41-
size_t start = currentInstruction.start;
42-
size_t end = currentInstruction.end;
43-
size_t assemblyItemIndex = currentInstruction.assemblyItemIndex;
44-
solAssert(end <= _linkerObject.bytecode.size());
45-
solAssert(start < end);
46-
solAssert(assemblyItemIndex < _assembly.codeSections().at(0).items.size());
47-
Json operation = Json::object();
48-
operation["mnemonic"] = instructionInfo(static_cast<Instruction>(_linkerObject.bytecode[start]), _assembly.evmVersion()).name;
49-
static size_t constexpr instructionSize = 1;
50-
if (start + instructionSize < end)
51-
{
52-
bytes const argumentData(
53-
_linkerObject.bytecode.begin() + static_cast<std::ptrdiff_t>(start) + instructionSize,
54-
_linkerObject.bytecode.begin() + static_cast<std::ptrdiff_t>(end)
55-
);
56-
solAssert(!argumentData.empty());
57-
operation["arguments"] = Json::array({util::toHex(argumentData, util::HexPrefix::Add)});
58-
}
59-
langutil::SourceLocation const& location = _assembly.codeSections().at(0).items.at(assemblyItemIndex).location();
60-
Json instruction = Json::object();
61-
instruction["offset"] = start;
62-
instruction["operation"] = operation;
63-
64-
instruction["context"] = Json::object();
65-
instruction["context"]["code"] = Json::object();
66-
instruction["context"]["code"]["source"] = Json::object();
67-
instruction["context"]["code"]["source"]["id"] = static_cast<int>(_sourceId);
68-
69-
instruction["context"]["code"]["range"] = Json::object();
70-
instruction["context"]["code"]["range"]["offset"] = location.start;
71-
instruction["context"]["code"]["range"]["length"] = location.end - location.start;
72-
instructions.emplace_back(instruction);
41+
bytes const argumentData(
42+
_linkerObject.bytecode.begin() + static_cast<std::ptrdiff_t>(_start) + instructionSize,
43+
_linkerObject.bytecode.begin() + static_cast<std::ptrdiff_t>(_end)
44+
);
45+
solAssert(!argumentData.empty());
46+
operation.arguments = {{schema::data::HexValue{argumentData}}};
7347
}
48+
return operation;
49+
}
7450

75-
return instructions;
51+
schema::materials::SourceRange::Range locationRange(langutil::SourceLocation const& _location)
52+
{
53+
return {
54+
.length = schema::data::Unsigned{_location.end - _location.start},
55+
.offset = schema::data::Unsigned{_location.start}
56+
};
7657
}
7758

78-
} // anonymous namespace
59+
schema::materials::Reference sourceReference(unsigned _sourceID)
60+
{
61+
return {
62+
.id = schema::materials::ID{_sourceID},
63+
.type = std::nullopt
64+
};
65+
}
7966

80-
Json ethdebug::program(std::string_view _name, unsigned _sourceId, Assembly const* _assembly, LinkerObject const& _linkerObject)
67+
std::optional<schema::program::Context> instructionContext(Assembly::CodeSection const& _codeSection, size_t _assemblyItemIndex, unsigned _sourceID)
8168
{
82-
Json result = Json::object();
83-
result["contract"] = Json::object();
84-
result["contract"]["name"] = _name;
85-
result["contract"]["definition"] = Json::object();
86-
result["contract"]["definition"]["source"] = Json::object();
87-
result["contract"]["definition"]["source"]["id"] = _sourceId;
88-
if (_assembly)
69+
solAssert(_assemblyItemIndex < _codeSection.items.size());
70+
langutil::SourceLocation const& location = _codeSection.items.at(_assemblyItemIndex).location();
71+
if (!location.isValid())
72+
return std::nullopt;
73+
74+
return schema::program::Context{
75+
schema::materials::SourceRange{
76+
.source = sourceReference(_sourceID),
77+
.range = locationRange(location)
78+
},
79+
std::nullopt,
80+
std::nullopt
81+
};
82+
}
83+
84+
std::vector<schema::program::Instruction> codeSectionInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned const _sourceID, size_t const _codeSectionIndex)
85+
{
86+
solAssert(_codeSectionIndex < _linkerObject.codeSectionLocations.size());
87+
solAssert(_codeSectionIndex < _assembly.codeSections().size());
88+
auto const& locations = _linkerObject.codeSectionLocations[_codeSectionIndex];
89+
auto const& codeSection = _assembly.codeSections().at(_codeSectionIndex);
90+
91+
std::vector<schema::program::Instruction> instructions;
92+
instructions.reserve(codeSection.items.size());
93+
94+
bool const codeSectionContainsVerbatim = ranges::any_of(
95+
codeSection.items,
96+
[](auto const& _instruction) { return _instruction.type() == VerbatimBytecode; }
97+
);
98+
solUnimplementedAssert(!codeSectionContainsVerbatim, "Verbatim bytecode is currently not supported by ethdebug.");
99+
100+
for (auto const& currentInstruction: locations.instructionLocations)
89101
{
90-
result["environment"] = _assembly->isCreation() ? "create" : "call";
91-
result["instructions"] = programInstructions(*_assembly, _linkerObject, _sourceId);
102+
size_t const start = currentInstruction.start;
103+
size_t const end = currentInstruction.end;
104+
105+
// some instructions do not contribute to the bytecode
106+
if (start == end)
107+
continue;
108+
109+
instructions.emplace_back(schema::program::Instruction{
110+
.offset = schema::data::Unsigned{start},
111+
.operation = instructionOperation(_assembly, _linkerObject, start, end),
112+
.context = instructionContext(codeSection, currentInstruction.assemblyItemIndex, _sourceID)
113+
});
92114
}
93-
return result;
115+
116+
return instructions;
117+
}
118+
119+
std::vector<schema::program::Instruction> programInstructions(Assembly const& _assembly, LinkerObject const& _linkerObject, unsigned const _sourceID)
120+
{
121+
auto const numCodeSections = _assembly.codeSections().size();
122+
solAssert(numCodeSections == _linkerObject.codeSectionLocations.size());
123+
124+
std::vector<schema::program::Instruction> instructionInfo;
125+
for (size_t codeSectionIndex = 0; codeSectionIndex < numCodeSections; ++codeSectionIndex)
126+
instructionInfo += codeSectionInstructions(_assembly, _linkerObject, _sourceID, codeSectionIndex);
127+
return instructionInfo;
128+
}
129+
130+
} // anonymous namespace
131+
132+
Json ethdebug::program(std::string_view _name, unsigned _sourceID, Assembly const& _assembly, LinkerObject const& _linkerObject)
133+
{
134+
return schema::Program{
135+
.compilation = std::nullopt,
136+
.contract = {
137+
.name = std::string{_name},
138+
.definition = {
139+
.source = {
140+
.id = {_sourceID},
141+
.type = std::nullopt
142+
},
143+
.range = std::nullopt
144+
}
145+
},
146+
.environment = _assembly.isCreation() ? schema::Program::Environment::CREATE : schema::Program::Environment::CALL,
147+
.context = std::nullopt,
148+
.instructions = programInstructions(_assembly, _linkerObject, _sourceID)
149+
};
94150
}
95151

96152
Json ethdebug::resources(std::vector<std::string> const& _sources, std::string const& _version)

libevmasm/Ethdebug.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace solidity::evmasm::ethdebug
2727
{
2828

2929
// returns ethdebug/format/program.
30-
Json program(std::string_view _name, unsigned _sourceId, Assembly const* _assembly, LinkerObject const& _linkerObject);
30+
Json program(std::string_view _name, unsigned _sourceID, Assembly const& _assembly, LinkerObject const& _linkerObject);
3131

3232
// returns ethdebug/format/info/resources
3333
Json resources(std::vector<std::string> const& _sources, std::string const& _version);

libevmasm/EthdebugSchema.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libevmasm/EthdebugSchema.h>
20+
21+
#include <libsolutil/Numeric.h>
22+
#include <libsolutil/Visitor.h>
23+
24+
using namespace solidity;
25+
using namespace solidity::evmasm::ethdebug;
26+
27+
void schema::data::to_json(Json& _json, HexValue const& _hexValue)
28+
{
29+
_json = util::toHex(_hexValue.value, util::HexPrefix::Add);
30+
}
31+
32+
void schema::data::to_json(Json& _json, Unsigned const& _unsigned)
33+
{
34+
std::visit(util::GenericVisitor{
35+
[&](HexValue const& _hexValue) { _json = _hexValue; },
36+
[&](std::uint64_t const _value) { _json = _value; }
37+
}, _unsigned.value);
38+
}
39+
40+
void schema::materials::to_json(Json& _json, ID const& _id)
41+
{
42+
std::visit(util::GenericVisitor{
43+
[&](std::string const& _hexValue) { _json = _hexValue; },
44+
[&](std::uint64_t const _value) { _json = _value; }
45+
}, _id.value);
46+
}
47+
48+
void schema::materials::to_json(Json& _json, Reference const& _source)
49+
{
50+
_json["id"] = _source.id;
51+
if (_source.type)
52+
_json["type"] = *_source.type == Reference::Type::Compilation ? "compilation" : "source";
53+
}
54+
55+
void schema::materials::to_json(Json& _json, SourceRange::Range const& _range)
56+
{
57+
_json["length"] = _range.length;
58+
_json["offset"] = _range.offset;
59+
}
60+
61+
62+
void schema::materials::to_json(Json& _json, SourceRange const& _sourceRange)
63+
{
64+
_json["source"] = _sourceRange.source;
65+
if (_sourceRange.range)
66+
_json["range"] = *_sourceRange.range;
67+
}
68+
69+
void schema::to_json(Json& _json, Program::Contract const& _contract)
70+
{
71+
if (_contract.name)
72+
_json["name"] = *_contract.name;
73+
_json["definition"] = _contract.definition;
74+
}
75+
76+
void schema::program::to_json(Json& _json, Context::Variable const& _contextVariable)
77+
{
78+
auto const numProperties =
79+
_contextVariable.identifier.has_value() +
80+
_contextVariable.declaration.has_value();
81+
solRequire(numProperties >= 1, EthdebugException, "Context variable has no properties.");
82+
if (_contextVariable.identifier)
83+
{
84+
solRequire(!_contextVariable.identifier->empty(), EthdebugException, "Variable identifier must not be empty.");
85+
_json["identifier"] = *_contextVariable.identifier;
86+
}
87+
if (_contextVariable.declaration)
88+
_json["declaration"] = *_contextVariable.declaration;
89+
}
90+
91+
void schema::program::to_json(Json& _json, Context const& _context)
92+
{
93+
solRequire(_context.code.has_value() + _context.remark.has_value() + _context.variables.has_value() >= 1, EthdebugException, "Context needs >=1 properties.");
94+
if (_context.code)
95+
_json["code"] = *_context.code;
96+
if (_context.variables)
97+
{
98+
solRequire(!_context.variables->empty(), EthdebugException, "Context variables must not be empty if provided.");
99+
_json["variables"] = *_context.variables;
100+
}
101+
if (_context.remark)
102+
_json["remark"] = *_context.remark;
103+
}
104+
105+
void schema::program::to_json(Json& _json, Instruction::Operation const& _operation)
106+
{
107+
_json = { {"mnemonic", _operation.mnemonic} };
108+
if (!_operation.arguments.empty())
109+
_json["arguments"] = _operation.arguments;
110+
}
111+
112+
void schema::program::to_json(Json& _json, Instruction const& _instruction)
113+
{
114+
_json["offset"] = _instruction.offset;
115+
if (_instruction.operation)
116+
_json["operation"] = *_instruction.operation;
117+
if (_instruction.context)
118+
_json["context"] = *_instruction.context;
119+
}
120+
121+
void schema::to_json(Json& _json, Program const& _program)
122+
{
123+
if (_program.compilation)
124+
_json["compilation"] = *_program.compilation;
125+
_json["contract"] = _program.contract;
126+
_json["environment"] = _program.environment;
127+
if (_program.context)
128+
_json["context"] = *_program.context;
129+
_json["instructions"] = _program.instructions;
130+
}
131+
132+
void schema::to_json(Json& _json, Program::Environment const& _environment)
133+
{
134+
switch (_environment)
135+
{
136+
case Program::Environment::CALL:
137+
_json = "call";
138+
break;
139+
case Program::Environment::CREATE:
140+
_json = "create";
141+
break;
142+
}
143+
}

0 commit comments

Comments
 (0)