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 Aug 12, 2024
1 parent 55a5fd9 commit c4cb445
Show file tree
Hide file tree
Showing 91 changed files with 1,184 additions and 21 deletions.
2 changes: 1 addition & 1 deletion libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,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
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 @@ -130,7 +130,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 @@ -167,6 +167,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
8 changes: 8 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,14 @@ Json CompilerStack::interfaceSymbols(std::string const& _contractName) const
return interfaceSymbols;
}

Json CompilerStack::ethdebug(std::string const& _contractName) const
{
(void)_contractName;

Json ethdebug = {{"not yet implemented", true}};
return ethdebug;
}

bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ 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) const;

/// @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
54 changes: 50 additions & 4 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ 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"};
static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "ethdebug"};
for (auto const& selectedArtifactJson: _outputSelection)
{
std::string const& selectedArtifact = selectedArtifactJson.get<std::string>();
Expand Down Expand Up @@ -265,7 +265,7 @@ bool isBinaryRequested(Json const& _outputSelection)
static std::vector<std::string> const outputsThatRequireBinaries = std::vector<std::string>{
"*",
"ir", "irAst", "irOptimized", "irOptimizedAst",
"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 @@ -295,6 +295,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 == "ethdebug")
return true;

return false;
}

/// @returns The IR output selection for CompilerStack, based on outputs requested in the JSON.
/// Note that as an exception, '*' does not yet match "ir", "irAst", "irOptimized" or "irOptimizedAst".
CompilerStack::IROutputSelection irOutputSelection(Json const& _outputSelection)
Expand Down Expand Up @@ -1172,6 +1187,30 @@ std::variant<StandardCompiler::InputsAndSettings, Json> StandardCompiler::parseI
ret.modelCheckerSettings.timeout = modelCheckerSettings["timeout"].get<Json::number_unsigned_t>();
}

if (isEthdebugRequested(ret.outputSelection))
{
if (ret.language == "Solidity" && !ret.viaIR)
return formatError(Error::Type::FatalError, "general", "''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)
return formatError(Error::Type::FatalError, "general", "'ethdebug' need to be enabled in 'settings.debug.debugInfo', if 'ethdebug' was selected as output.");
}
}

if (
ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug &&
irOutputSelection(ret.outputSelection) == CompilerStack::IROutputSelection::None &&
!isEthdebugRequested(ret.outputSelection)
)
return formatFatalError(Error::Type::FatalError, "'settings.debug.debugInfo' can only include 'ethdebug', if output 'ir', 'irOptimized' and/or 'ethdebug' was selected.");

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

Expand Down Expand Up @@ -1429,8 +1468,8 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu

Json output;

if (errors.size() > 0)
output["errors"] = std::move(errors);
if (!errors.empty())
output["errors"] = std::move(errors);

if (!compilerStack.unhandledSMTLib2Queries().empty())
for (std::string const& query: compilerStack.unhandledSMTLib2Queries())
Expand Down Expand Up @@ -1475,6 +1514,10 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental))
contractData["devdoc"] = compilerStack.natspecDev(contractName);

// ethdebug
if (compilationSuccess && _inputsAndSettings.viaIR && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ethdebug", wildcardMatchesExperimental))
contractData["ethdebug"] = compilerStack.ethdebug(contractName);

// IR
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental))
contractData["ir"] = compilerStack.yulIR(contractName);
Expand Down Expand Up @@ -1698,6 +1741,9 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly->assemblyString(stack.debugInfoSelection());

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

return output;
}

Expand Down
5 changes: 4 additions & 1 deletion libyul/YulStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
{{m_charStream->name(), 0}}
)
);
creationObject.ethdebug["not yet implemented"] = true;

