From 235af2b4d1245f59525d9fd148d7892ea89b46a0 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Thu, 4 Nov 2021 16:53:32 -0500 Subject: [PATCH] [solc] Add --import-asm-json input mode. --- solc/CommandLineInterface.cpp | 281 ++++++++++++++++------------- solc/CommandLineParser.cpp | 48 +++-- solc/CommandLineParser.h | 1 + test/solc/CommandLineInterface.cpp | 2 +- 4 files changed, 190 insertions(+), 142 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1d069f925..4a6598fbc 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -634,6 +634,7 @@ bool CommandLineInterface::processInput() break; case InputMode::Compiler: case InputMode::CompilerWithASTImport: + case InputMode::CompilerWithEvmAssemblyJsonImport: if (!compile()) return false; outputCompilationResults(); @@ -657,105 +658,112 @@ void CommandLineInterface::printLicense() bool 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()); - SourceReferenceFormatter formatter(serr(false), *m_compiler, coloredOutput(m_options), m_options.formatting.withErrorIds); - - try + if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) { - if (m_options.metadata.literalSources) - m_compiler->useMetadataLiteralSources(true); - m_compiler->setMetadataHash(m_options.metadata.hash); - if (m_options.modelChecker.initialize) - m_compiler->setModelCheckerSettings(m_options.modelChecker.settings); - m_compiler->setRemappings(m_options.input.remappings); - m_compiler->setLibraries(m_options.linker.libraries); - m_compiler->setViaIR(m_options.output.experimentalViaIR); - m_compiler->setEVMVersion(m_options.output.evmVersion); - m_compiler->setRevertStringBehaviour(m_options.output.revertStrings); - if (m_options.output.debugInfoSelection.has_value()) - m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value()); - // TODO: Perhaps we should not compile unless requested - m_compiler->enableIRGeneration(m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized); - m_compiler->enableEwasmGeneration(m_options.compiler.outputs.ewasm); - m_compiler->enableEvmBytecodeGeneration( - m_options.compiler.estimateGas || - m_options.compiler.outputs.asm_ || - m_options.compiler.outputs.asmJson || - m_options.compiler.outputs.opcodes || - m_options.compiler.outputs.binary || - m_options.compiler.outputs.binaryRuntime || - (m_options.compiler.combinedJsonRequests && ( - m_options.compiler.combinedJsonRequests->binary || - m_options.compiler.combinedJsonRequests->binaryRuntime || - m_options.compiler.combinedJsonRequests->opcodes || - m_options.compiler.combinedJsonRequests->asm_ || - m_options.compiler.combinedJsonRequests->generatedSources || - m_options.compiler.combinedJsonRequests->generatedSourcesRuntime || - m_options.compiler.combinedJsonRequests->srcMap || - m_options.compiler.combinedJsonRequests->srcMapRuntime || - m_options.compiler.combinedJsonRequests->funDebug || - m_options.compiler.combinedJsonRequests->funDebugRuntime - )) - ); + } + else + { + SourceReferenceFormatter + formatter(serr(false), *m_compiler, coloredOutput(m_options), m_options.formatting.withErrorIds); - m_compiler->setOptimiserSettings(m_options.optimiserSettings()); - - if (m_options.input.mode == InputMode::CompilerWithASTImport) + try { - try - { - m_compiler->importASTs(parseAstFromInput()); + if (m_options.metadata.literalSources) + m_compiler->useMetadataLiteralSources(true); + m_compiler->setMetadataHash(m_options.metadata.hash); + if (m_options.modelChecker.initialize) + m_compiler->setModelCheckerSettings(m_options.modelChecker.settings); + m_compiler->setRemappings(m_options.input.remappings); + m_compiler->setLibraries(m_options.linker.libraries); + m_compiler->setViaIR(m_options.output.experimentalViaIR); + m_compiler->setEVMVersion(m_options.output.evmVersion); + m_compiler->setRevertStringBehaviour(m_options.output.revertStrings); + if (m_options.output.debugInfoSelection.has_value()) + m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value()); + // TODO: Perhaps we should not compile unless requested - if (!m_compiler->analyze()) + m_compiler->enableIRGeneration(m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized); + m_compiler->enableEwasmGeneration(m_options.compiler.outputs.ewasm); + m_compiler->enableEvmBytecodeGeneration( + m_options.compiler.estimateGas || m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson + || m_options.compiler.outputs.opcodes || m_options.compiler.outputs.binary + || m_options.compiler.outputs.binaryRuntime + || (m_options.compiler.combinedJsonRequests + && (m_options.compiler.combinedJsonRequests->binary + || m_options.compiler.combinedJsonRequests->binaryRuntime + || m_options.compiler.combinedJsonRequests->opcodes + || m_options.compiler.combinedJsonRequests->asm_ + || m_options.compiler.combinedJsonRequests->generatedSources + || m_options.compiler.combinedJsonRequests->generatedSourcesRuntime + || m_options.compiler.combinedJsonRequests->srcMap + || m_options.compiler.combinedJsonRequests->srcMapRuntime + || m_options.compiler.combinedJsonRequests->funDebug + || m_options.compiler.combinedJsonRequests->funDebugRuntime))); + + m_compiler->setOptimiserSettings(m_options.optimiserSettings()); + + if (m_options.input.mode == InputMode::CompilerWithASTImport) + { + try { - formatter.printErrorInformation(m_compiler->errors()); - astAssert(false, "Analysis of the AST failed"); + m_compiler->importASTs(parseAstFromInput()); + + if (!m_compiler->analyze()) + { + formatter.printErrorInformation(m_compiler->errors()); + astAssert(false, "Analysis of the AST failed"); + } + } + catch (Exception const& _exc) + { + serr() << string("Failed to import AST: ") << _exc.what() << endl; + return false; } } - catch (Exception const& _exc) + else { - serr() << string("Failed to import AST: ") << _exc.what() << endl; - return false; + m_compiler->setSources(m_fileReader.sourceCodes()); + m_compiler->setParserErrorRecovery(m_options.input.errorRecovery); } - } - else - { - m_compiler->setSources(m_fileReader.sourceCodes()); - m_compiler->setParserErrorRecovery(m_options.input.errorRecovery); - } - bool successful = m_compiler->compile(m_options.output.stopAfter); + bool successful = m_compiler->compile(m_options.output.stopAfter); - for (auto const& error: m_compiler->errors()) + for (auto const& error: m_compiler->errors()) + { + m_hasOutput = true; + formatter.printErrorInformation(*error); + } + + if (!successful) + return m_options.input.errorRecovery; + } + catch (CompilerError const& _exception) { m_hasOutput = true; - formatter.printErrorInformation(*error); + formatter.printExceptionInformation(_exception, "Compiler error"); + return false; } - - if (!successful) - return m_options.input.errorRecovery; - } - catch (CompilerError const& _exception) - { - m_hasOutput = true; - formatter.printExceptionInformation(_exception, "Compiler error"); - return false; - } - catch (Error const& _error) - { - if (_error.type() == Error::Type::DocstringParsingError) - serr() << "Documentation parsing error: " << *boost::get_error_info(_error) << endl; - else + catch (Error const& _error) { - m_hasOutput = true; - formatter.printExceptionInformation(_error, _error.typeName()); - } + if (_error.type() == Error::Type::DocstringParsingError) + serr() << "Documentation parsing error: " << *boost::get_error_info(_error) << endl; + else + { + m_hasOutput = true; + formatter.printExceptionInformation(_error, _error.typeName()); + } - return false; + return false; + } } return true; @@ -763,7 +771,11 @@ bool 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::CompilerWithEvmAssemblyJsonImport || + m_options.input.mode == InputMode::CompilerWithASTImport, "" + ); if (!m_options.compiler.combinedJsonRequests.has_value()) return; @@ -1095,68 +1107,77 @@ bool 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::CompilerWithEvmAssemblyJsonImport || + m_options.input.mode == InputMode::CompilerWithASTImport, "" + ); handleCombinedJSON(); - // do we need AST output? - handleAst(); - - if ( - !m_compiler->compilationSuccessful() && - m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful - ) + if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) { - serr() << endl << "Compilation halted after AST generation due to errors." << endl; - return; + handleBytecode(""); } - - vector contracts = m_compiler->contractNames(); - for (string const& contract: contracts) + else { - if (needsHumanTargetedStdout(m_options)) - sout() << endl << "======= " << contract << " =======" << endl; + // do we need AST output? + handleAst(); - // do we need EVM assembly? - if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) + if (!m_compiler->compilationSuccessful() + && m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful) { - string ret; - if (m_options.compiler.outputs.asmJson) - ret = jsonPrettyPrint(removeNullMembers(m_compiler->assemblyJSON(contract))); - else - ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes()); - - if (!m_options.output.dir.empty()) - { - createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); - } - else - { - sout() << "EVM assembly:" << endl << ret << endl; - } + serr() << endl << "Compilation halted after AST generation due to errors." << endl; + return; } - if (m_options.compiler.estimateGas) - handleGasEstimation(contract); + vector contracts = m_compiler->contractNames(); + for (string const& contract: contracts) + { + if (needsHumanTargetedStdout(m_options)) + sout() << endl << "======= " << contract << " =======" << endl; - handleBytecode(contract); - handleIR(contract); - handleIROptimized(contract); - handleEwasm(contract); - handleSignatureHashes(contract); - handleMetadata(contract); - handleABI(contract); - handleStorageLayout(contract); - handleNatspec(true, contract); - handleNatspec(false, contract); - } // end of contracts iteration + // do we need EVM assembly? + if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) + { + string ret; + if (m_options.compiler.outputs.asmJson) + ret = jsonPrettyPrint(removeNullMembers(m_compiler->assemblyJSON(contract))); + else + ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes()); - if (!m_hasOutput) - { - if (!m_options.output.dir.empty()) - sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_options.output.dir << "." << endl; - else - serr() << "Compiler run successful, no output requested." << endl; + if (!m_options.output.dir.empty()) + { + createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); + } + else + { + sout() << "EVM assembly:" << endl << ret << endl; + } + } + + if (m_options.compiler.estimateGas) + handleGasEstimation(contract); + + handleBytecode(contract); + handleIR(contract); + handleIROptimized(contract); + handleEwasm(contract); + handleSignatureHashes(contract); + handleMetadata(contract); + handleABI(contract); + handleStorageLayout(contract); + handleNatspec(true, contract); + handleNatspec(false, contract); + } // end of contracts iteration + + if (!m_hasOutput) + { + if (!m_options.output.dir.empty()) + sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_options.output.dir << "." << endl; + else + serr() << "Compiler run successful, no output requested." << endl; + } } } diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 9356c9662..acfc09c87 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -46,6 +46,7 @@ static string const g_strAllowPaths = "allow-paths"; static string const g_strBasePath = "base-path"; static string const g_strIncludePath = "include-path"; static string const g_strAssemble = "assemble"; +static string const g_strImportEvmAssemblerJson = "import-asm-json"; static string const g_strCombinedJson = "combined-json"; static string const g_strErrorRecovery = "error-recovery"; static string const g_strEVM = "evm"; @@ -134,6 +135,7 @@ static map const g_inputModeName = { {InputMode::Compiler, "compiler"}, {InputMode::CompilerWithASTImport, "compiler (AST import)"}, {InputMode::Assembler, "assembler"}, + {InputMode::CompilerWithEvmAssemblyJsonImport, "assembler (EVM ASM JSON import)"}, {InputMode::StandardJson, "standard JSON"}, {InputMode::Linker, "linker"}, }; @@ -321,7 +323,20 @@ bool CommandLineParser::parseInputPathsAndRemappings() m_options.input.paths.insert(positionalArg); } - if (m_options.input.mode == InputMode::StandardJson) + if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) + { + if (m_options.input.paths.size() > 1 || (m_options.input.paths.size() == 1 && m_options.input.addStdin)) + { + serr() << "Too many input files for --" << g_strImportEvmAssemblerJson << "." << endl; + serr() << "Please either specify a single file name or provide its content on standard input." << endl; + return false; + } + else if (m_options.input.paths.size() == 0) + // 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.mode == InputMode::StandardJson) { if (m_options.input.paths.size() > 1 || (m_options.input.paths.size() == 1 && m_options.input.addStdin)) { @@ -466,6 +481,7 @@ bool CommandLineParser::parseOutputSelection() case InputMode::Version: solAssert(false); case InputMode::Compiler: + case InputMode::CompilerWithEvmAssemblyJsonImport: case InputMode::CompilerWithASTImport: return contains(compilerModeOutputs, _outputName); case InputMode::Assembler: @@ -641,8 +657,12 @@ General Information)").c_str(), ( g_strImportAst.c_str(), ("Import ASTs to be compiled, assumes input holds the AST in compact JSON format. " - "Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by " - "--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).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 to be compiled, assumes input holds the evm assembly in JSON format." ) ; desc.add(alternativeInputModes); @@ -879,6 +899,7 @@ bool CommandLineParser::processArgs() g_strStrictAssembly, g_strYul, g_strImportAst, + g_strImportEvmAssemblerJson, })) return false; @@ -892,6 +913,8 @@ bool CommandLineParser::processArgs() m_options.input.mode = InputMode::StandardJson; else if (m_args.count(g_strAssemble) > 0 || m_args.count(g_strStrictAssembly) > 0 || m_args.count(g_strYul) > 0) m_options.input.mode = InputMode::Assembler; + else if (m_args.count(g_strImportEvmAssemblerJson) > 0) + m_options.input.mode = InputMode::CompilerWithEvmAssemblyJsonImport; else if (m_args.count(g_strLink) > 0) m_options.input.mode = InputMode::Linker; else if (m_args.count(g_strImportAst) > 0) @@ -946,8 +969,8 @@ bool CommandLineParser::processArgs() if ( m_options.input.mode != InputMode::Compiler && m_options.input.mode != InputMode::CompilerWithASTImport && - m_options.input.mode != InputMode::Assembler - ) + m_options.input.mode != InputMode::Assembler && + m_options.input.mode != InputMode::CompilerWithEvmAssemblyJsonImport) { if (!m_args[g_strOptimizeRuns].defaulted()) { @@ -1127,16 +1150,18 @@ bool CommandLineParser::processArgs() m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as(); } - if (m_options.input.mode == InputMode::Assembler) + if (m_options.input.mode == InputMode::Assembler || + m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) { - vector const nonAssemblyModeOptions = { + vector nonAssemblyModeOptions = { // TODO: The list is not complete. Add more. g_strOutputDir, g_strGas, - g_strCombinedJson, g_strOptimizeYul, g_strNoOptimizeYul, }; + if (m_options.input.mode == InputMode::Assembler) + nonAssemblyModeOptions.emplace_back(g_strCombinedJson); if (countEnabledOptions(nonAssemblyModeOptions) >= 1) { auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; }; @@ -1206,9 +1231,10 @@ bool CommandLineParser::processArgs() serr() << "and automatic translation is not available." << endl; return false; } - serr() << - "Warning: Yul is still experimental. Please use the output with care." << - endl; + if (m_options.input.mode == InputMode::Assembler) + serr() << + "Warning: Yul is still experimental. Please use the output with care." << + endl; return true; } diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index a1d13c690..c946f1204 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -56,6 +56,7 @@ enum class InputMode StandardJson, Linker, Assembler, + CompilerWithEvmAssemblyJsonImport, }; struct CompilerOutputs diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp index 0269ec462..fcb3d6d67 100644 --- a/test/solc/CommandLineInterface.cpp +++ b/test/solc/CommandLineInterface.cpp @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes) }; string expectedMessage = "The following options are mutually exclusive: " - "--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. " + "--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast, --import-asm-json. " "Select at most one.\n"; for (string const& mode1: inputModeOptions)