Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for instructions and source ranges. #15368

Draft
wants to merge 2 commits into
base: enable_debug_info_ethdebug
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libevmasm/AbstractAssemblyStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class AbstractAssemblyStack
virtual std::string const* sourceMapping(std::string const& _contractName) const = 0;
virtual std::string const* runtimeSourceMapping(std::string const& _contractName) const = 0;

virtual Json ethdebug(std::string const& _contractName, bool _runtime) const = 0;

virtual Json ethdebug() const = 0;

virtual Json assemblyJSON(std::string const& _contractName) const = 0;
virtual std::string assemblyString(std::string const& _contractName, StringMap const& _sourceCodes) const = 0;

Expand Down
4 changes: 3 additions & 1 deletion libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ std::string Assembly::assemblyString(
{
std::ostringstream tmp;
assemblyStream(tmp, _debugInfoSelection, "", _sourceCodes);
return tmp.str();
return (_debugInfoSelection.ethdebug ? "/// ethdebug: enabled\n" : "") + tmp.str();
}

Json Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList) const
Expand Down Expand Up @@ -1130,6 +1130,8 @@ LinkerObject const& Assembly::assembleLegacy() const

for (AssemblyItem const& item: items)
{
ret.offsets.emplace_back(ret.bytecode.size());

// store position of the invalid jump destination
if (item.type() != Tag && m_tagPositionsInBytecode[0] == std::numeric_limits<size_t>::max())
m_tagPositionsInBytecode[0] = ret.bytecode.size();
Expand Down
11 changes: 11 additions & 0 deletions libevmasm/EVMAssemblyStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ std::string const* EVMAssemblyStack::runtimeSourceMapping(std::string const& _co
return &m_runtimeSourceMapping;
}

Json EVMAssemblyStack::ethdebug(std::string const& _contractName, bool _runtime) const
{
solAssert(_contractName == m_name);
return _runtime ? m_runtimeEthdebug : m_ethdebug;
}

Json EVMAssemblyStack::ethdebug() const
{
return {};
}

Json EVMAssemblyStack::assemblyJSON(std::string const& _contractName) const
{
solAssert(_contractName == m_name);
Expand Down
5 changes: 5 additions & 0 deletions libevmasm/EVMAssemblyStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class EVMAssemblyStack: public AbstractAssemblyStack
virtual std::string const* sourceMapping(std::string const& _contractName) const override;
virtual std::string const* runtimeSourceMapping(std::string const& _contractName) const override;

virtual Json ethdebug(std::string const& _contractName, bool _runtime) const override;
virtual Json ethdebug() const override;

virtual Json assemblyJSON(std::string const& _contractName) const override;
virtual std::string assemblyString(std::string const& _contractName, StringMap const& _sourceCodes) const override;

Expand Down Expand Up @@ -87,6 +90,8 @@ class EVMAssemblyStack: public AbstractAssemblyStack
langutil::DebugInfoSelection m_debugInfoSelection = langutil::DebugInfoSelection::Default();
std::string m_sourceMapping;
std::string m_runtimeSourceMapping;
Json m_ethdebug;
Json m_runtimeEthdebug;
};

} // namespace solidity::evmasm
2 changes: 2 additions & 0 deletions libevmasm/LinkerObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ struct LinkerObject
/// The bytecode.
bytes bytecode;

std::vector<size_t> offsets;

/// Map from offsets in bytecode to library identifiers. The addresses starting at those offsets
/// need to be replaced by the actual addresses by the linker.
std::map<size_t, std::string> linkReferences;
Expand Down
12 changes: 10 additions & 2 deletions liblangutil/DebugInfoSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,22 @@ DebugInfoSelection const DebugInfoSelection::Only(bool DebugInfoSelection::* _me
return result;
}

DebugInfoSelection const DebugInfoSelection::Except(std::vector<bool DebugInfoSelection::*> const& _members) noexcept
{
DebugInfoSelection result = All();
for (bool DebugInfoSelection::* member: _members)
result.*member = false;
return result;
}

std::optional<DebugInfoSelection> DebugInfoSelection::fromString(std::string_view _input)
{
// TODO: Make more stuff constexpr and make it a static_assert().
solAssert(componentMap().count("all") == 0, "");
solAssert(componentMap().count("none") == 0, "");

if (_input == "all")
return All();
return ExceptExperimental();
if (_input == "none")
return None();

Expand All @@ -74,7 +82,7 @@ std::optional<DebugInfoSelection> DebugInfoSelection::fromComponents(
for (auto const& component: _componentNames)
{
if (component == "*")
return (_acceptWildcards ? std::make_optional(DebugInfoSelection::All()) : std::nullopt);
return (_acceptWildcards ? std::make_optional(ExceptExperimental()) : std::nullopt);

if (!selection.enable(component))
return std::nullopt;
Expand Down
6 changes: 5 additions & 1 deletion liblangutil/DebugInfoSelection.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ struct DebugInfoSelection
static DebugInfoSelection const All(bool _value = true) noexcept;
static DebugInfoSelection const None() noexcept { return All(false); }
static DebugInfoSelection const Only(bool DebugInfoSelection::* _member) noexcept;
static DebugInfoSelection const Default() noexcept { return All(); }
static DebugInfoSelection const Default() noexcept { return ExceptExperimental(); }
static DebugInfoSelection const Except(std::vector<bool DebugInfoSelection::*> const& _members) noexcept;
static DebugInfoSelection const ExceptExperimental() noexcept { return Except({&DebugInfoSelection::ethdebug}); }

static std::optional<DebugInfoSelection> fromString(std::string_view _input);
static std::optional<DebugInfoSelection> fromComponents(
Expand Down Expand Up @@ -72,13 +74,15 @@ struct DebugInfoSelection
{"location", &DebugInfoSelection::location},
{"snippet", &DebugInfoSelection::snippet},
{"ast-id", &DebugInfoSelection::astID},
{"ethdebug", &DebugInfoSelection::ethdebug},
};
return components;
}

bool location = false; ///< Include source location. E.g. `@src 3:50:100`
bool snippet = false; ///< Include source code snippet next to location. E.g. `@src 3:50:100 "contract C {..."`
bool astID = false; ///< Include ID of the Solidity AST node. E.g. `@ast-id 15`
bool ethdebug = false; ///< Include ethdebug related debug information.
};

std::ostream& operator<<(std::ostream& _stream, DebugInfoSelection const& _selection);
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/codegen/ir/IRGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ std::string IRGenerator::generate(
);
};

