diff options
author | Alex Beregszaszi <alex@rtfs.hu> | 2017-04-21 03:11:40 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-21 03:11:40 +0800 |
commit | 2ccbc088f24a8a215072cbf919d80cb503aac89f (patch) | |
tree | b3f44ad47f31ef5bde284e68e87a87cce6f146f0 | |
parent | 965de29772963b640b00579fa8225bb662599ecd (diff) | |
parent | 74373ecc7ab538de03c69a7a26edb345be661355 (diff) | |
download | dexon-solidity-2ccbc088f24a8a215072cbf919d80cb503aac89f.tar.gz 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.md | 3 | ||||
-rw-r--r-- | docs/using-the-compiler.rst | 11 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.cpp | 418 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.h | 61 | ||||
-rw-r--r-- | solc/CommandLineInterface.cpp | 26 | ||||
-rw-r--r-- | solc/jsonCompiler.cpp | 80 | ||||
-rw-r--r-- | test/libsolidity/StandardCompiler.cpp | 270 |
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 |