Merge pull request #2146 from ethereum/jsonio-target-selection

Support target artifact selection in JSON I/O
This commit is contained in:
Alex Beregszaszi 2017-11-29 21:09:03 +00:00 committed by GitHub
commit ffb3a3c06c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 19 deletions

View File

@ -4,6 +4,7 @@ Features:
* Allow constant variables to be used as array length
* Code Generator: New ABI decoder which supports structs and arbitrarily nested
arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``.
* Standard JSON: Support the ``outputSelection`` field for selective compilation of target artifacts.
* Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature.
* Type Checker: Improve address checksum warning.
* Type Checker: More detailed errors for invalid array lengths (such as division by zero).

View File

@ -138,7 +138,7 @@ Input Description
// ewasm.wasm - eWASM binary format (not supported atm)
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
// target part of that output.
// target part of that output. Additionally, `*` can be used as a wildcard to request everything.
//
outputSelection: {
// Enable the metadata and bytecode outputs of every single contract.

View File

@ -131,6 +131,61 @@ StringMap createSourceList(Json::Value const& _input)
return sources;
}
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
{
for (auto const& artifact: _outputSelection)
/// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
if (artifact == "*" || artifact == _artifact)
return true;
return false;
}
///
/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename,
/// the second level is the contract name and the value is an array of artifact names to be requested for that contract.
/// @a _file is the current file
/// @a _contract is the current contract
/// @a _artifact is the current artifact name
///
/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract.
///
/// In @a _outputSelection the use of '*' as a wildcard is permitted.
///
/// @TODO optimise this. Perhaps flatten the structure upfront.
///
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact)
{
if (!_outputSelection.isObject())
return false;
for (auto const& file: { _file, string("*") })
if (_outputSelection.isMember(file) && _outputSelection[file].isObject())
{
/// For SourceUnit-level targets (such as AST) only allow empty name, otherwise
/// for Contract-level targets try both contract name and wildcard
vector<string> contracts{ _contract };
if (!_contract.empty())
contracts.push_back("*");
for (auto const& contract: contracts)
if (
_outputSelection[file].isMember(contract) &&
_outputSelection[file][contract].isArray() &&
isArtifactRequested(_outputSelection[file][contract], _artifact)
)
return true;
}
return false;
}
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts)
{
for (auto const& artifact: _artifacts)
if (isArtifactRequested(_outputSelection, _file, _contract, artifact))
return true;
return false;
}
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@ -396,8 +451,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
if (isArtifactRequested(outputSelection, sourceName, "", "ast"))
sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST"))
sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult;
}
@ -411,28 +468,48 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
contractData["abi"] = m_compilerStack.contractABI(contractName);
contractData["metadata"] = m_compilerStack.metadata(contractName);
contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
if (isArtifactRequested(outputSelection, file, name, "abi"))
contractData["abi"] = m_compilerStack.contractABI(contractName);
if (isArtifactRequested(outputSelection, file, name, "metadata"))
contractData["metadata"] = m_compilerStack.metadata(contractName);
if (isArtifactRequested(outputSelection, file, name, "userdoc"))
contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
if (isArtifactRequested(outputSelection, file, name, "devdoc"))
contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
// EVM
Json::Value evmData(Json::objectValue);
// @TODO: add ir
evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
if (isArtifactRequested(outputSelection, file, name, "evm.assembly"))
evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly"))
evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers"))
evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates"))
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
evmData["bytecode"] = collectEVMObject(
m_compilerStack.object(contractName),
m_compilerStack.sourceMapping(contractName)
);
if (isArtifactRequested(
outputSelection,
file,
name,
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
))
evmData["bytecode"] = collectEVMObject(
m_compilerStack.object(contractName),
m_compilerStack.sourceMapping(contractName)
);
evmData["deployedBytecode"] = collectEVMObject(
m_compilerStack.runtimeObject(contractName),
m_compilerStack.runtimeSourceMapping(contractName)
);
if (isArtifactRequested(
outputSelection,
file,
name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
))
evmData["deployedBytecode"] = collectEVMObject(
m_compilerStack.runtimeObject(contractName),
m_compilerStack.runtimeSourceMapping(contractName)
);
contractData["evm"] = evmData;

View File

@ -128,6 +128,11 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
input["settings"]["optimizer"]["enabled"] = _optimize;
input["settings"]["optimizer"]["runs"] = 200;
// Enable all SourceUnit-level outputs.
input["settings"]["outputSelection"]["*"][""][0] = "*";
// Enable all Contract-level outputs.
input["settings"]["outputSelection"]["*"]["*"][0] = "*";
StandardCompiler compiler(wrapReadCallback(_readCallback));
Json::Value ret = compiler.compile(input);

View File

@ -179,6 +179,14 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
"fileA": {
"content": "contract A { }"
}
},
"settings": {
"outputSelection": {
"fileA": {
"A": [ "abi", "devdoc", "userdoc", "evm.bytecode", "evm.assembly", "evm.gasEstimates", "metadata" ],
"": [ "legacyAST" ]
}
}
}
}
)";