From 43fe553ff002a60b1b2449250a530169b8db14fd Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Mon, 21 Feb 2022 18:56:05 -0500 Subject: [PATCH] [solc] Add --import-asm-json input mode. --- libsolidity/interface/CompilerStack.cpp | 112 ++++++++++++++-------- libsolidity/interface/CompilerStack.h | 5 + libsolidity/interface/OptimiserSettings.h | 2 + solc/CommandLineInterface.cpp | 57 ++++++++++- solc/CommandLineInterface.h | 2 + solc/CommandLineParser.cpp | 65 ++++++++----- solc/CommandLineParser.h | 3 +- 7 files changed, 173 insertions(+), 73 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3eb25987d..48936e2d8 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -414,6 +414,28 @@ void CompilerStack::importASTs(map const& _sources) storeContractDefinitions(); } +void CompilerStack::importEvmAssemblyJson(std::map const& _sources) +{ + solAssert(_sources.size() == 1, ""); + solAssert(m_sources.empty(), ""); + solAssert(m_sourceOrder.empty(), ""); + if (m_stackState != Empty) + solThrow(CompilerError, "Must call importEvmAssemblyJson only before the SourcesSet state."); + + Json::Value jsonValue = _sources.begin()->second; + if (jsonValue.isMember("sourceList")) + for (auto const& item: jsonValue["sourceList"]) + { + Source source; + source.charStream = std::make_shared(item.asString(), ""); + m_sources.emplace(std::make_pair(item.asString(), source)); + m_sourceOrder.push_back(&m_sources[item.asString()]); + } + m_evmAssemblyJson[_sources.begin()->first] = jsonValue; + m_importedSources = true; + m_stackState = SourcesSet; +} + bool CompilerStack::analyze() { if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed) @@ -600,6 +622,9 @@ bool CompilerStack::parseAndAnalyze(State _stopAfter) { m_stopAfter = _stopAfter; + if (!m_evmAssemblyJson.empty()) + return true; + bool success = parse(); if (m_stackState >= m_stopAfter) return success; @@ -649,55 +674,58 @@ bool CompilerStack::compile(State _stopAfter) // Only compile contracts individually which have been requested. map> otherCompilers; - for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->nodes()) - if (auto contract = dynamic_cast(node.get())) - if (isRequestedContract(*contract)) - { - try + if (!m_evmAssemblyJson.empty()) + { + + } + else + { + for (Source const* source: m_sourceOrder) + for (ASTPointer const& node: source->ast->nodes()) + if (auto contract = dynamic_cast(node.get())) + if (isRequestedContract(*contract)) { - if (m_viaIR || m_generateIR || m_generateEwasm) - generateIR(*contract); - if (m_generateEvmBytecode) + try { - if (m_viaIR) - generateEVMFromIR(*contract); - else - compileContract(*contract, otherCompilers); + if (m_viaIR || m_generateIR || m_generateEwasm) + generateIR(*contract); + if (m_generateEvmBytecode) + { + if (m_viaIR) + generateEVMFromIR(*contract); + else + compileContract(*contract, otherCompilers); + } + if (m_generateEwasm) + generateEwasm(*contract); } - if (m_generateEwasm) - generateEwasm(*contract); - } - catch (Error const& _error) - { - if (_error.type() != Error::Type::CodeGenerationError) - throw; - m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what()); - return false; - } - catch (UnimplementedFeatureError const& _unimplementedError) - { - if ( - SourceLocation const* sourceLocation = - boost::get_error_info(_unimplementedError) - ) + catch (Error const& _error) { - string const* comment = _unimplementedError.comment(); - m_errorReporter.error( - 1834_error, - Error::Type::CodeGenerationError, - *sourceLocation, - "Unimplemented feature error" + - ((comment && !comment->empty()) ? ": " + *comment : string{}) + - " in " + - _unimplementedError.lineInfo() - ); + if (_error.type() != Error::Type::CodeGenerationError) + throw; + m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what()); return false; } - else - throw; + catch (UnimplementedFeatureError const& _unimplementedError) + { + if (SourceLocation const* sourceLocation + = boost::get_error_info(_unimplementedError)) + { + string const* comment = _unimplementedError.comment(); + m_errorReporter.error( + 1834_error, + Error::Type::CodeGenerationError, + *sourceLocation, + "Unimplemented feature error" + + ((comment && !comment->empty()) ? ": " + *comment : string{}) + " in " + + _unimplementedError.lineInfo()); + return false; + } + else + throw; + } } - } + } m_stackState = CompilationSuccessful; this->link(); return true; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index c1f15a480..5618a9574 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -222,6 +222,10 @@ public: /// Will throw errors if the import fails void importASTs(std::map const& _sources); + /// Imports given Evm Assembly Json. Leads to the same internal state as parse(). + /// Will throw errors if the import fails + void importEvmAssemblyJson(std::map const& _sources); + /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// typechecking, staticAnalysis) on previously parsed sources. /// @returns false on error. @@ -499,6 +503,7 @@ private: std::map m_sources; // if imported, store AST-JSONS for each filename std::map m_sourceJsons; + std::map m_evmAssemblyJson; std::vector m_unhandledSMTLib2Queries; std::map m_smtlib2Responses; std::shared_ptr m_globalContext; diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index e233048c1..2e960e8d3 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -149,6 +149,8 @@ struct OptimiserSettings /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. size_t expectedExecutionsPerDeployment = 200; + /// Flag reflecting whether optimizer is enabled. + bool enabled = false; }; } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 03679bd7c..09d305282 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -555,6 +555,25 @@ map CommandLineInterface::parseAstFromInput() return sourceJsons; } +map CommandLineInterface::parseEvmAssemblyJsonFromInput() +{ + solAssert(m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, ""); + solAssert(m_fileReader.sourceUnits().size() == 1, ""); + + map sourceJsons; + + for (auto const& iter: m_fileReader.sourceUnits()) + { + Json::Value evmAsmJson; + astAssert(jsonParseStrict(iter.second, evmAsmJson), "Input file could not be parsed to JSON"); + astAssert(evmAsmJson.isMember(".code"), "Invalid Format for assembly-JSON: Must have '.code'-object"); + astAssert(evmAsmJson.isMember(".data"), "Invalid Format for assembly-JSON: Must have '.data'-object"); + sourceJsons[iter.first] = evmAsmJson; + } + + return sourceJsons; +} + void CommandLineInterface::createFile(string const& _fileName, string const& _data) { namespace fs = boost::filesystem; @@ -658,6 +677,7 @@ void CommandLineInterface::processInput() break; case InputMode::Compiler: case InputMode::CompilerWithASTImport: + case InputMode::CompilerWithEvmAssemblyJsonImport: compile(); outputCompilationResults(); } @@ -678,7 +698,11 @@ void CommandLineInterface::printLicense() void CommandLineInterface::compile() { - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); + solAssert( + m_options.input.mode == InputMode::Compiler || + m_options.input.mode == InputMode::CompilerWithASTImport || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "" + ); m_compiler = make_unique(m_fileReader.reader()); @@ -725,7 +749,18 @@ void CommandLineInterface::compile() m_compiler->setOptimiserSettings(m_options.optimiserSettings()); - if (m_options.input.mode == InputMode::CompilerWithASTImport) + if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) + { + try + { + m_compiler->importEvmAssemblyJson(parseEvmAssemblyJsonFromInput()); + } + catch (Exception const& _exc) + { + solThrow(CommandLineExecutionError, "Failed to import Evm Assembly JSON: "s + _exc.what()); + } + } + else if (m_options.input.mode == InputMode::CompilerWithASTImport) { try { @@ -785,7 +820,11 @@ void CommandLineInterface::compile() void CommandLineInterface::handleCombinedJSON() { - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); + solAssert( + m_options.input.mode == InputMode::Compiler || + m_options.input.mode == InputMode::CompilerWithASTImport || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "" + ); if (!m_options.compiler.combinedJsonRequests.has_value()) return; @@ -877,7 +916,11 @@ void CommandLineInterface::handleCombinedJSON() void CommandLineInterface::handleAst() { - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); + solAssert( + m_options.input.mode == InputMode::Compiler || + m_options.input.mode == InputMode::CompilerWithASTImport || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "" + ); if (!m_options.compiler.outputs.astCompactJson) return; @@ -1121,7 +1164,11 @@ void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul: void CommandLineInterface::outputCompilationResults() { - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); + solAssert( + m_options.input.mode == InputMode::Compiler || + m_options.input.mode == InputMode::CompilerWithASTImport || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "" + ); handleCombinedJSON(); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 951731825..de487aa10 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -115,6 +115,8 @@ private: /// or standard-json output std::map parseAstFromInput(); + std::map parseEvmAssemblyJsonFromInput(); + /// Create a file in the given directory /// @arg _fileName the name of the file /// @arg _data to be written diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 8f6d549f2..a306820b1 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -51,6 +51,7 @@ static string const g_strExperimentalViaIR = "experimental-via-ir"; static string const g_strGas = "gas"; static string const g_strHelp = "help"; static string const g_strImportAst = "import-ast"; +static string const g_strImportEvmAssemblerJson = "import-asm-json"; static string const g_strInputFile = "input-file"; static string const g_strYul = "yul"; static string const g_strYulDialect = "yul-dialect"; @@ -137,6 +138,7 @@ static map const g_inputModeName = { {InputMode::StandardJson, "standard JSON"}, {InputMode::Linker, "linker"}, {InputMode::LanguageServer, "language server (LSP)"}, + {InputMode::CompilerWithEvmAssemblyJsonImport, "assembler (EVM ASM JSON import)"} }; void CommandLineParser::checkMutuallyExclusive(vector const& _optionNames) @@ -166,7 +168,7 @@ ostream& operator<<(ostream& _out, CompilerOutputs const& _selection) if (_selection.*component) serializedSelection.push_back(CompilerOutputs::componentName(component)); - return _out << util::joinHumanReadable(serializedSelection, ","); + return _out << joinHumanReadable(serializedSelection, ","); } string const& CompilerOutputs::componentName(bool CompilerOutputs::* _component) @@ -197,7 +199,7 @@ ostream& operator<<(ostream& _out, CombinedJsonRequests const& _requests) if (_requests.*component) serializedRequests.push_back(CombinedJsonRequests::componentName(component)); - return _out << util::joinHumanReadable(serializedRequests, ","); + return _out << joinHumanReadable(serializedRequests, ","); } string const& CombinedJsonRequests::componentName(bool CombinedJsonRequests::* _component) @@ -267,6 +269,8 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const if (optimizer.yulSteps.has_value()) settings.yulOptimiserSteps = optimizer.yulSteps.value(); + settings.enabled = optimizer.enabled; + return settings; } @@ -316,20 +320,20 @@ void CommandLineParser::parseInputPathsAndRemappings() m_options.input.paths.insert(positionalArg); } - if (m_options.input.mode == InputMode::StandardJson) + if (m_options.input.mode == InputMode::StandardJson || m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) { if (m_options.input.paths.size() > 1 || (m_options.input.paths.size() == 1 && m_options.input.addStdin)) solThrow( CommandLineValidationError, - "Too many input files for --" + g_strStandardJSON + ".\n" + "Too many input files for --" + (m_options.input.mode == InputMode::StandardJson ? g_strStandardJSON : g_strImportEvmAssemblerJson) + ".\n" "Please either specify a single file name or provide its content on standard input." ); - else if (m_options.input.paths.size() == 0) + else if (m_options.input.paths.empty()) // Standard JSON mode input used to be handled separately and zero files meant "read from stdin". // Keep it working that way for backwards-compatibility. m_options.input.addStdin = true; } - else if (m_options.input.paths.size() == 0 && !m_options.input.addStdin) + else if (m_options.input.paths.empty() && !m_options.input.addStdin) solThrow( CommandLineValidationError, "No input files given. If you wish to use the standard input please specify \"-\" explicitly." @@ -343,17 +347,17 @@ void CommandLineParser::parseLibraryOption(string const& _input) try { if (fs::is_regular_file(_input)) - data = util::readFileAsString(_input); + data = readFileAsString(_input); } catch (fs::filesystem_error const&) { // Thrown e.g. if path is too long. } - catch (util::FileNotFound const&) + catch (FileNotFound const&) { // Should not happen if `fs::is_regular_file` is correct. } - catch (util::NotAFile const&) + catch (NotAFile const&) { // Should not happen if `fs::is_regular_file` is correct. } @@ -418,15 +422,15 @@ void CommandLineParser::parseLibraryOption(string const& _input) "Invalid length for address for library \"" + libName + "\": " + to_string(addrString.length()) + " instead of 40 characters." ); - if (!util::passesAddressChecksum(addrString, false)) + if (!passesAddressChecksum(addrString, false)) solThrow( CommandLineValidationError, "Invalid checksum on address for library \"" + libName + "\": " + addrString + "\n" - "The correct checksum is " + util::getChecksummedAddress(addrString) + "The correct checksum is " + getChecksummedAddress(addrString) ); - bytes binAddr = util::fromHex(addrString); - util::h160 address(binAddr, util::h160::AlignRight); - if (binAddr.size() > 20 || address == util::h160()) + bytes binAddr = fromHex(addrString); + h160 address(binAddr, h160::AlignRight); + if (binAddr.size() > 20 || address == h160()) solThrow( CommandLineValidationError, "Invalid address for library \"" + libName + "\": " + addrString @@ -461,6 +465,7 @@ void CommandLineParser::parseOutputSelection() solAssert(false); case InputMode::Compiler: case InputMode::CompilerWithASTImport: + case InputMode::CompilerWithEvmAssemblyJsonImport: return util::contains(compilerModeOutputs, _outputName); case InputMode::Assembler: return util::contains(assemblerModeOutputs, _outputName); @@ -582,15 +587,15 @@ General Information)").c_str(), ) ( g_strRevertStrings.c_str(), - po::value()->value_name(util::joinHumanReadable(g_revertStringsArgs, ",")), + po::value()->value_name(joinHumanReadable(g_revertStringsArgs, ",")), "Strip revert (and require) reason strings or add additional debugging information." ) ( g_strDebugInfo.c_str(), - po::value()->default_value(util::toString(DebugInfoSelection::Default())), + po::value()->default_value(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: " + joinHumanReadable(DebugInfoSelection::componentMap() | ranges::views::keys) + ".").c_str() ) ( g_strStopAfter.c_str(), @@ -636,6 +641,10 @@ General Information)").c_str(), "Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by " "--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str() ) + ( + g_strImportEvmAssemblerJson.c_str(), + "Import evm assembler json, assumes input holds the evm assembly in JSON format." + ) ( g_strLSP.c_str(), "Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend " @@ -648,12 +657,12 @@ General Information)").c_str(), assemblyModeOptions.add_options() ( g_strMachine.c_str(), - po::value()->value_name(util::joinHumanReadable(g_machineArgs, ",")), + po::value()->value_name(joinHumanReadable(g_machineArgs, ",")), "Target machine in assembly or Yul mode." ) ( g_strYulDialect.c_str(), - po::value()->value_name(util::joinHumanReadable(g_yulDialectArgs, ",")), + po::value()->value_name(joinHumanReadable(g_yulDialectArgs, ",")), "Input dialect to use in assembly or yul mode." ) ; @@ -726,7 +735,7 @@ General Information)").c_str(), ) ( g_strCombinedJson.c_str(), - po::value()->value_name(util::joinHumanReadable(CombinedJsonRequests::componentMap() | ranges::views::keys, ",")), + po::value()->value_name(joinHumanReadable(CombinedJsonRequests::componentMap() | ranges::views::keys, ",")), "Output a single json document containing the specified information." ) ; @@ -736,7 +745,7 @@ General Information)").c_str(), metadataOptions.add_options() ( g_strMetadataHash.c_str(), - po::value()->value_name(util::joinHumanReadable(g_metadataHashArgs, ",")), + po::value()->value_name(joinHumanReadable(g_metadataHashArgs, ",")), "Choose hash method for the bytecode metadata or disable it." ) ( @@ -892,6 +901,8 @@ void CommandLineParser::processArgs() m_options.input.mode = InputMode::Linker; else if (m_args.count(g_strImportAst) > 0) m_options.input.mode = InputMode::CompilerWithASTImport; + else if (m_args.count(g_strImportEvmAssemblerJson) > 0) + m_options.input.mode = InputMode::CompilerWithEvmAssemblyJsonImport; else m_options.input.mode = InputMode::Compiler; @@ -1011,11 +1022,11 @@ void CommandLineParser::processArgs() if (m_args.count(g_strPrettyJson) > 0) { - m_options.formatting.json.format = util::JsonFormat::Pretty; + m_options.formatting.json.format = JsonFormat::Pretty; } if (!m_args[g_strJsonIndent].defaulted()) { - m_options.formatting.json.format = util::JsonFormat::Pretty; + m_options.formatting.json.format = JsonFormat::Pretty; m_options.formatting.json.indent = m_args[g_strJsonIndent].as(); } @@ -1260,7 +1271,11 @@ void CommandLineParser::processArgs() if (m_options.input.mode == InputMode::Compiler) m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0); - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport); + solAssert( + m_options.input.mode == InputMode::Compiler || + m_options.input.mode == InputMode::CompilerWithASTImport || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport + ); } void CommandLineParser::parseCombinedJsonOption() @@ -1289,7 +1304,7 @@ size_t CommandLineParser::countEnabledOptions(vector const& _optionNames string CommandLineParser::joinOptionNames(vector const& _optionNames, string _separator) { - return util::joinHumanReadable( + return joinHumanReadable( _optionNames | ranges::views::transform([](string const& _option){ return "--" + _option; }), _separator ); diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index 791e7f1c1..68eb51ed3 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -56,7 +56,8 @@ enum class InputMode StandardJson, Linker, Assembler, - LanguageServer + LanguageServer, + CompilerWithEvmAssemblyJsonImport }; struct CompilerOutputs