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 Sep 9, 2024
1 parent 09e9aa6 commit 55503ce
Show file tree
Hide file tree
Showing 23 changed files with 547 additions and 23 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
18 changes: 18 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,24 @@ Json CompilerStack::interfaceSymbols(std::string const& _contractName) const
return interfaceSymbols;
}

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

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

bytes CompilerStack::cborMetadata(std::string const& _contractName, bool _forIR) const
{
solAssert(m_stackState >= AnalysisSuccessful, "Analysis was not successful.");
Expand Down
9 changes: 9 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,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 Expand Up @@ -422,6 +426,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
util::LazyInit<Json const> devDocumentation;
util::LazyInit<Json const> generatedSources;
util::LazyInit<Json const> runtimeGeneratedSources;
util::LazyInit<Json const> ethdebug;
mutable std::optional<std::string const> sourceMapping;
mutable std::optional<std::string const> runtimeSourceMapping;
};
Expand Down Expand Up @@ -530,6 +535,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) 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
60 changes: 56 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", "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>();
Expand Down Expand Up @@ -268,7 +268,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 @@ -298,6 +298,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 @@ -1175,6 +1190,36 @@ 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, "'ethdebug' is only supported for languages 'Solidity' and 'Yul'.");
}

if (isEthdebugRequested(ret.outputSelection))
{
if (ret.language == "Solidity" && !ret.viaIR)
return formatFatalError(Error::Type::FatalError, "'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 'ethdebug' was selected as output.");
}
}

if (
ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug && ret.language == "Solidity" &&
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 @@ -1433,8 +1478,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 @@ -1479,6 +1524,10 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental))
contractData["devdoc"] = compilerStack.natspecDev(contractName);

// ethdebug
if (compilationSuccess && 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 @@ -1704,6 +1753,9 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "yulCFGJson", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["yulCFGJson"] = stack.cfgJson();

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

return output;
}

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

if (deployedAssembly)
{
Expand All @@ -279,6 +280,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
{{m_charStream->name(), 0}}
)
);
deployedObject.ethdebug["not yet implemented @ MachineAssemblyObject::ethdebug"] = true;
}
}
catch (UnimplementedFeatureError const& _error)
Expand Down Expand Up @@ -352,10 +354,11 @@ std::string YulStack::print(
yulAssert(m_stackState >= Parsed);
yulAssert(m_parserResult, "");
yulAssert(m_parserResult->hasCode(), "");
return m_parserResult->toString(
m_debugInfoSelection,
_soliditySourceProvider
) + "\n";
return (m_debugInfoSelection.ethdebug ? "/// ethdebug: enabled\n" : "") +
m_parserResult->toString(
m_debugInfoSelection,
_soliditySourceProvider
) + "\n";
}

Json YulStack::astJson() const
Expand Down
1 change: 1 addition & 0 deletions libyul/YulStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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
24 changes: 24 additions & 0 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ static bool needsHumanTargetedStdout(CommandLineOptions const& _options)
_options.compiler.outputs.asmJson ||
_options.compiler.outputs.binary ||
_options.compiler.outputs.binaryRuntime ||
_options.compiler.outputs.ethdebug ||
_options.compiler.outputs.metadata ||
_options.compiler.outputs.natspecUser ||
_options.compiler.outputs.natspecDev ||
Expand Down Expand Up @@ -544,6 +545,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 @@ -1329,6 +1344,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 @@ -1372,6 +1395,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 @@ -117,6 +117,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
Loading

0 comments on commit 55503ce

Please sign in to comment.