diff options
Diffstat (limited to 'libsolidity/interface')
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 190 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 39 | ||||
-rw-r--r-- | libsolidity/interface/Exceptions.cpp | 12 | ||||
-rw-r--r-- | libsolidity/interface/ReadFile.h | 45 | ||||
-rw-r--r-- | libsolidity/interface/SourceReferenceFormatter.h | 11 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.cpp | 482 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.h | 63 |
7 files changed, 800 insertions, 42 deletions
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 6b0024ad..9c9c9614 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/InterfaceHandler.h> +#include <libsolidity/interface/GasEstimator.h> #include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -55,8 +56,8 @@ using namespace std; using namespace dev; using namespace dev::solidity; -CompilerStack::CompilerStack(ReadFileCallback const& _readFile): - m_readFile(_readFile), m_parseSuccessful(false) {} +CompilerStack::CompilerStack(ReadFile::Callback const& _readFile): + m_readFile(_readFile) {} void CompilerStack::setRemappings(vector<string> const& _remappings) { @@ -78,10 +79,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings) void CompilerStack::reset(bool _keepSources) { - m_parseSuccessful = false; if (_keepSources) + { + m_stackState = SourcesSet; for (auto sourcePair: m_sources) sourcePair.second.reset(); + } else { m_sources.clear(); @@ -93,6 +96,7 @@ void CompilerStack::reset(bool _keepSources) m_sourceOrder.clear(); m_contracts.clear(); m_errors.clear(); + m_stackState = Empty; } bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) @@ -101,6 +105,7 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool reset(true); m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); m_sources[_name].isLibrary = _isLibrary; + m_stackState = SourcesSet; return existed; } @@ -113,9 +118,10 @@ void CompilerStack::setSource(string const& _sourceCode) bool CompilerStack::parse() { //reset + if(m_stackState != SourcesSet) + return false; m_errors.clear(); ASTNode::resetID(); - m_parseSuccessful = false; if (SemVerVersion{string(VersionString)}.isPrerelease()) { @@ -127,14 +133,12 @@ bool CompilerStack::parse() vector<string> sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); - map<string, SourceUnit const*> sourceUnitsByName; for (size_t i = 0; i < sourcesToParse.size(); ++i) { string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); source.ast = Parser(m_errors).parse(source.scanner); - sourceUnitsByName[path] = source.ast.get(); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); else @@ -149,10 +153,19 @@ bool CompilerStack::parse() } } } - if (!Error::containsOnlyWarnings(m_errors)) - // errors while parsing. should stop before type checking + if (Error::containsOnlyWarnings(m_errors)) + { + m_stackState = ParsingSuccessful; + return true; + } + else return false; +} +bool CompilerStack::analyze() +{ + if (m_stackState != ParsingSuccessful) + return false; resolveImports(); bool noErrors = true; @@ -172,6 +185,9 @@ bool CompilerStack::parse() if (!resolver.registerDeclarations(*source->ast)) return false; + map<string, SourceUnit const*> sourceUnitsByName; + for (auto& source: m_sources) + sourceUnitsByName[source.first] = source.second.ast.get(); for (Source const* source: m_sourceOrder) if (!resolver.performImports(*source->ast, sourceUnitsByName)) return false; @@ -234,8 +250,13 @@ bool CompilerStack::parse() noErrors = false; } - m_parseSuccessful = noErrors; - return m_parseSuccessful; + if (noErrors) + { + m_stackState = AnalysisSuccessful; + return true; + } + else + return false; } bool CompilerStack::parse(string const& _sourceCode) @@ -244,9 +265,20 @@ bool CompilerStack::parse(string const& _sourceCode) return parse(); } +bool CompilerStack::parseAndAnalyze() +{ + return parse() && analyze(); +} + +bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode) +{ + setSource(_sourceCode); + return parseAndAnalyze(); +} + vector<string> CompilerStack::contractNames() const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); vector<string> contractNames; for (auto const& contract: m_contracts) @@ -257,8 +289,8 @@ vector<string> CompilerStack::contractNames() const bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries) { - if (!m_parseSuccessful) - if (!parse()) + if (m_stackState < AnalysisSuccessful) + if (!parseAndAnalyze()) return false; m_optimize = _optimize; @@ -271,12 +303,13 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) compileContract(*contract, compiledContracts); this->link(); + m_stackState = CompilationSuccessful; return true; } bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs) { - return parse(_sourceCode) && compile(_optimize, _runs); + return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs); } void CompilerStack::link() @@ -405,8 +438,9 @@ vector<string> CompilerStack::sourceNames() const map<string, unsigned> CompilerStack::sourceIndices() const { map<string, unsigned> indices; + unsigned index = 0; for (auto const& s: m_sources) - indices[s.first] = indices.size(); + indices[s.first] = index++; return indices; } @@ -417,7 +451,7 @@ Json::Value const& CompilerStack::interface(string const& _contractName) const Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return metadata(contract(_contractName), _type); @@ -425,7 +459,7 @@ Json::Value const& CompilerStack::metadata(string const& _contractName, Document Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); solAssert(_contract.contract, ""); @@ -456,7 +490,7 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat string const& CompilerStack::onChainMetadata(string const& _contractName) const { - if (!m_parseSuccessful) + if (m_stackState != CompilationSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return contract(_contractName).onChainMetadata; @@ -522,18 +556,18 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string if (m_sources.count(importPath) || newSources.count(importPath)) continue; - ReadFileResult result{false, string("File not supplied initially.")}; + ReadFile::Result result{false, string("File not supplied initially.")}; if (m_readFile) result = m_readFile(importPath); if (result.success) - newSources[importPath] = result.contentsOrErrorMesage; + newSources[importPath] = result.contentsOrErrorMessage; else { auto err = make_shared<Error>(Error::Type::ParserError); *err << errinfo_sourceLocation(import->location()) << - errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage); + errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage); m_errors.push_back(std::move(err)); continue; } @@ -655,8 +689,33 @@ void CompilerStack::compileContract( cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); compiledContract.compiler = compiler; - compiledContract.object = compiler->assembledObject(); - compiledContract.runtimeObject = compiler->runtimeObject(); + + try + { + compiledContract.object = compiler->assembledObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode")); + } + + try + { + compiledContract.runtimeObject = compiler->runtimeObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode")); + } + compiledContract.onChainMetadata = onChainMetadata; _compiledContracts[compiledContract.contract] = &compiler->assembly(); @@ -841,3 +900,88 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con } return ret; } + +namespace +{ + +Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) +{ + if (_gas.isInfinite) + return Json::Value("infinite"); + else + return Json::Value(toString(_gas.value)); +} + +} + +Json::Value CompilerStack::gasEstimates(string const& _contractName) const +{ + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) + return Json::Value(); + + using Gas = GasEstimator::GasConsumption; + Json::Value output(Json::objectValue); + + if (eth::AssemblyItems const* items = assemblyItems(_contractName)) + { + Gas executionGas = GasEstimator::functionalEstimation(*items); + u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); + Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; + + Json::Value creation(Json::objectValue); + creation["codeDepositCost"] = gasToJson(codeDepositGas); + creation["executionCost"] = gasToJson(executionGas); + /// TODO: implement + overload to avoid the need of += + executionGas += codeDepositGas; + creation["totalCost"] = gasToJson(executionGas); + output["creation"] = creation; + } + + if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName)) + { + /// External functions + ContractDefinition const& contract = contractDefinition(_contractName); + Json::Value externalFunctions(Json::objectValue); + for (auto it: contract.interfaceFunctions()) + { + string sig = it.second->externalSignature(); + externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + } + + if (contract.fallbackFunction()) + /// This needs to be set to an invalid signature in order to trigger the fallback, + /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. + /// An empty string ("") would work to trigger the shortcut only. + externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + + if (!externalFunctions.empty()) + output["external"] = externalFunctions; + + /// Internal functions + Json::Value internalFunctions(Json::objectValue); + for (auto const& it: contract.definedFunctions()) + { + /// Exclude externally visible functions, constructor and the fallback function + if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty()) + continue; + + size_t entry = functionEntryPoint(_contractName, *it); + GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); + if (entry > 0) + gas = GasEstimator::functionalEstimation(*items, entry, *it); + + FunctionType type(*it); + string sig = it->name() + "("; + auto paramTypes = type.parameterTypes(); + for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) + sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); + sig += ")"; + internalFunctions[sig] = gasToJson(gas); + } + + if (!internalFunctions.empty()) + output["internal"] = internalFunctions; + } + + return output; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index eddfea68..c1d344ca 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -36,6 +36,7 @@ #include <libevmasm/SourceLocation.h> #include <libevmasm/LinkerObject.h> #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> namespace dev { @@ -77,18 +78,10 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: - struct ReadFileResult - { - bool success; - std::string contentsOrErrorMesage; - }; - - /// File reading callback. - using ReadFileCallback = std::function<ReadFileResult(std::string const&)>; - /// Creates a new compiler stack. - /// @param _readFile callback to used to read files for import statements. Should return - explicit CompilerStack(ReadFileCallback const& _readFile = ReadFileCallback()); + /// @param _readFile callback to used to read files for import statements. Must return + /// and must not emit exceptions. + explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()); /// Sets path remappings in the format "context:prefix=target" void setRemappings(std::vector<std::string> const& _remappings); @@ -110,6 +103,16 @@ public: /// Sets the given source code as the only source unit apart from standard sources and parses it. /// @returns false on error. bool parse(std::string const& _sourceCode); + /// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving, + /// typechecking, staticAnalysis) on previously set sources + /// @returns false on error. + bool analyze(); + /// Parses and analyzes all source units that were added + /// @returns false on error. + bool parseAndAnalyze(); + /// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it. + /// @returns false on error. + bool parseAndAnalyze(std::string const& _sourceCode); /// @returns a list of the contract names in the sources. std::vector<std::string> contractNames() const; std::string defaultContractName() const; @@ -181,6 +184,9 @@ public: std::string const& onChainMetadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions + Json::Value gasEstimates(std::string const& _contractName) const; + /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; /// @returns the parsed source unit with the supplied name. @@ -230,6 +236,13 @@ private: mutable std::unique_ptr<std::string const> sourceMapping; mutable std::unique_ptr<std::string const> runtimeSourceMapping; }; + enum State { + Empty, + SourcesSet, + ParsingSuccessful, + AnalysisSuccessful, + CompilationSuccessful + }; /// Loads the missing sources from @a _ast (named @a _path) using the callback /// @a m_readFile and stores the absolute paths of all imports in the AST annotations. @@ -263,14 +276,13 @@ private: std::string target; }; - ReadFileCallback m_readFile; + ReadFile::Callback m_readFile; bool m_optimize = false; unsigned m_optimizeRuns = 200; std::map<std::string, h160> m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" std::vector<Remapping> m_remappings; - bool m_parseSuccessful; std::map<std::string const, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; @@ -279,6 +291,7 @@ private: std::string m_formalTranslation; ErrorList m_errors; bool m_metadataLiteralSources = false; + State m_stackState = Empty; }; } diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index 968a24ad..c09180de 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -33,22 +33,22 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip switch(m_type) { case Type::DeclarationError: - m_typeName = "Declaration Error"; + m_typeName = "DeclarationError"; break; case Type::DocstringParsingError: - m_typeName = "Docstring Parsing Error"; + m_typeName = "DocstringParsingError"; break; case Type::ParserError: - m_typeName = "Parser Error"; + m_typeName = "ParserError"; break; case Type::SyntaxError: - m_typeName = "Syntax Error"; + m_typeName = "SyntaxError"; break; case Type::TypeError: - m_typeName = "Type Error"; + m_typeName = "TypeError"; break; case Type::Why3TranslatorError: - m_typeName = "Why3 Translator Error"; + m_typeName = "Why3TranslatorError"; break; case Type::Warning: m_typeName = "Warning"; diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h new file mode 100644 index 00000000..2e8a6bd8 --- /dev/null +++ b/libsolidity/interface/ReadFile.h @@ -0,0 +1,45 @@ +/* + 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/>. +*/ + +#pragma once + +#include <string> +#include <functional> +#include <boost/noncopyable.hpp> + +namespace dev +{ + +namespace solidity +{ + +class ReadFile: boost::noncopyable +{ +public: + /// File reading result. + struct Result + { + bool success; + std::string contentsOrErrorMessage; + }; + + /// File reading callback. + using Callback = std::function<Result(std::string const&)>; +}; + +} +} diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/libsolidity/interface/SourceReferenceFormatter.h index 7034f4ab..e8676d60 100644 --- a/libsolidity/interface/SourceReferenceFormatter.h +++ b/libsolidity/interface/SourceReferenceFormatter.h @@ -23,6 +23,7 @@ #pragma once #include <ostream> +#include <sstream> #include <functional> #include <libevmasm/SourceLocation.h> @@ -53,6 +54,16 @@ public: std::string const& _name, ScannerFromSourceNameFun const& _scannerFromSourceName ); + static std::string formatExceptionInformation( + Exception const& _exception, + std::string const& _name, + ScannerFromSourceNameFun const& _scannerFromSourceName + ) + { + std::ostringstream errorOutput; + printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName); + return errorOutput.str(); + } private: /// Prints source name if location is given. static void printSourceName( diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp new file mode 100644 index 00000000..223cc15d --- /dev/null +++ b/libsolidity/interface/StandardCompiler.cpp @@ -0,0 +1,482 @@ +/* + 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> +#include <libdevcore/SHA3.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); +} + +/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content. +bool hashMatchesContent(string const& _hash, string const& _content) +{ + try + { + return dev::h256(_hash) == dev::keccak256(_content); + } + catch (dev::BadHexCharacter) + { + return false; + } +} + +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."); + + Json::Value errors = Json::arrayValue; + + for (auto const& sourceName: sources.getMemberNames()) + { + string hash; + + if (!sources[sourceName].isObject()) + return formatFatalError("JSONError", "Source input is not a JSON object."); + + if (sources[sourceName]["keccak256"].isString()) + hash = sources[sourceName]["keccak256"].asString(); + + if (sources[sourceName]["content"].isString()) + { + string content = sources[sourceName]["content"].asString(); + if (!hash.empty() && !hashMatchesContent(hash, content)) + errors.append(formatError( + false, + "IOError", + "general", + "Mismatch between content and supplied hash for \"" + sourceName + "\"" + )); + else + m_compilerStack.addSource(sourceName, content); + } + else if (sources[sourceName]["urls"].isArray()) + { + if (!m_readFile) + return formatFatalError("JSONError", "No import callback supplied, but URL is requested."); + + bool found = false; + vector<string> failures; + + for (auto const& url: sources[sourceName]["urls"]) + { + ReadFile::Result result = m_readFile(url.asString()); + if (result.success) + { + if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage)) + errors.append(formatError( + false, + "IOError", + "general", + "Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.asString() + "\"" + )); + else + { + m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage); + found = true; + break; + } + } + else + failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage); + } + + for (auto const& failure: failures) + { + /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. + errors.append(formatError( + found ? true : false, + "IOError", + "general", + failure + )); + } + } + 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); }; + + 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: success ? m_compilerStack.contractNames() : vector<string>()) + { + 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"] = m_compilerStack.metadata(contractName, DocumentationType::ABIInterface); + contractData["metadata"] = m_compilerStack.onChainMetadata(contractName); + contractData["userdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecUser); + contractData["devdoc"] = 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; + + return output; +} + +Json::Value StandardCompiler::compile(Json::Value const& _input) +{ + try + { + return compileInternal(_input); + } + catch (Json::LogicError const& _exception) + { + return formatFatalError("InternalCompilerError", string("JSON logic exception: ") + _exception.what()); + } + catch (Json::RuntimeError const& _exception) + { + return formatFatalError("InternalCompilerError", string("JSON runtime exception: ") + _exception.what()); + } + catch (Exception const& _exception) + { + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception)); + } + catch (...) + { + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal"); + } +} + +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..dfaf88cd --- /dev/null +++ b/libsolidity/interface/StandardCompiler.h @@ -0,0 +1,63 @@ +/* + 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. Must return + /// and must not emit exceptions. + StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback()) + : m_compilerStack(_readFile), m_readFile(_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; + ReadFile::Callback m_readFile; +}; + +} +} |