mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1639 from ethereum/json-interface-api
Support "standardised" JSON compiler input/output
This commit is contained in:
commit
2ccbc088f2
@ -1,7 +1,10 @@
|
||||
### 0.4.11 (unreleased)
|
||||
|
||||
Features:
|
||||
* Implement the Standard JSON Input / Output API
|
||||
* Support ``interface`` contracts.
|
||||
* C API (``jsonCompiler``): Add the ``compileStandard()`` method to process a Standard JSON I/O.
|
||||
* Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O.
|
||||
* Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the
|
||||
path(s) of the supplied source file(s) is always trusted.
|
||||
|
||||
|
@ -121,7 +121,8 @@ Input Description
|
||||
//
|
||||
// The available output types are as follows:
|
||||
// abi - ABI
|
||||
// ast - AST of all source files
|
||||
// ast - AST of all source files (not supported atm)
|
||||
// legacyAST - legacy AST of all source files
|
||||
// why3 - Why3 translated output
|
||||
// devdoc - Developer documentation (natspec)
|
||||
// userdoc - User documentation (natspec)
|
||||
@ -155,9 +156,9 @@ Input Description
|
||||
"*": {
|
||||
"*": [ "evm.sourceMap" ]
|
||||
},
|
||||
// Enable the AST and Why3 output of every single file.
|
||||
// Enable the legacy AST and Why3 output of every single file.
|
||||
"*": {
|
||||
"": [ "ast", "why3" ]
|
||||
"": [ "legacyAST", "why3" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,7 +198,9 @@ Output Description
|
||||
// Identifier (used in source maps)
|
||||
id: 1,
|
||||
// The AST object
|
||||
ast: {}
|
||||
ast: {},
|
||||
// The legacy AST object
|
||||
legacyAST: {}
|
||||
}
|
||||
},
|
||||
// This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
|
||||
|
418
libsolidity/interface/StandardCompiler.cpp
Normal file
418
libsolidity/interface/StandardCompiler.cpp
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Alex Beregszaszi
|
||||
* @date 2016
|
||||
* Standard JSON compiler interface.
|
||||
*/
|
||||
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolidity/interface/SourceReferenceFormatter.h>
|
||||
#include <libsolidity/ast/ASTJsonConverter.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace {
|
||||
|
||||
Json::Value formatError(
|
||||
bool _warning,
|
||||
string const& _type,
|
||||
string const& _component,
|
||||
string const& _message,
|
||||
string const& _formattedMessage = "",
|
||||
Json::Value const& _sourceLocation = Json::Value()
|
||||
)
|
||||
{
|
||||
Json::Value error = Json::objectValue;
|
||||
error["type"] = _type;
|
||||
error["component"] = _component;
|
||||
error["severity"] = _warning ? "warning" : "error";
|
||||
error["message"] = _message;
|
||||
error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message;
|
||||
if (_sourceLocation.isObject())
|
||||
error["sourceLocation"] = _sourceLocation;
|
||||
return error;
|
||||
}
|
||||
|
||||
Json::Value formatFatalError(string const& _type, string const& _message)
|
||||
{
|
||||
Json::Value output = Json::objectValue;
|
||||
output["errors"] = Json::arrayValue;
|
||||
output["errors"].append(formatError(false, _type, "general", _message));
|
||||
return output;
|
||||
}
|
||||
|
||||
Json::Value formatErrorWithException(
|
||||
Exception const& _exception,
|
||||
bool const& _warning,
|
||||
string const& _type,
|
||||
string const& _component,
|
||||
string const& _message,
|
||||
function<Scanner const&(string const&)> const& _scannerFromSourceName
|
||||
)
|
||||
{
|
||||
string message;
|
||||
string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _message, _scannerFromSourceName);
|
||||
|
||||
// NOTE: the below is partially a copy from SourceReferenceFormatter
|
||||
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
|
||||
|
||||
if (string const* description = boost::get_error_info<errinfo_comment>(_exception))
|
||||
message = ((_message.length() > 0) ? (_message + ":") : "") + *description;
|
||||
else
|
||||
message = _message;
|
||||
|
||||
if (location && location->sourceName)
|
||||
{
|
||||
Json::Value sourceLocation = Json::objectValue;
|
||||
sourceLocation["file"] = *location->sourceName;
|
||||
sourceLocation["start"] = location->start;
|
||||
sourceLocation["end"] = location->end;
|
||||
}
|
||||
|
||||
return formatError(_warning, _type, _component, message, formattedMessage, location);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Json::Value methodIdentifiers(ContractDefinition const& _contract)
|
||||
{
|
||||
Json::Value methodIdentifiers(Json::objectValue);
|
||||
for (auto const& it: _contract.interfaceFunctions())
|
||||
methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref());
|
||||
return methodIdentifiers;
|
||||
}
|
||||
|
||||
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
|
||||
{
|
||||
Json::Value ret(Json::objectValue);
|
||||
|
||||
for (auto const& ref: linkReferences)
|
||||
{
|
||||
string const& fullname = ref.second;
|
||||
size_t colon = fullname.find(':');
|
||||
solAssert(colon != string::npos, "");
|
||||
string file = fullname.substr(0, colon);
|
||||
string name = fullname.substr(colon + 1);
|
||||
|
||||
Json::Value fileObject = ret.get(file, Json::objectValue);
|
||||
Json::Value libraryArray = fileObject.get(name, Json::arrayValue);
|
||||
|
||||
Json::Value entry = Json::objectValue;
|
||||
entry["start"] = Json::UInt(ref.first);
|
||||
entry["length"] = 20;
|
||||
|
||||
libraryArray.append(entry);
|
||||
fileObject[name] = libraryArray;
|
||||
ret[file] = fileObject;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _sourceMap)
|
||||
{
|
||||
Json::Value output = Json::objectValue;
|
||||
output["object"] = _object.toHex();
|
||||
output["opcodes"] = solidity::disassemble(_object.bytecode);
|
||||
output["sourceMap"] = _sourceMap ? *_sourceMap : "";
|
||||
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
{
|
||||
m_compilerStack.reset(false);
|
||||
|
||||
if (!_input.isObject())
|
||||
return formatFatalError("JSONError", "Input is not a JSON object.");
|
||||
|
||||
if (_input["language"] != "Solidity")
|
||||
return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language.");
|
||||
|
||||
Json::Value const& sources = _input["sources"];
|
||||
if (!sources)
|
||||
return formatFatalError("JSONError", "No input sources specified.");
|
||||
|
||||
for (auto const& sourceName: sources.getMemberNames())
|
||||
if (sources[sourceName]["content"].isString())
|
||||
m_compilerStack.addSource(sourceName, sources[sourceName]["content"].asString());
|
||||
else if (sources[sourceName]["urls"].isArray())
|
||||
return formatFatalError("UnimplementedFeatureError", "Input URLs not supported yet.");
|
||||
else
|
||||
return formatFatalError("JSONError", "Invalid input source specified.");
|
||||
|
||||
Json::Value const& settings = _input.get("settings", Json::Value());
|
||||
|
||||
vector<string> remappings;
|
||||
for (auto const& remapping: settings.get("remappings", Json::Value()))
|
||||
remappings.push_back(remapping.asString());
|
||||
m_compilerStack.setRemappings(remappings);
|
||||
|
||||
Json::Value optimizerSettings = settings.get("optimizer", Json::Value());
|
||||
bool optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool();
|
||||
unsigned optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt();
|
||||
|
||||
map<string, h160> libraries;
|
||||
Json::Value jsonLibraries = settings.get("libraries", Json::Value());
|
||||
for (auto const& sourceName: jsonLibraries.getMemberNames())
|
||||
{
|
||||
auto const& jsonSourceName = jsonLibraries[sourceName];
|
||||
for (auto const& library: jsonSourceName.getMemberNames())
|
||||
// @TODO use libraries only for the given source
|
||||
libraries[library] = h160(jsonSourceName[library].asString());
|
||||
}
|
||||
|
||||
Json::Value metadataSettings = settings.get("metadata", Json::Value());
|
||||
m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool());
|
||||
|
||||
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); };
|
||||
|
||||
Json::Value errors = Json::arrayValue;
|
||||
bool success = false;
|
||||
|
||||
try
|
||||
{
|
||||
success = m_compilerStack.compile(optimize, optimizeRuns, libraries);
|
||||
|
||||
for (auto const& error: m_compilerStack.errors())
|
||||
{
|
||||
auto err = dynamic_pointer_cast<Error const>(error);
|
||||
|
||||
errors.append(formatErrorWithException(
|
||||
*error,
|
||||
err->type() == Error::Type::Warning,
|
||||
err->typeName(),
|
||||
"general",
|
||||
"",
|
||||
scannerFromSourceName
|
||||
));
|
||||
}
|
||||
}
|
||||
catch (Error const& _error)
|
||||
{
|
||||
if (_error.type() == Error::Type::DocstringParsingError)
|
||||
errors.append(formatError(
|
||||
false,
|
||||
"DocstringParsingError",
|
||||
"general",
|
||||
"Documentation parsing error: " + *boost::get_error_info<errinfo_comment>(_error)
|
||||
));
|
||||
else
|
||||
errors.append(formatErrorWithException(
|
||||
_error,
|
||||
false,
|
||||
_error.typeName(),
|
||||
"general",
|
||||
"",
|
||||
scannerFromSourceName
|
||||
));
|
||||
}
|
||||
catch (CompilerError const& _exception)
|
||||
{
|
||||
errors.append(formatErrorWithException(
|
||||
_exception,
|
||||
false,
|
||||
"CompilerError",
|
||||
"general",
|
||||
"Compiler error (" + _exception.lineInfo() + ")",
|
||||
scannerFromSourceName
|
||||
));
|
||||
}
|
||||
catch (InternalCompilerError const& _exception)
|
||||
{
|
||||
errors.append(formatErrorWithException(
|
||||
_exception,
|
||||
false,
|
||||
"InternalCompilerError",
|
||||
"general",
|
||||
"Internal compiler error (" + _exception.lineInfo() + ")", scannerFromSourceName
|
||||
));
|
||||
}
|
||||
catch (UnimplementedFeatureError const& _exception)
|
||||
{
|
||||
errors.append(formatErrorWithException(
|
||||
_exception,
|
||||
false,
|
||||
"UnimplementedFeatureError",
|
||||
"general",
|
||||
"Unimplemented feature (" + _exception.lineInfo() + ")",
|
||||
scannerFromSourceName));
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
errors.append(formatError(
|
||||
false,
|
||||
"Exception",
|
||||
"general",
|
||||
"Exception during compilation: " + boost::diagnostic_information(_exception)
|
||||
));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
errors.append(formatError(
|
||||
false,
|
||||
"Exception",
|
||||
"general",
|
||||
"Unknown exception during compilation."
|
||||
));
|
||||
}
|
||||
|
||||
Json::Value output = Json::objectValue;
|
||||
|
||||
if (errors.size() > 0)
|
||||
output["errors"] = errors;
|
||||
|
||||
/// Inconsistent state - stop here to receive error reports from users
|
||||
if (!success && (errors.size() == 0))
|
||||
return formatFatalError("InternalCompilerError", "No error reported, but compilation failed.");
|
||||
|
||||
output["sources"] = Json::objectValue;
|
||||
unsigned sourceIndex = 0;
|
||||
for (auto const& source: m_compilerStack.sourceNames())
|
||||
{
|
||||
Json::Value sourceResult = Json::objectValue;
|
||||
sourceResult["id"] = sourceIndex++;
|
||||
sourceResult["legacyAST"] = ASTJsonConverter(m_compilerStack.ast(source), m_compilerStack.sourceIndices()).json();
|
||||
output["sources"][source] = sourceResult;
|
||||
}
|
||||
|
||||
Json::Value contractsOutput = Json::objectValue;
|
||||
for (string const& contractName: m_compilerStack.contractNames())
|
||||
{
|
||||
size_t colon = contractName.find(':');
|
||||
solAssert(colon != string::npos, "");
|
||||
string file = contractName.substr(0, colon);
|
||||
string name = contractName.substr(colon + 1);
|
||||
|
||||
// ABI, documentation and metadata
|
||||
Json::Value contractData(Json::objectValue);
|
||||
contractData["abi"] = dev::jsonCompactPrint(m_compilerStack.metadata(contractName, DocumentationType::ABIInterface));
|
||||
contractData["metadata"] = m_compilerStack.onChainMetadata(contractName);
|
||||
contractData["userdoc"] = dev::jsonCompactPrint(m_compilerStack.metadata(contractName, DocumentationType::NatspecUser));
|
||||
contractData["devdoc"] = dev::jsonCompactPrint(m_compilerStack.metadata(contractName, DocumentationType::NatspecDev));
|
||||
|
||||
// EVM
|
||||
Json::Value evmData(Json::objectValue);
|
||||
// @TODO: add ir
|
||||
ostringstream tmp;
|
||||
m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false);
|
||||
evmData["assembly"] = tmp.str();
|
||||
evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true);
|
||||
evmData["methodIdentifiers"] = methodIdentifiers(m_compilerStack.contractDefinition(contractName));
|
||||
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
|
||||
|
||||
evmData["bytecode"] = collectEVMObject(
|
||||
m_compilerStack.object(contractName),
|
||||
m_compilerStack.sourceMapping(contractName)
|
||||
);
|
||||
|
||||
evmData["deployedBytecode"] = collectEVMObject(
|
||||
m_compilerStack.runtimeObject(contractName),
|
||||
m_compilerStack.runtimeSourceMapping(contractName)
|
||||
);
|
||||
|
||||
contractData["evm"] = evmData;
|
||||
|
||||
if (!contractsOutput.isMember(file))
|
||||
contractsOutput[file] = Json::objectValue;
|
||||
|
||||
contractsOutput[file][name] = contractData;
|
||||
}
|
||||
output["contracts"] = contractsOutput;
|
||||
|
||||
{
|
||||
ErrorList formalErrors;
|
||||
if (m_compilerStack.prepareFormalAnalysis(&formalErrors))
|
||||
output["why3"] = m_compilerStack.formalTranslation();
|
||||
|
||||
for (auto const& error: formalErrors)
|
||||
{
|
||||
auto err = dynamic_pointer_cast<Error const>(error);
|
||||
|
||||
errors.append(formatErrorWithException(
|
||||
*error,
|
||||
err->type() == Error::Type::Warning,
|
||||
err->typeName(),
|
||||
"general",
|
||||
"",
|
||||
scannerFromSourceName
|
||||
));
|
||||
}
|
||||
|
||||
// FIXME!!
|
||||
if (!formalErrors.empty())
|
||||
output["errors"] = errors;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
Json::Value StandardCompiler::compile(Json::Value const& _input)
|
||||
{
|
||||
try
|
||||
{
|
||||
return compileInternal(_input);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compilerInternal");
|
||||
}
|
||||
}
|
||||
|
||||
string StandardCompiler::compile(string const& _input)
|
||||
{
|
||||
Json::Value input;
|
||||
Json::Reader reader;
|
||||
|
||||
try
|
||||
{
|
||||
if (!reader.parse(_input, input, false))
|
||||
return jsonCompactPrint(formatFatalError("JSONError", reader.getFormattedErrorMessages()));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}";
|
||||
}
|
||||
|
||||
// cout << "Input: " << input.toStyledString() << endl;
|
||||
Json::Value output = compile(input);
|
||||
// cout << "Output: " << output.toStyledString() << endl;
|
||||
|
||||
try
|
||||
{
|
||||
return jsonCompactPrint(output);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}";
|
||||
}
|
||||
}
|
61
libsolidity/interface/StandardCompiler.h
Normal file
61
libsolidity/interface/StandardCompiler.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Alex Beregszaszi
|
||||
* @date 2016
|
||||
* Standard JSON compiler interface.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
/**
|
||||
* Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput.
|
||||
* See docs/using-the-compiler#compiler-input-and-output-json-description.
|
||||
*/
|
||||
class StandardCompiler: boost::noncopyable
|
||||
{
|
||||
public:
|
||||
/// Creates a new StandardCompiler.
|
||||
/// @param _readFile callback to used to read files for import statements. Should return
|
||||
StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback())
|
||||
: m_compilerStack(_readFile)
|
||||
{
|
||||
}
|
||||
|
||||
/// Sets all input parameters according to @a _input which conforms to the standardized input
|
||||
/// format, performs compilation and returns a standardized output.
|
||||
Json::Value compile(Json::Value const& _input);
|
||||
/// Parses input as JSON and peforms the above processing steps, returning a serialized JSON
|
||||
/// output. Parsing errors are returned as regular errors.
|
||||
std::string compile(std::string const& _input);
|
||||
|
||||
private:
|
||||
Json::Value compileInternal(Json::Value const& _input);
|
||||
|
||||
CompilerStack m_compilerStack;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/interface/Exceptions.h>
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolidity/interface/SourceReferenceFormatter.h>
|
||||
#include <libsolidity/interface/GasEstimator.h>
|
||||
#include <libsolidity/formal/Why3Translator.h>
|
||||
@ -103,6 +104,7 @@ static string const g_strVersion = "version";
|
||||
static string const g_stdinFileNameStr = "<stdin>";
|
||||
static string const g_strMetadataLiteral = "metadata-literal";
|
||||
static string const g_strAllowPaths = "allow-paths";
|
||||
static string const g_strStandardJSON = "standard-json";
|
||||
|
||||
static string const g_argAbi = g_strAbi;
|
||||
static string const g_argAddStandard = g_strAddStandard;
|
||||
@ -133,6 +135,7 @@ static string const g_argVersion = g_strVersion;
|
||||
static string const g_stdinFileName = g_stdinFileNameStr;
|
||||
static string const g_argMetadataLiteral = g_strMetadataLiteral;
|
||||
static string const g_argAllowPaths = g_strAllowPaths;
|
||||
static string const g_argStandardJSON = g_strStandardJSON;
|
||||
|
||||
/// Possible arguments to for --combined-json
|
||||
static set<string> const g_combinedJsonArgs{
|
||||
@ -526,6 +529,11 @@ Allowed options)",
|
||||
"Output a single json document containing the specified information."
|
||||
)
|
||||
(g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.")
|
||||
(
|
||||
g_argStandardJSON.c_str(),
|
||||
"Switch to Standard JSON input / output mode, ignoring all options."
|
||||
"It reads from standard input and provides the result on the standard output."
|
||||
)
|
||||
(
|
||||
g_argAssemble.c_str(),
|
||||
"Switch to assembly mode, ignoring all options and assumes input is assembly."
|
||||
@ -615,6 +623,20 @@ bool CommandLineInterface::processInput()
|
||||
m_allowedDirectories.push_back(boost::filesystem::path(path));
|
||||
}
|
||||
|
||||
if (m_args.count(g_argStandardJSON))
|
||||
{
|
||||
string input;
|
||||
while (!cin.eof())
|
||||
{
|
||||
string tmp;
|
||||
getline(cin, tmp);
|
||||
input.append(tmp + "\n");
|
||||
}
|
||||
StandardCompiler compiler;
|
||||
cout << compiler.compile(input) << endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
readInputFilesAndConfigureRemappings();
|
||||
|
||||
if (m_args.count(g_argLibraries))
|
||||
@ -882,7 +904,9 @@ void CommandLineInterface::handleAst(string const& _argStr)
|
||||
|
||||
bool CommandLineInterface::actOnInput()
|
||||
{
|
||||
if (m_onlyAssemble)
|
||||
if (m_args.count(g_argStandardJSON))
|
||||
return true;
|
||||
else if (m_onlyAssemble)
|
||||
outputAssembly();
|
||||
else if (m_onlyLink)
|
||||
writeLinkedFiles();
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/interface/Exceptions.h>
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolidity/interface/SourceReferenceFormatter.h>
|
||||
#include <libsolidity/ast/ASTJsonConverter.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
@ -50,6 +51,41 @@ extern "C" {
|
||||
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
|
||||
}
|
||||
|
||||
ReadFile::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr)
|
||||
{
|
||||
ReadFile::Callback readCallback;
|
||||
if (_readCallback)
|
||||
{
|
||||
readCallback = [=](string const& _path)
|
||||
{
|
||||
char* contents_c = nullptr;
|
||||
char* error_c = nullptr;
|
||||
_readCallback(_path.c_str(), &contents_c, &error_c);
|
||||
ReadFile::Result result;
|
||||
result.success = true;
|
||||
if (!contents_c && !error_c)
|
||||
{
|
||||
result.success = false;
|
||||
result.contentsOrErrorMessage = "File not found.";
|
||||
}
|
||||
if (contents_c)
|
||||
{
|
||||
result.success = true;
|
||||
result.contentsOrErrorMessage = string(contents_c);
|
||||
free(contents_c);
|
||||
}
|
||||
if (error_c)
|
||||
{
|
||||
result.success = false;
|
||||
result.contentsOrErrorMessage = string(error_c);
|
||||
free(error_c);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return readCallback;
|
||||
}
|
||||
|
||||
Json::Value functionHashes(ContractDefinition const& _contract)
|
||||
{
|
||||
Json::Value functionHashes(Json::objectValue);
|
||||
@ -103,37 +139,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
|
||||
{
|
||||
Json::Value output(Json::objectValue);
|
||||
Json::Value errors(Json::arrayValue);
|
||||
ReadFile::Callback readCallback;
|
||||
if (_readCallback)
|
||||
{
|
||||
readCallback = [=](string const& _path)
|
||||
{
|
||||
char* contents_c = nullptr;
|
||||
char* error_c = nullptr;
|
||||
_readCallback(_path.c_str(), &contents_c, &error_c);
|
||||
ReadFile::Result result;
|
||||
result.success = true;
|
||||
if (!contents_c && !error_c)
|
||||
{
|
||||
result.success = false;
|
||||
result.contentsOrErrorMessage = "File not found.";
|
||||
}
|
||||
if (contents_c)
|
||||
{
|
||||
result.success = true;
|
||||
result.contentsOrErrorMessage = string(contents_c);
|
||||
free(contents_c);
|
||||
}
|
||||
if (error_c)
|
||||
{
|
||||
result.success = false;
|
||||
result.contentsOrErrorMessage = string(error_c);
|
||||
free(error_c);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
CompilerStack compiler(readCallback);
|
||||
CompilerStack compiler(wrapReadCallback(_readCallback));
|
||||
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); };
|
||||
bool success = false;
|
||||
try
|
||||
@ -287,6 +293,13 @@ string compileSingle(string const& _input, bool _optimize)
|
||||
return compile(sources, _optimize, nullptr);
|
||||
}
|
||||
|
||||
|
||||
string compileStandardInternal(string const& _input, CStyleReadFileCallback _readCallback = nullptr)
|
||||
{
|
||||
StandardCompiler compiler(wrapReadCallback(_readCallback));
|
||||
return compiler.compile(_input);
|
||||
}
|
||||
|
||||
static string s_outputBuffer;
|
||||
|
||||
extern "C"
|
||||
@ -310,4 +323,9 @@ extern char const* compileJSONCallback(char const* _input, bool _optimize, CStyl
|
||||
s_outputBuffer = compileMulti(_input, _optimize, _readCallback);
|
||||
return s_outputBuffer.c_str();
|
||||
}
|
||||
extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback)
|
||||
{
|
||||
s_outputBuffer = compileStandardInternal(_input, _readCallback);
|
||||
return s_outputBuffer.c_str();
|
||||
}
|
||||
}
|
||||
|
270
test/libsolidity/StandardCompiler.cpp
Normal file
270
test/libsolidity/StandardCompiler.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @date 2017
|
||||
* Unit tests for interface/StandardCompiler.h.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libdevcore/JSON.h>
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::eth;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Helper to match a specific error type and message
|
||||
bool containsError(Json::Value const& _compilerResult, string const& _type, string const& _message)
|
||||
{
|
||||
if (!_compilerResult.isMember("errors"))
|
||||
return false;
|
||||
|
||||
for (auto const& error: _compilerResult["errors"])
|
||||
{
|
||||
BOOST_REQUIRE(error.isObject());
|
||||
BOOST_REQUIRE(error["type"].isString());
|
||||
BOOST_REQUIRE(error["message"].isString());
|
||||
if ((error["type"].asString() == _type) && (error["message"].asString() == _message))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool containsAtMostWarnings(Json::Value const& _compilerResult)
|
||||
{
|
||||
if (!_compilerResult.isMember("errors"))
|
||||
return true;
|
||||
|
||||
for (auto const& error: _compilerResult["errors"])
|
||||
{
|
||||
BOOST_REQUIRE(error.isObject());
|
||||
BOOST_REQUIRE(error["severity"].isString());
|
||||
if (error["severity"].asString() != "warning")
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string bytecodeSansMetadata(string const& _bytecode)
|
||||
{
|
||||
/// The metadata hash takes up 43 bytes (or 86 characters in hex)
|
||||
/// /a165627a7a72305820([0-9a-f]{64})0029$/
|
||||
|
||||
if (_bytecode.size() < 88)
|
||||
return _bytecode;
|
||||
|
||||
if (_bytecode.substr(_bytecode.size() - 4, 4) != "0029")
|
||||
return _bytecode;
|
||||
|
||||
if (_bytecode.substr(_bytecode.size() - 86, 18) != "a165627a7a72305820")
|
||||
return _bytecode;
|
||||
|
||||
return _bytecode.substr(0, _bytecode.size() - 86);
|
||||
}
|
||||
|
||||
bool isValidMetadata(string const& _metadata)
|
||||
{
|
||||
Json::Value metadata;
|
||||
if (!Json::Reader().parse(_metadata, metadata, false))
|
||||
return false;
|
||||
|
||||
if (
|
||||
!metadata.isObject() ||
|
||||
!metadata.isMember("version") ||
|
||||
!metadata.isMember("language") ||
|
||||
!metadata.isMember("compiler") ||
|
||||
!metadata.isMember("settings") ||
|
||||
!metadata.isMember("sources") ||
|
||||
!metadata.isMember("output")
|
||||
)
|
||||
return false;
|
||||
|
||||
if (!metadata["version"].isNumeric() || metadata["version"] != 1)
|
||||
return false;
|
||||
|
||||
if (!metadata["language"].isString() || metadata["language"].asString() != "Solidity")
|
||||
return false;
|
||||
|
||||
/// @TODO add more strict checks
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Json::Value getContractResult(Json::Value const& _compilerResult, string const& _file, string const& _name)
|
||||
{
|
||||
if (
|
||||
!_compilerResult["contracts"].isObject() ||
|
||||
!_compilerResult["contracts"][_file].isObject() ||
|
||||
!_compilerResult["contracts"][_file][_name].isObject()
|
||||
)
|
||||
return Json::Value();
|
||||
return _compilerResult["contracts"][_file][_name];
|
||||
}
|
||||
|
||||
Json::Value compile(string const& _input)
|
||||
{
|
||||
StandardCompiler compiler;
|
||||
string output = compiler.compile(_input);
|
||||
Json::Value ret;
|
||||
BOOST_REQUIRE(Json::Reader().parse(output, ret, false));
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(StandardCompiler)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(assume_object_input)
|
||||
{
|
||||
Json::Value result;
|
||||
|
||||
/// Use the native JSON interface of StandardCompiler to trigger these
|
||||
solidity::StandardCompiler compiler;
|
||||
result = compiler.compile(Json::Value());
|
||||
BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
|
||||
result = compiler.compile(Json::Value("INVALID"));
|
||||
BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
|
||||
|
||||
/// Use the string interface of StandardCompiler to trigger these
|
||||
result = compile("");
|
||||
BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
|
||||
result = compile("invalid");
|
||||
BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
|
||||
result = compile("\"invalid\"");
|
||||
BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
|
||||
BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
|
||||
result = compile("{}");
|
||||
BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
|
||||
BOOST_CHECK(!containsAtMostWarnings(result));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(invalid_language)
|
||||
{
|
||||
char const* input = R"(
|
||||
{
|
||||
"language": "INVALID"
|
||||
}
|
||||
)";
|
||||
Json::Value result = compile(input);
|
||||
BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" is supported as a language."));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(valid_language)
|
||||
{
|
||||
char const* input = R"(
|
||||
{
|
||||
"language": "Solidity"
|
||||
}
|
||||
)";
|
||||
Json::Value result = compile(input);
|
||||
BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" is supported as a language."));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(no_sources)
|
||||
{
|
||||
char const* input = R"(
|
||||
{
|
||||
"language": "Solidity"
|
||||
}
|
||||
)";
|
||||
Json::Value result = compile(input);
|
||||
BOOST_CHECK(containsError(result, "JSONError", "No input sources specified."));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(smoke_test)
|
||||
{
|
||||
char const* input = R"(
|
||||
{
|
||||
"language": "Solidity",
|
||||
"sources": {
|
||||
"empty": {
|
||||
"content": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
Json::Value result = compile(input);
|
||||
BOOST_CHECK(containsAtMostWarnings(result));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(basic_compilation)
|
||||
{
|
||||
char const* input = R"(
|
||||
{
|
||||
"language": "Solidity",
|
||||
"sources": {
|
||||
"fileA": {
|
||||
"content": "contract A { }"
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
Json::Value result = compile(input);
|
||||
BOOST_CHECK(containsAtMostWarnings(result));
|
||||
Json::Value contract = getContractResult(result, "fileA", "A");
|
||||
BOOST_CHECK(contract.isObject());
|
||||
BOOST_CHECK(contract["abi"].isString());
|
||||
BOOST_CHECK(contract["abi"].asString() == "[]");
|
||||
BOOST_CHECK(contract["devdoc"].isString());
|
||||
BOOST_CHECK(contract["devdoc"].asString() == "{\"methods\":{}}");
|
||||
BOOST_CHECK(contract["userdoc"].isString());
|
||||
BOOST_CHECK(contract["userdoc"].asString() == "{\"methods\":{}}");
|
||||
BOOST_CHECK(contract["evm"].isObject());
|
||||
/// @TODO check evm.methodIdentifiers, legacyAssembly, bytecode, deployedBytecode
|
||||
BOOST_CHECK(contract["evm"]["bytecode"].isObject());
|
||||
BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString());
|
||||
BOOST_CHECK(bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()) ==
|
||||
"60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00");
|
||||
BOOST_CHECK(contract["evm"]["assembly"].isString());
|
||||
BOOST_CHECK(contract["evm"]["assembly"].asString() ==
|
||||
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n"
|
||||
" invalid\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n"
|
||||
" return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n"
|
||||
" mstore(0x40, 0x60)\n tag_1:\n invalid\n}\n");
|
||||
BOOST_CHECK(contract["evm"]["gasEstimates"].isObject());
|
||||
BOOST_CHECK(dev::jsonCompactPrint(contract["evm"]["gasEstimates"]) ==
|
||||
"{\"creation\":{\"codeDepositCost\":\"10200\",\"executionCost\":\"62\",\"totalCost\":\"10262\"}}");
|
||||
BOOST_CHECK(contract["metadata"].isString());
|
||||
BOOST_CHECK(isValidMetadata(contract["metadata"].asString()));
|
||||
BOOST_CHECK(result["sources"].isObject());
|
||||
BOOST_CHECK(result["sources"]["fileA"].isObject());
|
||||
BOOST_CHECK(result["sources"]["fileA"]["legacyAST"].isObject());
|
||||
BOOST_CHECK(dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]) ==
|
||||
"{\"children\":[{\"attributes\":{\"fullyImplemented\":true,\"isLibrary\":false,\"linearizedBaseContracts\":[1],"
|
||||
"\"name\":\"A\"},\"children\":[],\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"name\":\"SourceUnit\"}");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
Loading…
Reference in New Issue
Block a user