if (deployedAssembly)
{
Expand All @@ -287,6 +288,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
{{m_charStream->name(), 0}}
)
);
deployedObject.ethdebug["not yet implemented"] = true;
}
}
catch (UnimplementedFeatureError const& _error)
Expand Down Expand Up @@ -360,7 +362,8 @@ std::string YulStack::print(
yulAssert(m_stackState >= Parsed);
yulAssert(m_parserResult, "");
yulAssert(m_parserResult->hasCode(), "");
return m_parserResult->toString(
return (m_debugInfoSelection.ethdebug ? "/// ethdebug: enabled\n" : "") +
m_parserResult->toString(
languageToDialect(m_language, m_evmVersion),
AsmPrinter::TypePrinting::OmitDefault,
m_debugInfoSelection,
Expand Down
1 change: 1 addition & 0 deletions libyul/YulStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct MachineAssemblyObject
std::shared_ptr<evmasm::LinkerObject> bytecode;
std::shared_ptr<evmasm::Assembly> assembly;
std::unique_ptr<std::string> sourceMappings;
Json ethdebug = Json::object();
};

/*
Expand Down
26 changes: 25 additions & 1 deletion solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,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.ethdebug;
}

static bool coloredOutput(CommandLineOptions const& _options)
Expand Down Expand Up @@ -520,6 +521,20 @@ void CommandLineInterface::handleGasEstimation(std::string const& _contract)
}
}

void CommandLineInterface::handleEthdebug(std::string const& _contract)
{
solAssert(CompilerInputModes.count(m_options.input.mode) == 1);

if (!m_options.compiler.outputs.ethdebug)
return;

std::string data = jsonPrint(removeNullMembers(m_compiler->ethdebug(_contract)), m_options.formatting.json);
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + "_ethdebug.json", data);
else
sout() << "Debug Data (ethdebug):" << std::endl << data << std::endl;
}

void CommandLineInterface::readInputFiles()
{
solAssert(!m_standardJsonInput.has_value());
Expand Down Expand Up @@ -1300,6 +1315,14 @@ void CommandLineInterface::assembleYul(yul::YulStack::Language _language, yul::Y
m_options.formatting.json
) << std::endl;
}
if (m_options.compiler.outputs.ethdebug)
{
sout() << std::endl << "Debug Data (ethdebug):" << std::endl;
sout() << util::jsonPrint(
object.ethdebug,
m_options.formatting.json
) << std::endl;
}
}
}

Expand Down Expand Up @@ -1342,6 +1365,7 @@ void CommandLineInterface::outputCompilationResults()
handleTransientStorageLayout(contract);
handleNatspec(true, contract);
handleNatspec(false, contract);
handleEthdebug(contract);
} // end of contracts iteration
}

Expand Down
1 change: 1 addition & 0 deletions solc/CommandLineInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class CommandLineInterface
void handleGasEstimation(std::string const& _contract);
void handleStorageLayout(std::string const& _contract);
void handleTransientStorageLayout(std::string const& _contract);
void handleEthdebug(std::string const& _contract);

/// Tries to read @ m_sourceCodes as a JSONs holding ASTs
/// such that they can be imported into the compiler (importASTs())
Expand Down
51 changes: 50 additions & 1 deletion solc/CommandLineParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ void CommandLineParser::parseOutputSelection()
CompilerOutputs::componentName(&CompilerOutputs::irOptimized),
CompilerOutputs::componentName(&CompilerOutputs::astCompactJson),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
CompilerOutputs::componentName(&CompilerOutputs::ethdebug),
};
static std::set<std::string> const evmAssemblyJsonImportModeOutputs = {
CompilerOutputs::componentName(&CompilerOutputs::asm_),
Expand Down Expand Up @@ -641,7 +642,13 @@ General Information)").c_str(),
po::value<std::string>()->default_value(util::toString(DebugInfoSelection::Default())),
("Debug info components to be included in the produced EVM assembly and Yul code. "
"Value can be all, none or a comma-separated list containing one or more of the "
"following components: " + util::joinHumanReadable(DebugInfoSelection::componentMap() | ranges::views::keys) + ".").c_str()
"following components: " +
util::joinHumanReadable(
DebugInfoSelection::componentMap() | ranges::views::keys |
// Note: We intentionally keep ethdebug undocumented for now.
ranges::views::filter([](std::string const& key) { return key != "ethdebug"; }) |
ranges::to<std::vector>()
) + ".").c_str()
)
(
g_strStopAfter.c_str(),
Expand Down Expand Up @@ -762,6 +769,13 @@ General Information)").c_str(),
(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.")
;
if (!_forHelp) // Note: We intentionally keep this undocumented for now.
outputComponents.add_options()
(
CompilerOutputs::componentName(&CompilerOutputs::ethdebug).c_str(),
"Ethdebug output of all contracts."
)
;
desc.add(outputComponents);

po::options_description extraOutput("Extra Output");
Expand Down Expand Up @@ -1446,6 +1460,41 @@ void CommandLineParser::processArgs()
m_options.input.mode == InputMode::CompilerWithASTImport ||
m_options.input.mode == InputMode::EVMAssemblerJSON
);

if (m_options.compiler.outputs.ethdebug)
{
if (!m_options.output.viaIR)
solThrow(
CommandLineValidationError,
"--" + CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + " output can only be selected, if --via-ir was specified."
);

if (!m_options.output.debugInfoSelection.has_value())
{
m_options.output.debugInfoSelection = DebugInfoSelection::Default();
m_options.output.debugInfoSelection->enable("ethdebug");
}
else
{
if (!m_options.output.debugInfoSelection->ethdebug)
solThrow(
CommandLineValidationError,
"--debug-info should contain ethdebug, if it was set explicitly when compiling with --" +
CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + "."
);
}
}

if (
m_options.output.debugInfoSelection.has_value() && m_options.output.debugInfoSelection->ethdebug &&
!(m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized || m_options.compiler.outputs.ethdebug)
)
solThrow(
CommandLineValidationError,
"--debug-info ethdebug can only be used with --" + CompilerOutputs::componentName(&CompilerOutputs::ir) +
", --" + CompilerOutputs::componentName(&CompilerOutputs::irOptimized) +
" and/or --" + CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + "."
);
}

void CommandLineParser::parseCombinedJsonOption()
Expand Down
Loading

0 comments on commit c4cb445

Please sign in to comment.