[solc] Add --import-asm-json input mode.

This commit is contained in:
Alexander Arlt 2021-11-04 16:53:32 -05:00
parent 7334420423
commit 235af2b4d1
4 changed files with 190 additions and 142 deletions

View File

@ -634,6 +634,7 @@ bool CommandLineInterface::processInput()
break; break;
case InputMode::Compiler: case InputMode::Compiler:
case InputMode::CompilerWithASTImport: case InputMode::CompilerWithASTImport:
case InputMode::CompilerWithEvmAssemblyJsonImport:
if (!compile()) if (!compile())
return false; return false;
outputCompilationResults(); outputCompilationResults();
@ -657,105 +658,112 @@ void CommandLineInterface::printLicense()
bool CommandLineInterface::compile() 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<CompilerStack>(m_fileReader.reader()); m_compiler = make_unique<CompilerStack>(m_fileReader.reader());
SourceReferenceFormatter formatter(serr(false), *m_compiler, coloredOutput(m_options), m_options.formatting.withErrorIds); if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport)
try
{ {
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); else
m_compiler->enableEvmBytecodeGeneration( {
m_options.compiler.estimateGas || SourceReferenceFormatter
m_options.compiler.outputs.asm_ || formatter(serr(false), *m_compiler, coloredOutput(m_options), m_options.formatting.withErrorIds);
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()); try
if (m_options.input.mode == InputMode::CompilerWithASTImport)
{ {
try if (m_options.metadata.literalSources)
{ m_compiler->useMetadataLiteralSources(true);
m_compiler->importASTs(parseAstFromInput()); 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()); m_compiler->importASTs(parseAstFromInput());
astAssert(false, "Analysis of the AST failed");
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; m_compiler->setSources(m_fileReader.sourceCodes());
return false; 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; m_hasOutput = true;
formatter.printErrorInformation(*error); formatter.printExceptionInformation(_exception, "Compiler error");
return false;
} }
catch (Error const& _error)
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<errinfo_comment>(_error) << endl;
else
{ {
m_hasOutput = true; if (_error.type() == Error::Type::DocstringParsingError)
formatter.printExceptionInformation(_error, _error.typeName()); serr() << "Documentation parsing error: " << *boost::get_error_info<errinfo_comment>(_error) << endl;
} else
{
m_hasOutput = true;
formatter.printExceptionInformation(_error, _error.typeName());
}
return false; return false;
}
} }
return true; return true;
@ -763,7 +771,11 @@ bool CommandLineInterface::compile()
void CommandLineInterface::handleCombinedJSON() 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()) if (!m_options.compiler.combinedJsonRequests.has_value())
return; return;
@ -1095,68 +1107,77 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
void CommandLineInterface::outputCompilationResults() 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(); handleCombinedJSON();
// do we need AST output? if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport)
handleAst();
if (
!m_compiler->compilationSuccessful() &&
m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful
)
{ {
serr() << endl << "Compilation halted after AST generation due to errors." << endl; handleBytecode("");
return;
} }
else
vector<string> contracts = m_compiler->contractNames();
for (string const& contract: contracts)
{ {
if (needsHumanTargetedStdout(m_options)) // do we need AST output?
sout() << endl << "======= " << contract << " =======" << endl; handleAst();
// do we need EVM assembly? if (!m_compiler->compilationSuccessful()
if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) && m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful)
{ {
string ret; serr() << endl << "Compilation halted after AST generation due to errors." << endl;
if (m_options.compiler.outputs.asmJson) return;
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;
}
} }
if (m_options.compiler.estimateGas) vector<string> contracts = m_compiler->contractNames();
handleGasEstimation(contract); for (string const& contract: contracts)
{
if (needsHumanTargetedStdout(m_options))
sout() << endl << "======= " << contract << " =======" << endl;
handleBytecode(contract); // do we need EVM assembly?
handleIR(contract); if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson)
handleIROptimized(contract); {
handleEwasm(contract); string ret;
handleSignatureHashes(contract); if (m_options.compiler.outputs.asmJson)
handleMetadata(contract); ret = jsonPrettyPrint(removeNullMembers(m_compiler->assemblyJSON(contract)));
handleABI(contract); else
handleStorageLayout(contract); ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes());
handleNatspec(true, contract);
handleNatspec(false, contract);
} // end of contracts iteration
if (!m_hasOutput) if (!m_options.output.dir.empty())
{ {
if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret);
sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_options.output.dir << "." << endl; }
else else
serr() << "Compiler run successful, no output requested." << endl; {
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;
}
} }
} }

View File

