Skip to content

Commit

Permalink
Enable ethdebug debug info and output selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
aarlt committed Oct 2, 2024
1 parent 4ab3e36 commit 13c0683
Show file tree
Hide file tree
Showing 27 changed files with 1,159 additions and 21 deletions.
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
2 changes: 1 addition & 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
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
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
27 changes: 27 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,33 @@ 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());
if (_runtime)
{
Json result = Json::object();
return result;
}
Json result = Json::object();
return result;
}

bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
Expand Down
12 changes: 12 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 @@ -571,6 +579,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
83 changes: 80 additions & 3 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,28 @@ bool hashMatchesContent(std::string const& _hash, std::string const& _content)

bool isArtifactRequested(Json const& _outputSelection, std::string const& _artifact, bool _wildcardMatchesExperimental)
{
static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson"};
static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson", "ethdebug"};
for (auto const& selectedArtifactJson: _outputSelection)
{
std::string const& selectedArtifact = selectedArtifactJson.get<std::string>();
if (
_artifact == selectedArtifact ||
boost::algorithm::starts_with(_artifact, selectedArtifact + ".")
)
{
if (_artifact.find("ethdebug") != std::string::npos)
// only accept exact matches for ethdebug, e.g. evm.bytecode.ethdebug
return selectedArtifact == _artifact;
return true;
}
else if (selectedArtifact == "*")
{
// TODO: yulCFGJson is only experimental now, so it should not be matched by "*".
if (_artifact == "yulCFGJson")
return false;
// "ir", "irOptimized" can only be matched by "*" if activated.
if (_artifact.find("ethdebug") != std::string::npos)
return false;
if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental)
return true;
}
Expand Down Expand Up @@ -237,7 +244,7 @@ bool isArtifactRequested(Json const& _outputSelection, std::string const& _file,
std::vector<std::string> evmObjectComponents(std::string const& _objectKind)
{
solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", "");
std::vector<std::string> components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences"};
std::vector<std::string> components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences", ".ethdebug"};
if (_objectKind == "deployedBytecode")
components.push_back(".immutableReferences");
return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; });
Expand All @@ -253,7 +260,7 @@ bool isBinaryRequested(Json const& _outputSelection)
static std::vector<std::string> const outputsThatRequireBinaries = std::vector<std::string>{
"*",
"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson",
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly", "ethdebug"
} + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode");

for (auto const& fileRequests: _outputSelection)
Expand Down Expand Up @@ -283,6 +290,21 @@ bool isEvmBytecodeRequested(Json const& _outputSelection)
return false;
}

/// @returns true if ethdebug was requested.
bool isEthdebugRequested(Json const& _outputSelection)
{
if (!_outputSelection.is_object())
return false;

for (auto const& fileRequests: _outputSelection)
for (auto const& requests: fileRequests)
for (auto const& request: requests)
if (request == "evm.bytecode.ethdebug" || request == "evm.deployedBytecode.ethdebug")
return true;

return false;
}

/// @returns The set of selected contracts, along with their compiler pipeline configuration, based
/// on outputs requested in the JSON. Translates wildcards to the ones understood by CompilerStack.
/// Note that as an exception, '*' does not yet match "ir", "irAst", "irOptimized" or "irOptimizedAst".
Expand Down Expand Up @@ -378,6 +400,7 @@ Json collectEVMObject(
langutil::EVMVersion _evmVersion,
evmasm::LinkerObject const& _object,
std::string const* _sourceMap,
Json _ethdebug,
Json _generatedSources,
bool _runtimeObject,
std::function<bool(std::string)> const& _artifactRequested
Expand All @@ -398,6 +421,9 @@ Json collectEVMObject(
output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences);
if (_artifactRequested("generatedSources"))
output["generatedSources"] = std::move(_generatedSources);
if (_artifactRequested("ethdebug"))
output["ethdebug"] = std::move(_ethdebug);

return output;
}

Expand Down Expand Up @@ -1179,6 +1205,35 @@ std::variant<StandardCompiler::InputsAndSettings, Json> StandardCompiler::parseI
ret.modelCheckerSettings.timeout = modelCheckerSettings["timeout"].get<Json::number_unsigned_t>();
}