Whiskers t(R"(
Whiskers t(R"(<?isEthdebugEnabled>/// ethdebug: enabled</isEthdebugEnabled>
/// @use-src <useSrcMapCreation>
object "<CreationObject>" {
code {
Expand Down Expand Up @@ -157,6 +157,7 @@ std::string IRGenerator::generate(
for (VariableDeclaration const* var: ContractType(_contract).immutableVariables())
m_context.registerImmutableVariable(*var);

t("isEthdebugEnabled", m_context.debugInfoSelection().ethdebug);
t("CreationObject", IRNames::creationObject(_contract));
t("sourceLocationCommentCreation", dispenseLocationComment(_contract));
t("library", _contract.isLibrary());
Expand Down
77 changes: 77 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,83 @@ Json CompilerStack::interfaceSymbols(std::string const& _contractName) const
return interfaceSymbols;
}

Json CompilerStack::ethdebug() const
{
Json result = Json::object();
result["sources"] = sourceNames();
return result;
}

Json CompilerStack::ethdebug(std::string const& _contractName, bool _runtime) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
return ethdebug(contract(_contractName), _runtime);
}

Json CompilerStack::ethdebug(Contract const& _contract, bool _runtime) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
solAssert(_contract.contract);
solUnimplementedAssert(!isExperimentalSolidity());
Json result = Json::object();
result["instructions"] = ethdebugInstructions(_contract, _runtime);
return result;
}

Json CompilerStack::ethdebugInstructions(Contract const& _contract, bool _runtime) const
{
evmasm::LinkerObject const* object;
evmasm::Assembly const* assembly;
if (_runtime)
{
object = &_contract.runtimeObject;
assembly = _contract.evmRuntimeAssembly.get();
}
else
{
object = &_contract.object;
assembly = _contract.evmAssembly.get();
}

Json instructions = Json::array();
for (size_t i = 0; i < object->offsets.size(); ++i)
{
size_t offset = object->offsets[i];
size_t nextOffset = offset;
if (i + 1 < object->offsets.size())
nextOffset = object->offsets[i + 1];
Json instruction = Json::object();
instruction["offset"] = offset;
instruction["mnemonic"] = instructionInfo(static_cast<evmasm::Instruction>(object->bytecode[offset]), m_evmVersion).name;
bytes argumentData;
for (size_t args = offset + 1; args < nextOffset; ++args)
argumentData.emplace_back(object->bytecode[args]);
if (!argumentData.empty())
{
Json arguments = Json::array();
arguments.emplace_back("0x" + util::toHex(argumentData));
instruction["arguments"] = arguments;
}

solAssert(assembly->codeSections().size() == 1);
SourceLocation const& location = assembly->codeSections().at(0).items.at(i).location();
Json instructionContext = Json::object();
Json source = Json::object();
source["id"] = location.sourceName ? static_cast<int>(sourceIndices()[*location.sourceName]) : static_cast<int>(-1);
Json range = Json::object();
range["offset"] = location.start;
range["length"] = location.end;
Json code = Json::object();
code["source"] = source;
code["range"] = range;
instructionContext["code"] = code;
instruction["context"] = instructionContext;
instructions.emplace_back(instruction);
}

return instructions;
}

bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
Expand Down
14 changes: 14 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,14 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
/// @returns a JSON object with the three members ``methods``, ``events``, ``errors``. Each is a map, mapping identifiers (hashes) to function names.
Json interfaceSymbols(std::string const& _contractName) const;

/// @returns a JSON representing the ethdebug data of the specified contract.
/// Prerequisite: Successful call to parse or compile.
Json ethdebug(std::string const& _contractName, bool _runtime) const override;

/// @returns a JSON representing the top-level ethdebug data (types, etc.).
/// Prerequisite: Successful call to parse or compile.
Json ethdebug() const override;

/// @returns the Contract Metadata matching the pipeline selected using the viaIR setting.
std::string const& metadata(std::string const& _contractName) const { return metadata(contract(_contractName)); }

Expand Down Expand Up @@ -457,6 +465,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
mutable std::optional<std::string const> runtimeSourceMapping;
};

Json ethdebugInstructions(Contract const& _contract, bool _runtime) const;

void createAndAssignCallGraphs();
void findAndReportCyclicContractDependencies();

Expand Down Expand Up @@ -571,6 +581,10 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
/// This will generate the metadata and store it in the Contract object if it is not present yet.
std::string const& metadata(Contract const& _contract) const;

/// @returns the Contract ethdebug data.
/// This will generate the JSON object and store it in the Contract object if it is not present yet.
Json ethdebug(Contract const& _contract, bool _runtime) const;

/// @returns the offset of the entry point of the given function into the list of assembly items
/// or zero if it is not found or does not exist.
size_t functionEntryPoint(
Expand Down
Loading