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

This commit is contained in:
Alexander Arlt 2022-02-21 18:56:05 -05:00
parent 3f6beaa0ad
commit 43fe553ff0
7 changed files with 173 additions and 73 deletions

View File

@ -414,6 +414,28 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
storeContractDefinitions();
}
void CompilerStack::importEvmAssemblyJson(std::map<std::string, Json::Value> 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<CharStream>(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<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
try
if (!m_evmAssemblyJson.empty())
{
}
else
{
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(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<langutil::errinfo_sourceLocation>(_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<langutil::errinfo_sourceLocation>(_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;

View File

@ -222,6 +222,10 @@ public:
/// Will throw errors if the import fails
void importASTs(std::map<std::string, Json::Value> 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<std::string, Json::Value> 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<std::string const, Source> m_sources;
// if imported, store AST-JSONS for each filename
std::map<std::string, Json::Value> m_sourceJsons;
std::map<std::string, Json::Value> m_evmAssemblyJson;
std::vector<std::string> m_unhandledSMTLib2Queries;
std::map<util::h256, std::string> m_smtlib2Responses;
std::shared_ptr<GlobalContext> m_globalContext;

View File

@ -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;
};
}

View File

@ -555,6 +555,25 @@ map<string, Json::Value> CommandLineInterface::parseAstFromInput()
return sourceJsons;
}
map<string, Json::Value> CommandLineInterface::parseEvmAssemblyJsonFromInput()
{
solAssert(m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport, "");
solAssert(m_fileReader.sourceUnits().size() == 1, "");
map<string, Json::Value> 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<CompilerStack>(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();

View File

@ -115,6 +115,8 @@ private:
/// or standard-json output
std::map<std::string, Json::Value> parseAstFromInput();
std::map<std::string, Json::Value> parseEvmAssemblyJsonFromInput();
/// Create a file in the given directory
/// @arg _fileName the name of the file
/// @arg _data to be written

View File

@ -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<InputMode, string> 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<string> 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<string>()->value_name(util::joinHumanReadable(g_revertStringsArgs, ",")),
po::value<string>()->value_name(joinHumanReadable(g_revertStringsArgs, ",")),
"Strip revert (and require) reason strings or add additional debugging information."
)
(
g_strDebugInfo.c_str(),
po::value<string>()->default_value(util::toString(DebugInfoSelection::Default())),
po::value<string>()->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<string>()->value_name(util::joinHumanReadable(g_machineArgs, ",")),
po::value<string>()->value_name(joinHumanReadable(g_machineArgs, ",")),
"Target machine in assembly or Yul mode."
)
(
g_strYulDialect.c_str(),
po::value<string>()->value_name(util::joinHumanReadable(g_yulDialectArgs, ",")),
po::value<string>()->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<string>()->value_name(util::joinHumanReadable(CombinedJsonRequests::componentMap() | ranges::views::keys, ",")),
po::value<string>()->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<string>()->value_name(util::joinHumanReadable(g_metadataHashArgs, ",")),
po::value<string>()->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<uint32_t>();
}
@ -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<string> const& _optionNames
string CommandLineParser::joinOptionNames(vector<string> const& _optionNames, string _separator)
{
return util::joinHumanReadable(
return joinHumanReadable(
_optionNames | ranges::views::transform([](string const& _option){ return "--" + _option; }),
_separator
);

View File

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