Refactor StandardCompiler to split into input parsing/validating and compilation steps.

This commit is contained in:
chriseth 2019-03-07 15:35:31 +01:00
parent 0eb799424d
commit 8b20ecb558
2 changed files with 111 additions and 82 deletions

View File

@ -127,16 +127,6 @@ bool hashMatchesContent(string const& _hash, string const& _content)
} }
} }
StringMap createSourceList(Json::Value const& _input)
{
StringMap sources;
Json::Value const& jsonSources = _input["sources"];
if (jsonSources.isObject())
for (auto const& sourceName: jsonSources.getMemberNames())
sources[sourceName] = jsonSources[sourceName]["content"].asString();
return sources;
}
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
{ {
for (auto const& artifact: _outputSelection) for (auto const& artifact: _outputSelection)
@ -365,9 +355,9 @@ boost::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSele
return boost::none; return boost::none;
} }
} /// Validates the optimizer settings and returns them in a parsed object.
/// On error returns the json-formatted error message.
boost::optional<Json::Value> StandardCompiler::parseOptimizerSettings(Json::Value const& _jsonInput) boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput)
{ {
if (auto result = checkOptimizerKeys(_jsonInput)) if (auto result = checkOptimizerKeys(_jsonInput))
return *result; return *result;
@ -417,14 +407,14 @@ boost::optional<Json::Value> StandardCompiler::parseOptimizerSettings(Json::Valu
return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet."); return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet.");
} }
} }
m_compilerStack.setOptimiserSettings(std::move(settings)); return std::move(settings);
return {};
} }
}
Json::Value StandardCompiler::compileInternal(Json::Value const& _input) boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler::parseInput(Json::Value const& _input)
{ {
m_compilerStack.reset(false); InputsAndSettings ret;
if (!_input.isObject()) if (!_input.isObject())
return formatFatalError("JSONError", "Input is not a JSON object."); return formatFatalError("JSONError", "Input is not a JSON object.");
@ -443,7 +433,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
if (sources.empty()) if (sources.empty())
return formatFatalError("JSONError", "No input sources specified."); return formatFatalError("JSONError", "No input sources specified.");
Json::Value errors = Json::arrayValue; ret.errors = Json::arrayValue;
for (auto const& sourceName: sources.getMemberNames()) for (auto const& sourceName: sources.getMemberNames())
{ {
@ -459,14 +449,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{ {
string content = sources[sourceName]["content"].asString(); string content = sources[sourceName]["content"].asString();
if (!hash.empty() && !hashMatchesContent(hash, content)) if (!hash.empty() && !hashMatchesContent(hash, content))
errors.append(formatError( ret.errors.append(formatError(
false, false,
"IOError", "IOError",
"general", "general",
"Mismatch between content and supplied hash for \"" + sourceName + "\"" "Mismatch between content and supplied hash for \"" + sourceName + "\""
)); ));
else else
m_compilerStack.addSource(sourceName, content); ret.sources[sourceName] = content;
} }
else if (sources[sourceName]["urls"].isArray()) else if (sources[sourceName]["urls"].isArray())
{ {
@ -484,7 +474,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
if (result.success) if (result.success)
{ {
if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage))
errors.append(formatError( ret.errors.append(formatError(
false, false,
"IOError", "IOError",
"general", "general",
@ -492,7 +482,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
)); ));
else else
{ {
m_compilerStack.addSource(sourceName, result.responseOrErrorMessage); ret.sources[sourceName] = result.responseOrErrorMessage;
found = true; found = true;
break; break;
} }
@ -504,7 +494,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
for (auto const& failure: failures) for (auto const& failure: failures)
{ {
/// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors.
errors.append(formatError( ret.errors.append(formatError(
found ? true : false, found ? true : false,
"IOError", "IOError",
"general", "general",
@ -547,7 +537,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
"\"smtlib2Responses." + hashString + "\" must be a string." "\"smtlib2Responses." + hashString + "\" must be a string."
); );
m_compilerStack.addSMTLib2Response(hash, smtlib2Responses[hashString].asString()); ret.smtLib2Responses[hash] = smtlib2Responses[hashString].asString();
} }
} }
} }
@ -564,29 +554,31 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
boost::optional<langutil::EVMVersion> version = langutil::EVMVersion::fromString(settings["evmVersion"].asString()); boost::optional<langutil::EVMVersion> version = langutil::EVMVersion::fromString(settings["evmVersion"].asString());
if (!version) if (!version)
return formatFatalError("JSONError", "Invalid EVM version requested."); return formatFatalError("JSONError", "Invalid EVM version requested.");
m_compilerStack.setEVMVersion(*version); ret.evmVersion = *version;
} }
if (settings.isMember("remappings") && !settings["remappings"].isArray()) if (settings.isMember("remappings") && !settings["remappings"].isArray())
return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings.");
vector<CompilerStack::Remapping> remappings;
for (auto const& remapping: settings.get("remappings", Json::Value())) for (auto const& remapping: settings.get("remappings", Json::Value()))
{ {
if (!remapping.isString()) if (!remapping.isString())
return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings"); return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings");
if (auto r = CompilerStack::parseRemapping(remapping.asString())) if (auto r = CompilerStack::parseRemapping(remapping.asString()))
remappings.emplace_back(std::move(*r)); ret.remappings.emplace_back(std::move(*r));
else else
return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\"");
} }
m_compilerStack.setRemappings(remappings);
if (settings.isMember("optimizer")) if (settings.isMember("optimizer"))
if (auto result = parseOptimizerSettings(settings["optimizer"])) {
return *result; auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]);
if (optimiserSettings.type() == typeid(Json::Value))
return boost::get<Json::Value>(std::move(optimiserSettings)); // was an error
else
ret.optimiserSettings = boost::get<OptimiserSettings>(std::move(optimiserSettings));
}
map<string, h160> libraries;
Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue));
if (!jsonLibraries.isObject()) if (!jsonLibraries.isObject())
return formatFatalError("JSONError", "\"libraries\" is not a JSON object."); return formatFatalError("JSONError", "\"libraries\" is not a JSON object.");
@ -616,7 +608,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
try try
{ {
// @TODO use libraries only for the given source // @TODO use libraries only for the given source
libraries[library] = h160(address); ret.libraries[library] = h160(address);
} }
catch (dev::BadHexCharacter const&) catch (dev::BadHexCharacter const&)
{ {
@ -627,32 +619,52 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
} }
} }
} }
m_compilerStack.setLibraries(libraries);
Json::Value metadataSettings = settings.get("metadata", Json::Value()); Json::Value metadataSettings = settings.get("metadata", Json::Value());
if (auto result = checkMetadataKeys(metadataSettings)) if (auto result = checkMetadataKeys(metadataSettings))
return *result; return *result;
m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool();
Json::Value outputSelection = settings.get("outputSelection", Json::Value()); Json::Value outputSelection = settings.get("outputSelection", Json::Value());
if (auto jsonError = checkOutputSelection(outputSelection)) if (auto jsonError = checkOutputSelection(outputSelection))
return *jsonError; return *jsonError;
m_compilerStack.setRequestedContractNames(requestedContractNames(outputSelection)); ret.outputSelection = std::move(outputSelection);
bool const binariesRequested = isBinaryRequested(outputSelection); return std::move(ret);
}
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings)
{
CompilerStack compilerStack(m_readFile);
StringMap sourceList = std::move(_inputsAndSettings.sources);
for (auto const& source: sourceList)
compilerStack.addSource(source.first, source.second);
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
compilerStack.setRemappings(_inputsAndSettings.remappings);
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
compilerStack.setLibraries(_inputsAndSettings.libraries);
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
Json::Value errors = std::move(_inputsAndSettings.errors);
bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection);
try try
{ {
if (binariesRequested) if (binariesRequested)
m_compilerStack.compile(); compilerStack.compile();
else else
m_compilerStack.parseAndAnalyze(); compilerStack.parseAndAnalyze();
for (auto const& error: m_compilerStack.errors()) for (auto const& error: compilerStack.errors())
{ {
Error const& err = dynamic_cast<Error const&>(*error); Error const& err = dynamic_cast<Error const&>(*error);
@ -735,8 +747,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
)); ));
} }
bool const analysisSuccess = m_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; bool const analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful;
bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful;
/// Inconsistent state - stop here to receive error reports from users /// Inconsistent state - stop here to receive error reports from users
if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty()) if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty())
@ -745,27 +757,27 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
Json::Value output = Json::objectValue; Json::Value output = Json::objectValue;
if (errors.size() > 0) if (errors.size() > 0)
output["errors"] = errors; output["errors"] = std::move(errors);
if (!m_compilerStack.unhandledSMTLib2Queries().empty()) if (!compilerStack.unhandledSMTLib2Queries().empty())
for (string const& query: m_compilerStack.unhandledSMTLib2Queries()) for (string const& query: compilerStack.unhandledSMTLib2Queries())
output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query; output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query;
output["sources"] = Json::objectValue; output["sources"] = Json::objectValue;
unsigned sourceIndex = 0; unsigned sourceIndex = 0;
for (string const& sourceName: analysisSuccess ? m_compilerStack.sourceNames() : vector<string>()) for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector<string>())
{ {
Json::Value sourceResult = Json::objectValue; Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++; sourceResult["id"] = sourceIndex++;
if (isArtifactRequested(outputSelection, sourceName, "", "ast")) if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast"))
sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST")) if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST"))
sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult; output["sources"][sourceName] = sourceResult;
} }
Json::Value contractsOutput = Json::objectValue; Json::Value contractsOutput = Json::objectValue;
for (string const& contractName: analysisSuccess ? m_compilerStack.contractNames() : vector<string>()) for (string const& contractName: analysisSuccess ? compilerStack.contractNames() : vector<string>())
{ {
size_t colon = contractName.rfind(':'); size_t colon = contractName.rfind(':');
solAssert(colon != string::npos, ""); solAssert(colon != string::npos, "");
@ -774,47 +786,47 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
// ABI, documentation and metadata // ABI, documentation and metadata
Json::Value contractData(Json::objectValue); Json::Value contractData(Json::objectValue);
if (isArtifactRequested(outputSelection, file, name, "abi")) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi"))
contractData["abi"] = m_compilerStack.contractABI(contractName); contractData["abi"] = compilerStack.contractABI(contractName);
if (isArtifactRequested(outputSelection, file, name, "metadata")) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata"))
contractData["metadata"] = m_compilerStack.metadata(contractName); contractData["metadata"] = compilerStack.metadata(contractName);
if (isArtifactRequested(outputSelection, file, name, "userdoc")) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc"))
contractData["userdoc"] = m_compilerStack.natspecUser(contractName); contractData["userdoc"] = compilerStack.natspecUser(contractName);
if (isArtifactRequested(outputSelection, file, name, "devdoc")) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc"))
contractData["devdoc"] = m_compilerStack.natspecDev(contractName); contractData["devdoc"] = compilerStack.natspecDev(contractName);
// EVM // EVM
Json::Value evmData(Json::objectValue); Json::Value evmData(Json::objectValue);
// @TODO: add ir // @TODO: add ir
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.assembly")) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly"))
evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly"))
evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList);
if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers"))
evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates"))
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); evmData["gasEstimates"] = compilerStack.gasEstimates(contractName);
if (compilationSuccess && isArtifactRequested( if (compilationSuccess && isArtifactRequested(
outputSelection, _inputsAndSettings.outputSelection,
file, file,
name, name,
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
)) ))
evmData["bytecode"] = collectEVMObject( evmData["bytecode"] = collectEVMObject(
m_compilerStack.object(contractName), compilerStack.object(contractName),
m_compilerStack.sourceMapping(contractName) compilerStack.sourceMapping(contractName)
); );
if (compilationSuccess && isArtifactRequested( if (compilationSuccess && isArtifactRequested(
outputSelection, _inputsAndSettings.outputSelection,
file, file,
name, name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
)) ))
evmData["deployedBytecode"] = collectEVMObject( evmData["deployedBytecode"] = collectEVMObject(
m_compilerStack.runtimeObject(contractName), compilerStack.runtimeObject(contractName),
m_compilerStack.runtimeSourceMapping(contractName) compilerStack.runtimeSourceMapping(contractName)
); );
if (!evmData.empty()) if (!evmData.empty())
@ -837,7 +849,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
{ {
try try
{ {
return compileInternal(_input); auto parsed = parseInput(_input);
if (parsed.type() == typeid(InputsAndSettings))
return compileSolidity(boost::get<InputsAndSettings>(std::move(parsed)));
else
return boost::get<Json::Value>(std::move(parsed));
} }
catch (Json::LogicError const& _exception) catch (Json::LogicError const& _exception)
{ {
@ -849,11 +865,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
} }
catch (Exception const& _exception) catch (Exception const& _exception)
{ {
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception)); return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::diagnostic_information(_exception));
} }
catch (...) catch (...)
{ {
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal"); return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile");
} }
} }