@ -46,6 +46,7 @@ static string const g_strAllowPaths = "allow-paths";
static string const g_strBasePath = "base-path"; static string const g_strBasePath = "base-path";
static string const g_strIncludePath = "include-path"; static string const g_strIncludePath = "include-path";
static string const g_strAssemble = "assemble"; 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_strCombinedJson = "combined-json";
static string const g_strErrorRecovery = "error-recovery"; static string const g_strErrorRecovery = "error-recovery";
static string const g_strEVM = "evm"; static string const g_strEVM = "evm";
@ -134,6 +135,7 @@ static map<InputMode, string> const g_inputModeName = {
{InputMode::Compiler, "compiler"}, {InputMode::Compiler, "compiler"},
{InputMode::CompilerWithASTImport, "compiler (AST import)"}, {InputMode::CompilerWithASTImport, "compiler (AST import)"},
{InputMode::Assembler, "assembler"}, {InputMode::Assembler, "assembler"},
{InputMode::CompilerWithEvmAssemblyJsonImport, "assembler (EVM ASM JSON import)"},
{InputMode::StandardJson, "standard JSON"}, {InputMode::StandardJson, "standard JSON"},
{InputMode::Linker, "linker"}, {InputMode::Linker, "linker"},
}; };
@ -321,7 +323,20 @@ bool CommandLineParser::parseInputPathsAndRemappings()
m_options.input.paths.insert(positionalArg); 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)) 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: case InputMode::Version:
solAssert(false); solAssert(false);
case InputMode::Compiler: case InputMode::Compiler:
case InputMode::CompilerWithEvmAssemblyJsonImport:
case InputMode::CompilerWithASTImport: case InputMode::CompilerWithASTImport:
return contains(compilerModeOutputs, _outputName); return contains(compilerModeOutputs, _outputName);
case InputMode::Assembler: case InputMode::Assembler:
@ -641,8 +657,12 @@ General Information)").c_str(),
( (
g_strImportAst.c_str(), g_strImportAst.c_str(),
("Import ASTs to be compiled, assumes input holds the AST in compact JSON format. " ("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 " "Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by "
"--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str() "--" + 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); desc.add(alternativeInputModes);
@ -879,6 +899,7 @@ bool CommandLineParser::processArgs()
g_strStrictAssembly, g_strStrictAssembly,
g_strYul, g_strYul,
g_strImportAst, g_strImportAst,
g_strImportEvmAssemblerJson,
})) }))
return false; return false;
@ -892,6 +913,8 @@ bool CommandLineParser::processArgs()
m_options.input.mode = InputMode::StandardJson; 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) 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; 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) else if (m_args.count(g_strLink) > 0)
m_options.input.mode = InputMode::Linker; m_options.input.mode = InputMode::Linker;
else if (m_args.count(g_strImportAst) > 0) else if (m_args.count(g_strImportAst) > 0)
@ -946,8 +969,8 @@ bool CommandLineParser::processArgs()
if ( if (
m_options.input.mode != InputMode::Compiler && m_options.input.mode != InputMode::Compiler &&
m_options.input.mode != InputMode::CompilerWithASTImport && 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()) if (!m_args[g_strOptimizeRuns].defaulted())
{ {
@ -1127,16 +1150,18 @@ bool CommandLineParser::processArgs()
m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>(); m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>();
} }
if (m_options.input.mode == InputMode::Assembler) if (m_options.input.mode == InputMode::Assembler ||
m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport)
{ {
vector<string> const nonAssemblyModeOptions = { vector<string> nonAssemblyModeOptions = {
// TODO: The list is not complete. Add more. // TODO: The list is not complete. Add more.
g_strOutputDir, g_strOutputDir,
g_strGas, g_strGas,
g_strCombinedJson,
g_strOptimizeYul, g_strOptimizeYul,
g_strNoOptimizeYul, g_strNoOptimizeYul,
}; };
if (m_options.input.mode == InputMode::Assembler)
nonAssemblyModeOptions.emplace_back(g_strCombinedJson);
if (countEnabledOptions(nonAssemblyModeOptions) >= 1) if (countEnabledOptions(nonAssemblyModeOptions) >= 1)
{ {
auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; }; 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; serr() << "and automatic translation is not available." << endl;
return false; return false;
} }
serr() << if (m_options.input.mode == InputMode::Assembler)
"Warning: Yul is still experimental. Please use the output with care." << serr() <<
endl; "Warning: Yul is still experimental. Please use the output with care." <<
endl;
return true; return true;
} }

View File

@ -56,6 +56,7 @@ enum class InputMode
StandardJson, StandardJson,
Linker, Linker,
Assembler, Assembler,
CompilerWithEvmAssemblyJsonImport,
}; };
struct CompilerOutputs struct CompilerOutputs

View File

@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes)
}; };
string expectedMessage = string expectedMessage =
"The following options are mutually exclusive: " "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"; "Select at most one.\n";
for (string const& mode1: inputModeOptions) for (string const& mode1: inputModeOptions)