aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Beregszaszi <alex@rtfs.hu>2017-04-21 03:11:40 +0800
committerGitHub <noreply@github.com>2017-04-21 03:11:40 +0800
commit2ccbc088f24a8a215072cbf919d80cb503aac89f (patch)
treeb3f44ad47f31ef5bde284e68e87a87cce6f146f0
parent965de29772963b640b00579fa8225bb662599ecd (diff)
parent74373ecc7ab538de03c69a7a26edb345be661355 (diff)
downloaddexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.gz
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.bz2
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.lz
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.xz
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.zst
dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.zip
Merge pull request #1639 from ethereum/json-interface-api
Support "standardised" JSON compiler input/output
-rw-r--r--Changelog.md3
-rw-r--r--docs/using-the-compiler.rst11
-rw-r--r--libsolidity/interface/StandardCompiler.cpp418
-rw-r--r--libsolidity/interface/StandardCompiler.h61
-rw-r--r--solc/CommandLineInterface.cpp26
-rw-r--r--solc/jsonCompiler.cpp80
-rw-r--r--test/libsolidity/StandardCompiler.cpp270
7 files changed, 833 insertions, 36 deletions
diff --git a/Changelog.md b/Changelog.md
index dd62f2df..352e8374 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -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.
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
index 1cc1f2b8..9e9a5eb6 100644
--- a/docs/using-the-compiler.rst
+++ b/docs/using-the-compiler.rst
@@ -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.
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
new file mode 100644
index 00000000..db89e16c
--- /dev/null
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -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.\"}]}";
+ }
+}
diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h
new file mode 100644
index 00000000..12d85aad
--- /dev/null
+++ b/libsolidity/interface/StandardCompiler.h
@@ -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;
+};
+
+}
+}
diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp
index 76102b53..a622fcfe 100644
--- a/solc/CommandLineInterface.cpp
+++ b/solc/CommandLineInterface.cpp
@@ -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{
@@ -527,6 +530,11 @@ Allowed options)",
)
(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();
diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp
index fd375ce3..42c25de0 100644
--- a/solc/jsonCompiler.cpp
+++ b/solc/jsonCompiler.cpp
@@ -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();
+}
}
diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp
new file mode 100644
index 00000000..9b53e841
--- /dev/null
+++ b/test/libsolidity/StandardCompiler.cpp
@@ -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