diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 7a28208c1b86..7d66e7c93b2a 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -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 const& _sourceIndices, bool _includeSourceList) const diff --git a/liblangutil/DebugInfoSelection.cpp b/liblangutil/DebugInfoSelection.cpp index ad0b615c1c1e..57e4d17650b7 100644 --- a/liblangutil/DebugInfoSelection.cpp +++ b/liblangutil/DebugInfoSelection.cpp @@ -49,6 +49,14 @@ DebugInfoSelection const DebugInfoSelection::Only(bool DebugInfoSelection::* _me return result; } +DebugInfoSelection const DebugInfoSelection::Except(std::vector const& _members) noexcept +{ + DebugInfoSelection result = All(); + for (bool DebugInfoSelection::* member: _members) + result.*member = false; + return result; +} + std::optional DebugInfoSelection::fromString(std::string_view _input) { // TODO: Make more stuff constexpr and make it a static_assert(). @@ -56,7 +64,7 @@ std::optional DebugInfoSelection::fromString(std::string_vie solAssert(componentMap().count("none") == 0, ""); if (_input == "all") - return All(); + return ExceptExperimental(); if (_input == "none") return None(); @@ -74,7 +82,7 @@ std::optional 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; diff --git a/liblangutil/DebugInfoSelection.h b/liblangutil/DebugInfoSelection.h index 3a9432de6d02..cdaf75cc8045 100644 --- a/liblangutil/DebugInfoSelection.h +++ b/liblangutil/DebugInfoSelection.h @@ -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 const& _members) noexcept; + static DebugInfoSelection const ExceptExperimental() noexcept { return Except({&DebugInfoSelection::ethdebug}); } static std::optional fromString(std::string_view _input); static std::optional fromComponents( @@ -72,6 +74,7 @@ struct DebugInfoSelection {"location", &DebugInfoSelection::location}, {"snippet", &DebugInfoSelection::snippet}, {"ast-id", &DebugInfoSelection::astID}, + {"ethdebug", &DebugInfoSelection::ethdebug}, }; return components; } @@ -79,6 +82,7 @@ struct DebugInfoSelection 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); diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 00c9fa196bd8..738638d2097f 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -130,7 +130,7 @@ std::string IRGenerator::generate( ); }; - Whiskers t(R"( + Whiskers t(R"(/// ethdebug: enabled /// @use-src object "" { code { @@ -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()); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3389e7eca94b..d799fcca0bde 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -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."); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 0a62cbf7efaf..433580461c70 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -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)); } @@ -422,6 +426,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac util::LazyInit devDocumentation; util::LazyInit generatedSources; util::LazyInit runtimeGeneratedSources; + util::LazyInit ethdebug; mutable std::optional sourceMapping; mutable std::optional runtimeSourceMapping; }; @@ -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( diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 0194aa937d5e..cacbdbab452b 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -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 experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson"}; + static std::set experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson", "ethdebug"}; for (auto const& selectedArtifactJson: _outputSelection) { std::string const& selectedArtifact = selectedArtifactJson.get(); @@ -268,7 +268,7 @@ bool isBinaryRequested(Json const& _outputSelection) static std::vector const outputsThatRequireBinaries = std::vector{ "*", "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) @@ -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) @@ -1175,6 +1190,36 @@ std::variant StandardCompiler::parseI ret.modelCheckerSettings.timeout = modelCheckerSettings["timeout"].get(); } + 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)}; } @@ -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()) @@ -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); @@ -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; } diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index bd2c7390619b..659c1856063e 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -268,6 +268,7 @@ YulStack::assembleWithDeployed(std::optional _deployName) {{m_charStream->name(), 0}} ) ); + creationObject.ethdebug["not yet implemented @ MachineAssemblyObject::ethdebug"] = true; if (deployedAssembly) { @@ -279,6 +280,7 @@ YulStack::assembleWithDeployed(std::optional _deployName) {{m_charStream->name(), 0}} ) ); + deployedObject.ethdebug["not yet implemented @ MachineAssemblyObject::ethdebug"] = true; } } catch (UnimplementedFeatureError const& _error) @@ -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 diff --git a/libyul/YulStack.h b/libyul/YulStack.h index 0dc90baf890d..65778accc349 100644 --- a/libyul/YulStack.h +++ b/libyul/YulStack.h @@ -58,6 +58,7 @@ struct MachineAssemblyObject std::shared_ptr bytecode; std::shared_ptr assembly; std::unique_ptr sourceMappings; + Json ethdebug = Json::object(); }; /* diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index f78fdfeb81c9..b3af8b30c968 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -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 || @@ -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()); @@ -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; + } } } @@ -1372,6 +1395,7 @@ void CommandLineInterface::outputCompilationResults() handleTransientStorageLayout(contract); handleNatspec(true, contract); handleNatspec(false, contract); + handleEthdebug(contract); } // end of contracts iteration } diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index ce30a4d71db7..fed4b3aaa6af 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -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()) diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 4607250a62fb..85328dcd0c45 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -474,6 +474,7 @@ void CommandLineParser::parseOutputSelection() CompilerOutputs::componentName(&CompilerOutputs::astCompactJson), CompilerOutputs::componentName(&CompilerOutputs::asmJson), CompilerOutputs::componentName(&CompilerOutputs::yulCFGJson), + CompilerOutputs::componentName(&CompilerOutputs::ethdebug), }; static std::set const evmAssemblyJsonImportModeOutputs = { CompilerOutputs::componentName(&CompilerOutputs::asm_), @@ -642,7 +643,13 @@ General Information)").c_str(), po::value()->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() + ) + ".").c_str() ) ( g_strStopAfter.c_str(), @@ -766,6 +773,10 @@ General Information)").c_str(), if (!_forHelp) // Note: We intentionally keep this undocumented for now. outputComponents.add_options() (CompilerOutputs::componentName(&CompilerOutputs::yulCFGJson).c_str(), "Control Flow Graph (CFG) of Yul code in JSON format.") + ( + CompilerOutputs::componentName(&CompilerOutputs::ethdebug).c_str(), + "Ethdebug output of all contracts." + ) ; desc.add(outputComponents); @@ -1451,6 +1462,60 @@ void CommandLineParser::processArgs() m_options.input.mode == InputMode::CompilerWithASTImport || m_options.input.mode == InputMode::EVMAssemblerJSON ); + + bool incompatibleEthdebugOutputs = + m_options.compiler.outputs.asmJson || m_options.compiler.outputs.irAstJson || m_options.compiler.outputs.irOptimizedAstJson; + + bool incompatibleEthdebugInputs = m_options.input.mode != InputMode::Compiler; + + 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 (incompatibleEthdebugOutputs) + solThrow( + CommandLineValidationError, + "--" + CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + " output can only be used with --" + CompilerOutputs::componentName(&CompilerOutputs::ir) + + ", --" + CompilerOutputs::componentName(&CompilerOutputs::irOptimized) + + " and/or --" + CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + "." + ); + + 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 must contain ethdebug, 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) || incompatibleEthdebugOutputs) + ) + solThrow( + CommandLineValidationError, + "--debug-info ethdebug can only be used with --" + CompilerOutputs::componentName(&CompilerOutputs::ir) + + ", --" + CompilerOutputs::componentName(&CompilerOutputs::irOptimized) + + " and/or --" + CompilerOutputs::componentName(&CompilerOutputs::ethdebug) + "." + ); + + if (m_options.output.debugInfoSelection.has_value() && m_options.output.debugInfoSelection->ethdebug && incompatibleEthdebugInputs) + solThrow( + CommandLineValidationError, + "Invalid input mode for --debug-info ethdebug and/or --ethdebug." + ); } void CommandLineParser::parseCombinedJsonOption() diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index 3d187694e2e0..7726d5123e3c 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -88,6 +88,7 @@ struct CompilerOutputs {"storage-layout", &CompilerOutputs::storageLayout}, {"transient-storage-layout", &CompilerOutputs::transientStorageLayout}, {"yul-cfg-json", &CompilerOutputs::yulCFGJson}, + {"ethdebug", &CompilerOutputs::ethdebug}, }; return components; } @@ -110,6 +111,7 @@ struct CompilerOutputs bool metadata = false; bool storageLayout = false; bool transientStorageLayout = false; + bool ethdebug = false; }; struct CombinedJsonRequests diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 4cb0b3c93068..3c49034bc3b8 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -33,6 +33,7 @@ #include #include +#include using namespace solidity::evmasm; using namespace std::string_literals; @@ -152,6 +153,132 @@ Json compile(std::string _input) return ret; } +Json createLanguageAndSourcesSection(std::string const& _language, std::string const& _file, Json const& _content) +{ + Json result = Json::object(); + result["language"] = _language; + result["sources"] = Json::object(); + result["sources"][_file] = Json::object(); + result["sources"][_file]["content"] = _content; + return result; +} + +class Code +{ +public: + virtual ~Code() = default; + explicit Code(std::string _code = {}) : m_code(std::move(_code)) {} + [[nodiscard]] virtual Json json() const = 0; +protected: + std::string m_code; +}; + +class SolidityCode: public Code +{ +public: + explicit SolidityCode(std::string const& _code = {}) : Code(_code) {} + [[nodiscard]] Json json() const override + { + static std::string defaultCode = "pragma solidity >=0.0; contract C { function f() public pure {} }"; + return createLanguageAndSourcesSection("Solidity", "fileA", m_code.empty() ? defaultCode : m_code); + } +}; + +class YulCode: public Code +{ +public: + explicit YulCode(std::string const& _code = {}) : Code(_code) {} + [[nodiscard]] Json json() const override + { + static std::string defaultCode = "{}"; + return createLanguageAndSourcesSection("Yul", "fileA", m_code.empty() ? defaultCode : m_code); + } +}; + +class EvmAssemblyCode: public Code +{ +public: + explicit EvmAssemblyCode(std::string const& _code = {}) : Code(_code) {} + [[nodiscard]] Json json() const override + { + static std::string defaultCode = R"( + { + "assemblyJson": { + ".code": [ + { + "begin": 36, + "end": 51, + "name": "PUSH", + "source": 0, + "value": "0" + } + ], + "sourceList": [ + "" + ] + } + })"; + return createLanguageAndSourcesSection("EVMAssembly", "fileA", m_code.empty() ? Json::parse(defaultCode) : Json::parse(m_code)); + } +}; + +class SolidityAstCode: public Code +{ +public: + explicit SolidityAstCode(std::string const& _code = {}) : Code(_code) {} + [[nodiscard]] Json json() const override + { + static std::string defaultCode = R"( + { + "ast": { + "absolutePath": "empty_contract.sol", + "exportedSymbols": { + "test": [ + 1 + ] + }, + "id": 2, + "nodeType": "SourceUnit", + "nodes": [ + { + "abstract": false, + "baseContracts": [], + "canonicalName": "test", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 1, + "linearizedBaseContracts": [ + 1 + ], + "name": "test", + "nameLocation": "9:4:0", + "nodeType": "ContractDefinition", + "nodes": [], + "scope": 2, + "src": "0:17:0", + "usedErrors": [] + } + ], + "src": "0:124:0" + }, + "id": 0 + })"; + return createLanguageAndSourcesSection("SolidityAST", "fileA", m_code.empty() ? Json::parse(defaultCode) : Json::parse(m_code)); + } +}; + +Json generateStandardJson(bool _viaIr, std::optional const& _debugInfoSelection, Json const& _outputSelection, Code const& _code = SolidityCode()) +{ + Json result = _code.json(); + result["settings"] = Json::object(); + result["settings"]["viaIR"] = _viaIr; + if (_debugInfoSelection.has_value()) + result["settings"]["debug"]["debugInfo"] = _debugInfoSelection.value(); + result["settings"]["outputSelection"]["*"]["*"] = _outputSelection; + return result; +} + } // end anonymous namespace BOOST_AUTO_TEST_SUITE(StandardCompiler) @@ -1782,6 +1909,91 @@ BOOST_AUTO_TEST_CASE(source_location_of_bare_block) BOOST_REQUIRE(sourceMap.find(sourceRef) != std::string::npos); } +BOOST_AUTO_TEST_CASE(ethdebug_excluded_from_wildcards) +{ + frontend::StandardCompiler compiler; + // excluded from output selection wildcard + Json result = compiler.compile(generateStandardJson(true, {}, Json::array({"*"}))); + BOOST_REQUIRE(result.dump().find("ethdebug") == std::string::npos); + // excluded from debug info selection wildcard + result = compiler.compile(generateStandardJson(true, {"*"}, Json::array({"ir"}))); + BOOST_REQUIRE(result.dump().find("ethdebug") == std::string::npos); + // excluded from both - just in case ;) + result = compiler.compile(generateStandardJson(true, {"*"}, Json::array({"*"}))); + BOOST_REQUIRE(result.dump().find("ethdebug") == std::string::npos); +} + +BOOST_AUTO_TEST_CASE(ethdebug_debug_info_ethdebug) +{ + frontend::StandardCompiler compiler; + // it will only work if ir, irOptimized and / or ethdebug was selected as output + // - debug-info selection "ethdebug" will not work on output selection wildcard. + Json result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"*"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + result = compiler.compile(generateStandardJson(true, Json::array({"ethdebug"}), Json::array({"*"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + // debug-info selection "ethdebug" only works with via-ir, if "ethdebug" was selected as output. + // - this one will not work, because via-ir is disabled: + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"ethdebug"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + // - via-ir enabled, everything is fine: + result = compiler.compile(generateStandardJson(true, Json::array({"ethdebug"}), Json::array({"ethdebug"}))); + BOOST_REQUIRE(result.contains("contracts")); + // 'settings.debug.debugInfo' can only include 'ethdebug', if output 'ir', 'irOptimized' and/or 'ethdebug' was selected. + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"bin"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + result = compiler.compile(generateStandardJson(true, Json::array({"ethdebug"}), Json::array({ "bin"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + // debug-info selection "ethdebug" works with ir, irOptimized. + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"ir"}))); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"irOptimized"}))); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + // ir and irOptimized works if selected at the same time, of course. + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"ir", "irOptimized"}))); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + // output selection "ethdebug" only works with via-ir, even if "ir" and "irOptimized" was selected. + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"ir", "irOptimized", "ethdebug"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + result = compiler.compile(generateStandardJson(true, Json::array({"ethdebug"}), Json::array({"ir", "irOptimized", "ethdebug"}))); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + // debug-info ethdebug always works with yul + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"*"}), YulCode())); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + // debug-info ethdebug only supported for languages Solidity and Yul. + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"*"}), EvmAssemblyCode())); + BOOST_REQUIRE(result["errors"].size() == 1); + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"*"}), SolidityAstCode())); + BOOST_REQUIRE(result["errors"].size() == 1); +} + +BOOST_AUTO_TEST_CASE(ethdebug_ethdebug_output) +{ + frontend::StandardCompiler compiler; + // if no debug-info was selected, it implicitly set debug-info to ethdebug. + Json result = compiler.compile(generateStandardJson(true, std::nullopt, Json::array({"ethdebug", "ir"}))); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("/// ethdebug: enabled") != std::string::npos); + // if via-ir was not specified, it will error with a message stating that ethdebug can only be selected as output, if via-ir was defined. solc --ethdebug only works with --via-ir + result = compiler.compile(generateStandardJson(false, Json::array({"ethdebug"}), Json::array({"ethdebug"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + // via-ir enabled, everything is fine: + result = compiler.compile(generateStandardJson(true, Json::array({"ethdebug"}), Json::array({"ethdebug"}))); + BOOST_REQUIRE(result.contains("contracts")); + // if debug-info was selected and did not contain ethdebug, an error will be generated stating that ethdebug need to be set in debug-info solc --ethdebug --debug-info location + result = compiler.compile(generateStandardJson(false, Json::array({"all"}), Json::array({"ethdebug"}))); + BOOST_REQUIRE(result["errors"].size() == 1); + // strict-assembly will always work e.g. solc --strict-assembly --ethdebug + result = compiler.compile(generateStandardJson(false, std::nullopt, Json::array({"ethdebug"}), YulCode())); + BOOST_REQUIRE(result.contains("contracts")); + BOOST_REQUIRE(result.dump().find("ethdebug") != std::string::npos); +} + BOOST_AUTO_TEST_SUITE_END() } // end namespaces diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 8980adcb1821..d365280b84fd 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -59,7 +59,7 @@ std::pair, std::shared_ptr> yul solidity::test::CommonOptions::get().optimize ? solidity::frontend::OptimiserSettings::standard() : solidity::frontend::OptimiserSettings::minimal(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); if (!stack.parseAndAnalyze("", _source) || !stack.errors().empty()) BOOST_FAIL("Invalid source."); diff --git a/test/libyul/EVMCodeTransformTest.cpp b/test/libyul/EVMCodeTransformTest.cpp index e54ab9c8f002..a415cf9a87d5 100644 --- a/test/libyul/EVMCodeTransformTest.cpp +++ b/test/libyul/EVMCodeTransformTest.cpp @@ -57,7 +57,7 @@ TestCase::TestResult EVMCodeTransformTest::run(std::ostream& _stream, std::strin std::nullopt, YulStack::Language::StrictAssembly, settings, - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); if (!stack.parseAndAnalyze("", m_source)) { diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index 5d6dd1b23dea..215f26b934e9 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -69,7 +69,7 @@ TestCase::TestResult ObjectCompilerTest::run(std::ostream& _stream, std::string std::nullopt, YulStack::Language::StrictAssembly, OptimiserSettings::preset(m_optimisationPreset), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); if (!stack.parseAndAnalyze("source", m_source)) { diff --git a/test/libyul/ObjectParser.cpp b/test/libyul/ObjectParser.cpp index b23e530601da..f08e23489618 100644 --- a/test/libyul/ObjectParser.cpp +++ b/test/libyul/ObjectParser.cpp @@ -60,7 +60,7 @@ std::pair parse(std::string const& _source) solidity::test::CommonOptions::get().eofVersion(), YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::none(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); bool success = asmStack.parseAndAnalyze("source", _source); return {success, asmStack.errors()}; @@ -177,7 +177,7 @@ BOOST_AUTO_TEST_CASE(to_string) solidity::test::CommonOptions::get().eofVersion(), YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::none(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); BOOST_REQUIRE(asmStack.parseAndAnalyze("source", code)); BOOST_CHECK_EQUAL(asmStack.print(), expectation); diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 9d4ccd81ba49..84c559e91f60 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -71,7 +71,7 @@ bool YulInterpreterTest::parse(std::ostream& _stream, std::string const& _linePr solidity::test::CommonOptions::get().eofVersion(), YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::none(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); if (stack.parseAndAnalyze("", m_source)) { diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp index 6d19df01b3f8..85b0d1253829 100644 --- a/test/solc/CommandLineInterface.cpp +++ b/test/solc/CommandLineInterface.cpp @@ -1412,6 +1412,130 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_ambiguous_import) BOOST_REQUIRE(!result.success); } +BOOST_AUTO_TEST_CASE(cli_ethdebug_no_ethdebug_in_help) +{ + OptionsReaderAndMessages result = runCLI({"solc", "--help"}); + BOOST_REQUIRE(result.stdoutContent.find("ethdebug") == std::string::npos); +} + +BOOST_AUTO_TEST_CASE(cli_ethdebug_incompatible_outputs) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir.path() / "input.sol"}); + // output selection "asm-json" not working with debug-info "ethdebug" or "ethdebug" output selection. + OptionsReaderAndMessages result = runCLI({"solc", "--debug-info", "ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--via-ir", "--ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--via-ir", "--ethdebug", "--ir-ast-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ir-ast-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--via-ir", "--ethdebug", "--ir-optimized-ast-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ir-optimized-ast-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--import-asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--ethdebug", "--import-asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--import-asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--via-ir", "--ethdebug", "--asm-json", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); +} + +BOOST_AUTO_TEST_CASE(cli_ethdebug_incompatible_input_modes) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir.path() / "input.json"}); + OptionsReaderAndMessages result = runCLI({"solc", "--ethdebug", "--via-ir", "--import-asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--import-asm-json", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--ethdebug", "--via-ir", "--import-ast", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ir", "--import-ast", tempDir.path().string() + "/input.json"}, ""); + BOOST_REQUIRE(!result.success); +} + +BOOST_AUTO_TEST_CASE(cli_ethdebug_debug_info_ethdebug) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir.path() / "input.sol"}, "pragma solidity >=0.0; contract C { function f() public pure {} }"); + createFilesWithParentDirs({tempDir.path() / "input.yul"}, "{}"); + // --debug-info ethdebug only works with --ir, --ir-optimized and --ethdebug (--ethdebug always need --via-ir) + OptionsReaderAndMessages result = runCLI({"solc", "--debug-info", "ethdebug", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + // debug-info selection "ethdebug" only works, if "ir" and "irOptimized" was selected. + result = runCLI({"solc", "--debug-info", "ethdebug", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ir", tempDir.path().string() + "/input.sol"}, ""); + // if --debug-info ethdebug was set, we will output a comment on top of the ir output, indicating that ethdebug was enabled, + // so we use this to check whether --debug-info ethdebug was implicitly enabled. + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") != std::string::npos); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ir-optimized", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") != std::string::npos); + // --ethdebug only works, if --via-ir was defined + result = runCLI({"solc", "--debug-info", "ethdebug", "--ethdebug", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("Debug Data (ethdebug):") != std::string::npos); + // --debug-info ethdebug always works with --strict-assembly + result = runCLI({"solc", "--debug-info", "ethdebug", "--strict-assembly", tempDir.path().string() + "/input.yul"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") != std::string::npos); + // --debug-info ethdebug is excluded from --debug-info all + result = runCLI({"solc", "--debug-info", "all", "--strict-assembly", tempDir.path().string() + "/input.yul"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") == std::string::npos); +} + +BOOST_AUTO_TEST_CASE(cli_ethdebug_ethdebug_output) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir.path() / "input.sol"}, "pragma solidity >=0.0; contract C { function f() public pure {} }"); + // --ethdebug only works with --via-ir + OptionsReaderAndMessages result = runCLI({"solc", "--ethdebug", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("Debug Data (ethdebug):") != std::string::npos); + result = runCLI({"solc", "--ethdebug", "--via-ir", "--ir", tempDir.path().string() + "/input.sol"}, ""); + // --ethdebug will implicitly set --debug-info ethdebug, if no --debug-info was set. + // right now, if --debug-info ethdebug was set, we will output a comment on top of the ir output, indicating that ethdebug was enabled, + // so we use this to check whether --debug-info ethdebug was implicitly enabled. + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") != std::string::npos); + // let's just do the same for --ir-optimized + result = runCLI({"solc", "--ethdebug", "--via-ir", "--ir-optimized", "--optimize", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); + BOOST_REQUIRE(result.stdoutContent.find("/// ethdebug: enabled") != std::string::npos); + // --ethdebug only works with --via-ir. + result = runCLI({"solc", "--debug-info", "ethdebug", "--ethdebug", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); + // --ethdebug only works, if --debug-info was not set to something else as "ethdebug". + result = runCLI({"solc", "--debug-info", "location", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "all", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(!result.success); + result = runCLI({"solc", "--debug-info", "ethdebug", "--ethdebug", "--via-ir", tempDir.path().string() + "/input.sol"}, ""); + BOOST_REQUIRE(result.success); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace solidity::frontend::test diff --git a/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp index 595aecdca3f4..5df804388cd8 100644 --- a/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_assembly_ossfuzz.cpp @@ -41,7 +41,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) std::nullopt, YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::minimal(), - langutil::DebugInfoSelection::All() + langutil::DebugInfoSelection::ExceptExperimental() ); if (!stack.parseAndAnalyze("source", input)) diff --git a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp index f685aa90da4b..d477f0736e70 100644 --- a/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_diff_ossfuzz.cpp @@ -65,7 +65,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) std::nullopt, YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::full(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); try { diff --git a/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp index 7553c81c0f44..42bb78d49894 100644 --- a/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp +++ b/test/tools/ossfuzz/strictasm_opt_ossfuzz.cpp @@ -42,7 +42,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) std::nullopt, YulStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::full(), - DebugInfoSelection::All() + DebugInfoSelection::ExceptExperimental() ); if (!stack.parseAndAnalyze("source", input))