View File

@ -25,6 +25,7 @@
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/variant.hpp>
namespace dev namespace dev
{ {
@ -42,8 +43,8 @@ public:
/// Creates a new StandardCompiler. /// Creates a new StandardCompiler.
/// @param _readFile callback to used to read files for import statements. Must return /// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions. /// and must not emit exceptions.
explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()) explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()):
: m_compilerStack(_readFile), m_readFile(_readFile) m_readFile(_readFile)
{ {
} }
@ -55,13 +56,25 @@ public:
std::string compile(std::string const& _input) noexcept; std::string compile(std::string const& _input) noexcept;
private: private:
/// Validaes and applies the optimizer settings. struct InputsAndSettings
/// On error returns the json-formatted error message. {
boost::optional<Json::Value> parseOptimizerSettings(Json::Value const& _settings); Json::Value errors;
std::map<std::string, std::string> sources;
std::map<h256, std::string> smtLib2Responses;
langutil::EVMVersion evmVersion;
std::vector<CompilerStack::Remapping> remappings;
OptimiserSettings optimiserSettings = OptimiserSettings::minimal();
std::map<std::string, h160> libraries;
bool metadataLiteralSources = false;
Json::Value outputSelection;
};
Json::Value compileInternal(Json::Value const& _input); /// Parses the input json (and potentially invokes the read callback) and either returns
/// it in condensed form or an error as a json object.
boost::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
Json::Value compileSolidity(InputsAndSettings _inputsAndSettings);
CompilerStack m_compilerStack;
ReadCallback::Callback m_readFile; ReadCallback::Callback m_readFile;
}; };