if ((ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug) || isEthdebugRequested(ret.outputSelection))
{
if (ret.language != "Solidity" && ret.language != "Yul")
return formatFatalError(Error::Type::FatalError, "'settings.debug.debugInfo' 'ethdebug' is only supported for languages 'Solidity' and 'Yul'.");
}

if (isEthdebugRequested(ret.outputSelection))
{
if (ret.language == "Solidity" && !ret.viaIR)
return formatFatalError(Error::Type::FatalError, "'evm.bytecode.ethdebug' or 'evm.deployedBytecode.ethdebug' can only be selected as output, if 'viaIR' was set.");

if (!ret.debugInfoSelection.has_value())
{
ret.debugInfoSelection = DebugInfoSelection::Default();
ret.debugInfoSelection->enable("ethdebug");
}
else
{
if (!ret.debugInfoSelection->ethdebug && ret.language == "Solidity")
return formatFatalError(Error::Type::FatalError, "'ethdebug' needs to be enabled in 'settings.debug.debugInfo', if 'evm.bytecode.ethdebug' or 'evm.deployedBytecode.ethdebug' was selected as output.");
}
}

if (
ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug && ret.language == "Solidity" &&
!pipelineConfig(ret.outputSelection)[""][""].irCodegen && !isEthdebugRequested(ret.outputSelection)
)
return formatFatalError(Error::Type::FatalError, "'settings.debug.debugInfo' can only include 'ethdebug', if output 'ir', 'irOptimized', 'evm.bytecode.ethdebug' or 'evm.deployedBytecode.ethdebug' was selected.");

return {std::move(ret)};
}

Expand Down Expand Up @@ -1254,6 +1309,7 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in
_inputsAndSettings.evmVersion,
stack.object(sourceName),
stack.sourceMapping(sourceName),
stack.ethdebug(sourceName, false),
{},
false, // _runtimeObject
[&](std::string const& _element) {
Expand All @@ -1278,6 +1334,7 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in
_inputsAndSettings.evmVersion,
stack.runtimeObject(sourceName),
stack.runtimeSourceMapping(sourceName),
stack.ethdebug(sourceName, true),
{},
true, // _runtimeObject
[&](std::string const& _element) {
Expand All @@ -1299,6 +1356,7 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in
contractsOutput[sourceName][""] = contractData;
Json output;
output["contracts"] = contractsOutput;

return util::removeNullMembers(output);
}

Expand Down Expand Up @@ -1514,6 +1572,7 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
_inputsAndSettings.evmVersion,
compilerStack.object(contractName),
compilerStack.sourceMapping(contractName),
compilerStack.ethdebug(contractName, false),
compilerStack.generatedSources(contractName),
false,
[&](std::string const& _element) { return isArtifactRequested(
Expand All @@ -1536,6 +1595,7 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
_inputsAndSettings.evmVersion,
compilerStack.runtimeObject(contractName),
compilerStack.runtimeSourceMapping(contractName),
compilerStack.ethdebug(contractName, true),
compilerStack.generatedSources(contractName, true),
true,
[&](std::string const& _element) { return isArtifactRequested(
Expand All @@ -1560,6 +1620,9 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
if (!contractsOutput.empty())
output["contracts"] = contractsOutput;

if (isEthdebugRequested(_inputsAndSettings.outputSelection))
output["ethdebug"] = compilerStack.ethdebug();

return output;
}

Expand Down Expand Up @@ -1608,6 +1671,19 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
return output;
}

for (auto const& fileRequests: _inputsAndSettings.outputSelection)
for (auto const& requests: fileRequests)
for (auto const& request: requests)
if (request == "evm.deployedBytecode.ethdebug")
{
output["errors"].emplace_back(formatError(
Error::Type::JSONError,
"general",
"\"evm.deployedBytecode.ethdebug\" cannot be used for Yul."
));
return output;
}

YulStack stack(
_inputsAndSettings.evmVersion,
_inputsAndSettings.eofVersion,
Expand Down Expand Up @@ -1679,6 +1755,7 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
_inputsAndSettings.evmVersion,
*o.bytecode,
o.sourceMappings.get(),
o.ethdebug,
Json::array(),
isDeployed,
[&, kind = kind](std::string const& _element) { return isArtifactRequested(
Expand Down
Loading

0 comments on commit 13c0683

Please sign in to comment.