diff options
Diffstat (limited to 'libsolidity/interface')
-rw-r--r-- | libsolidity/interface/AssemblyStack.cpp | 20 | ||||
-rw-r--r-- | libsolidity/interface/AssemblyStack.h | 6 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 191 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 72 | ||||
-rw-r--r-- | libsolidity/interface/EVMVersion.h | 1 | ||||
-rw-r--r-- | libsolidity/interface/ErrorReporter.h | 6 | ||||
-rw-r--r-- | libsolidity/interface/Exceptions.cpp | 3 | ||||
-rw-r--r-- | libsolidity/interface/Exceptions.h | 2 | ||||
-rw-r--r-- | libsolidity/interface/GasEstimator.cpp | 7 | ||||
-rw-r--r-- | libsolidity/interface/Natspec.cpp | 75 | ||||
-rw-r--r-- | libsolidity/interface/Natspec.h | 8 | ||||
-rw-r--r-- | libsolidity/interface/SourceReferenceFormatter.cpp | 11 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.cpp | 64 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.h | 6 | ||||
-rw-r--r-- | libsolidity/interface/Version.cpp | 6 |
15 files changed, 284 insertions, 194 deletions
diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 7f97336b..26496de7 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -15,7 +15,7 @@ along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** - * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and * eWasm as output. */ @@ -31,8 +31,8 @@ #include <libevmasm/Assembly.h> -#include <libjulia/backends/evm/EVMCodeTransform.h> -#include <libjulia/backends/evm/EVMAssembly.h> +#include <libyul/backends/evm/EVMCodeTransform.h> +#include <libyul/backends/evm/EVMAssembly.h> using namespace std; using namespace dev; @@ -48,11 +48,11 @@ assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) return assembly::AsmFlavour::Loose; case AssemblyStack::Language::StrictAssembly: return assembly::AsmFlavour::Strict; - case AssemblyStack::Language::JULIA: - return assembly::AsmFlavour::IULIA; + case AssemblyStack::Language::Yul: + return assembly::AsmFlavour::Yul; } solAssert(false, ""); - return assembly::AsmFlavour::IULIA; + return assembly::AsmFlavour::Yul; } } @@ -116,10 +116,10 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const case Machine::EVM15: { MachineAssemblyObject object; - julia::EVMAssembly assembly(true); - julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::JULIA, true)(*m_parserResult); + yul::EVMAssembly assembly(true); + yul::CodeTransform(assembly, *m_analysisInfo, m_language == Language::Yul, true)(*m_parserResult); object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); - /// TOOD: fill out text representation + /// TODO: fill out text representation return object; } case Machine::eWasm: @@ -132,5 +132,5 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const string AssemblyStack::print() const { solAssert(m_parserResult, ""); - return assembly::AsmPrinter(m_language == Language::JULIA)(*m_parserResult); + return assembly::AsmPrinter(m_language == Language::Yul)(*m_parserResult); } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 720220ab..8132ce63 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -15,7 +15,7 @@ along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** - * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and * eWasm as output. */ @@ -47,13 +47,13 @@ struct MachineAssemblyObject }; /* - * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and * eWasm as output. */ class AssemblyStack { public: - enum class Language { JULIA, Assembly, StrictAssembly }; + enum class Language { Yul, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; explicit AssemblyStack(EVMVersion _evmVersion = EVMVersion(), Language _language = Language::Assembly): diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 47dc30cf..441c7897 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -47,6 +47,8 @@ #include <libevmasm/Exceptions.h> +#include <libyul/YulString.h> + #include <libdevcore/SwarmHash.h> #include <libdevcore/JSON.h> @@ -58,22 +60,31 @@ using namespace std; using namespace dev; using namespace dev::solidity; -void CompilerStack::setRemappings(vector<string> const& _remappings) +boost::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping) +{ + auto eq = find(_remapping.begin(), _remapping.end(), '='); + if (eq == _remapping.end()) + return {}; + + auto colon = find(_remapping.begin(), eq, ':'); + + Remapping r; + + r.context = colon == eq ? string() : string(_remapping.begin(), colon); + r.prefix = colon == eq ? string(_remapping.begin(), eq) : string(colon + 1, eq); + r.target = string(eq + 1, _remapping.end()); + + if (r.prefix.empty()) + return {}; + + return r; +} + +void CompilerStack::setRemappings(vector<Remapping> const& _remappings) { - vector<Remapping> remappings; for (auto const& remapping: _remappings) - { - auto eq = find(remapping.begin(), remapping.end(), '='); - if (eq == remapping.end()) - continue; // ignore - auto colon = find(remapping.begin(), eq, ':'); - Remapping r; - r.context = colon == eq ? string() : string(remapping.begin(), colon); - r.prefix = colon == eq ? string(remapping.begin(), eq) : string(colon + 1, eq); - r.target = string(eq + 1, remapping.end()); - remappings.push_back(r); - } - swap(m_remappings, remappings); + solAssert(!remapping.prefix.empty(), ""); + m_remappings = _remappings; } void CompilerStack::setEVMVersion(EVMVersion _version) @@ -119,7 +130,7 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool bool CompilerStack::parse() { //reset - if(m_stackState != SourcesSet) + if (m_stackState != SourcesSet) return false; m_errorReporter.clear(); ASTNode::resetID(); @@ -191,6 +202,8 @@ bool CompilerStack::analyze() if (!resolver.performImports(*source->ast, sourceUnitsByName)) return false; + // This is the main name and type resolution loop. Needs to be run for every contract, because + // the special variables "this" and "super" must be set appropriately. for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) @@ -204,11 +217,15 @@ bool CompilerStack::analyze() // thus contracts can only conflict if declared in the same source file. This // already causes a double-declaration error elsewhere, so we do not report // an error here and instead silently drop any additional contracts we find. - if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) m_contracts[contract->fullyQualifiedName()].contract = contract; } + // This cannot be done in the above loop, because cross-contract types couldn't be resolved. + // A good example is `LibraryName.TypeName x;`. + // + // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed, + // which is only done one step later. TypeChecker typeChecker(m_evmVersion, m_errorReporter); for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) @@ -218,6 +235,7 @@ bool CompilerStack::analyze() if (noErrors) { + // Checks that can only be done when all types of all AST nodes are known. PostTypeChecker postTypeChecker(m_errorReporter); for (Source const* source: m_sourceOrder) if (!postTypeChecker.check(*source->ast)) @@ -226,6 +244,8 @@ bool CompilerStack::analyze() if (noErrors) { + // Control flow graph generator and analyzer. It can check for issues such as + // variable is used before it is assigned to. CFG cfg(m_errorReporter); for (Source const* source: m_sourceOrder) if (!cfg.constructFlow(*source->ast)) @@ -242,6 +262,7 @@ bool CompilerStack::analyze() if (noErrors) { + // Checks for common mistakes. Only generates warnings. StaticAnalyzer staticAnalyzer(m_errorReporter); for (Source const* source: m_sourceOrder) if (!staticAnalyzer.analyze(*source->ast)) @@ -250,6 +271,7 @@ bool CompilerStack::analyze() if (noErrors) { + // Check for state mutability in every function. vector<ASTPointer<ASTNode>> ast; for (Source const* source: m_sourceOrder) ast.push_back(source->ast); @@ -262,7 +284,7 @@ bool CompilerStack::analyze() { SMTChecker smtChecker(m_errorReporter, m_smtQuery); for (Source const* source: m_sourceOrder) - smtChecker.analyze(*source->ast); + smtChecker.analyze(*source->ast, source->scanner); } } catch(FatalError const&) @@ -300,6 +322,7 @@ bool CompilerStack::compile() if (!parseAndAnalyze()) return false; + // Only compile contracts individually which have been requested. map<ContractDefinition const*, eth::Assembly const*> compiledContracts; for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) @@ -317,7 +340,6 @@ void CompilerStack::link() { contract.second.object.link(m_libraries); contract.second.runtimeObject.link(m_libraries); - contract.second.cloneObject.link(m_libraries); } } @@ -396,11 +418,6 @@ eth::LinkerObject const& CompilerStack::runtimeObject(string const& _contractNam return contract(_contractName).runtimeObject; } -eth::LinkerObject const& CompilerStack::cloneObject(string const& _contractName) const -{ - return contract(_contractName).cloneObject; -} - /// FIXME: cache this string string CompilerStack::assemblyString(string const& _contractName, StringMap _sourceCodes) const { @@ -571,7 +588,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string for (auto const& node: _ast.nodes()) if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get())) { - string importPath = absolutePath(import->path(), _sourcePath); + string importPath = dev::absolutePath(import->path(), _sourcePath); // The current value of `path` is the absolute path as seen from this source file. // We first have to apply remappings before we can store the actual absolute path // as seen globally. @@ -614,8 +631,8 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context for (auto const& redir: m_remappings) { - string context = sanitizePath(redir.context); - string prefix = sanitizePath(redir.prefix); + string context = dev::sanitizePath(redir.context); + string prefix = dev::sanitizePath(redir.prefix); // Skip if current context is closer if (context.length() < longestContext) @@ -632,7 +649,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context longestContext = context.length(); longestPrefix = prefix.length(); - bestMatchTarget = sanitizePath(redir.target); + bestMatchTarget = dev::sanitizePath(redir.target); } string path = bestMatchTarget; path.append(_path.begin() + longestPrefix, _path.end()); @@ -669,23 +686,6 @@ void CompilerStack::resolveImports() swap(m_sourceOrder, sourceOrder); } -string CompilerStack::absolutePath(string const& _path, string const& _reference) const -{ - using path = boost::filesystem::path; - path p(_path); - // Anything that does not start with `.` is an absolute path. - if (p.begin() == p.end() || (*p.begin() != "." && *p.begin() != "..")) - return _path; - path result(_reference); - result.remove_filename(); - for (path::iterator it = p.begin(); it != p.end(); ++it) - if (*it == "..") - result = result.parent_path(); - else if (*it != ".") - result /= *it; - return result.generic_string(); -} - namespace { bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& features) @@ -711,39 +711,33 @@ void CompilerStack::compileContract( for (auto const* dependency: _contract.annotation().contractDependencies) compileContract(*dependency, _compiledContracts); - shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - string metadata = createMetadata(compiledContract); - bytes cborEncodedHash = - // CBOR-encoding of the key "bzzr0" - bytes{0x65, 'b', 'z', 'z', 'r', '0'}+ - // CBOR-encoding of the hash - bytes{0x58, 0x20} + dev::swarmHash(metadata).asBytes(); - bytes cborEncodedMetadata; - if (onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures)) - cborEncodedMetadata = - // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} - bytes{0xa1} + - cborEncodedHash; - else - cborEncodedMetadata = - // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true} - bytes{0xa2} + - cborEncodedHash + - bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; - solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large"); - // 16-bit big endian length - cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); - compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); + + shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns); compiledContract.compiler = compiler; + string metadata = createMetadata(compiledContract); + compiledContract.metadata = metadata; + + bytes cborEncodedMetadata = createCBORMetadata( + metadata, + !onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures) + ); + try { - compiledContract.object = compiler->assembledObject(); + // Run optimiser and compile the contract. + compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); } catch(eth::OptimizerException const&) { - solAssert(false, "Assembly optimizer exception for bytecode"); + solAssert(false, "Optimizer exception during compilation"); + } + + try + { + // Assemble deployment (incl. runtime) object. + compiledContract.object = compiler->assembledObject(); } catch(eth::AssemblyException const&) { @@ -752,36 +746,15 @@ void CompilerStack::compileContract( try { + // Assemble runtime object. compiledContract.runtimeObject = compiler->runtimeObject(); } - catch(eth::OptimizerException const&) - { - solAssert(false, "Assembly optimizer exception for deployed bytecode"); - } catch(eth::AssemblyException const&) { solAssert(false, "Assembly exception for deployed bytecode"); } - compiledContract.metadata = metadata; _compiledContracts[compiledContract.contract] = &compiler->assembly(); - - try - { - if (!_contract.isLibrary()) - { - Compiler cloneCompiler(m_evmVersion, m_optimize, m_optimizeRuns); - cloneCompiler.compileClone(_contract, _compiledContracts); - compiledContract.cloneObject = cloneCompiler.assembledObject(); - } - } - catch (eth::AssemblyException const&) - { - // In some cases (if the constructor requests a runtime function), it is not - // possible to compile the clone. - - // TODO: Report error / warning - } } string const CompilerStack::lastContractName() const @@ -809,7 +782,7 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa // To provide a measure of backward-compatibility, if a contract is not located by its // fully-qualified name, a lookup will be attempted purely on the contract's name to see // if anything will satisfy. - if (_contractName.find(":") == string::npos) + if (_contractName.find(':') == string::npos) { for (auto const& contractEntry: m_contracts) { @@ -894,6 +867,31 @@ string CompilerStack::createMetadata(Contract const& _contract) const return jsonCompactPrint(meta); } +bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode) +{ + bytes cborEncodedHash = + // CBOR-encoding of the key "bzzr0" + bytes{0x65, 'b', 'z', 'z', 'r', '0'}+ + // CBOR-encoding of the hash + bytes{0x58, 0x20} + dev::swarmHash(_metadata).asBytes(); + bytes cborEncodedMetadata; + if (_experimentalMode) + cborEncodedMetadata = + // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true} + bytes{0xa2} + + cborEncodedHash + + bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; + else + cborEncodedMetadata = + // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} + bytes{0xa1} + + cborEncodedHash; + solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large"); + // 16-bit big endian length + cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); + return cborEncodedMetadata; +} + string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const { string ret; @@ -938,17 +936,17 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con if (components-- > 0) { if (location.start != prevStart) - ret += std::to_string(location.start); + ret += to_string(location.start); if (components-- > 0) { ret += ':'; if (length != prevLength) - ret += std::to_string(length); + ret += to_string(length); if (components-- > 0) { ret += ':'; if (sourceIndex != prevSourceIndex) - ret += std::to_string(sourceIndex); + ret += to_string(sourceIndex); if (components-- > 0) { ret += ':'; @@ -992,8 +990,7 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const if (eth::AssemblyItems const* items = assemblyItems(_contractName)) { Gas executionGas = gasEstimator.functionalEstimation(*items); - u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); - Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; + Gas codeDepositGas{eth::GasMeter::dataGas(runtimeObject(_contractName).bytecode, false)}; Json::Value creation(Json::objectValue); creation["codeDepositCost"] = gasToJson(codeDepositGas); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 13c9cc7a..9a15fbf0 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -36,7 +36,6 @@ #include <json/json.h> #include <boost/noncopyable.hpp> -#include <boost/filesystem.hpp> #include <ostream> #include <string> @@ -85,6 +84,13 @@ public: CompilationSuccessful }; + struct Remapping + { + std::string context; + std::string prefix; + std::string target; + }; + /// Creates a new compiler stack. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. @@ -93,7 +99,7 @@ public: m_errorList(), m_errorReporter(m_errorList) {} - /// @returns the list of errors that occured during parsing and type checking. + /// @returns the list of errors that occurred during parsing and type checking. ErrorList const& errors() const { return m_errorReporter.errors(); } /// @returns the current state. @@ -104,8 +110,11 @@ public: /// All settings, with the exception of remappings, are reset. void reset(bool _keepSources = false); - /// Sets path remappings in the format "context:prefix=target" - void setRemappings(std::vector<std::string> const& _remappings); + // Parses a remapping of the format "context:prefix=target". + static boost::optional<Remapping> parseRemapping(std::string const& _remapping); + + /// Sets path remappings. + void setRemappings(std::vector<Remapping> const& _remappings); /// Sets library addresses. Addresses are cleared iff @a _libraries is missing. /// Will not take effect before running compile. @@ -122,6 +131,8 @@ public: m_optimizeRuns = _runs; } + /// Set the EVM version used before running compile. + /// When called without an argument it will revert to the default version. void setEVMVersion(EVMVersion _version = EVMVersion{}); /// Sets the list of requested contract names. If empty, no filtering is performed and every contract @@ -188,12 +199,6 @@ public: /// @returns the runtime object for the contract. eth::LinkerObject const& runtimeObject(std::string const& _contractName) const; - /// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL. - /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to - /// substituted by the actual address. Note that this sequence starts end ends in three X - /// characters but can contain anything in between. - eth::LinkerObject const& cloneObject(std::string const& _contractName) const; - /// @returns normal contract assembly items eth::AssemblyItems const* assemblyItems(std::string const& _contractName) const; @@ -240,9 +245,7 @@ public: Json::Value gasEstimates(std::string const& _contractName) const; private: - /** - * Information pertaining to one source unit, filled gradually during parsing and compilation. - */ + /// The state per source unit. Filled gradually during parsing. struct Source { std::shared_ptr<Scanner> scanner; @@ -251,13 +254,13 @@ private: void reset() { scanner.reset(); ast.reset(); } }; + /// The state per contract. Filled gradually during compilation. struct Contract { ContractDefinition const* contract = nullptr; std::shared_ptr<Compiler> compiler; - eth::LinkerObject object; - eth::LinkerObject runtimeObject; - eth::LinkerObject cloneObject; + eth::LinkerObject object; ///< Deployment object (includes the runtime sub-object). + eth::LinkerObject runtimeObject; ///< Runtime object. std::string metadata; ///< The metadata json that will be hashed into the chain. mutable std::unique_ptr<Json::Value const> abi; mutable std::unique_ptr<Json::Value const> userDocumentation; @@ -272,10 +275,6 @@ private: StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path); std::string applyRemapping(std::string const& _path, std::string const& _context); void resolveImports(); - /// @returns the absolute path corresponding to @a _path relative to @a _reference. - std::string absolutePath(std::string const& _path, std::string const& _reference) const; - /// Helper function to return path converted strings. - std::string sanitizePath(std::string const& _path) const { return boost::filesystem::path(_path).generic_string(); } /// @returns true if the contract is requested to be compiled. bool isRequestedContract(ContractDefinition const& _contract) const; @@ -285,19 +284,42 @@ private: ContractDefinition const& _contract, std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts ); + + /// Links all the known library addresses in the available objects. Any unknown + /// library will still be kept as an unlinked placeholder in the objects. void link(); + /// @returns the contract object for the given @a _contractName. + /// Can only be called after state is CompilationSuccessful. Contract const& contract(std::string const& _contractName) const; + + /// @returns the source object for the given @a _sourceName. + /// Can only be called after state is SourcesSet. Source const& source(std::string const& _sourceName) const; /// @returns the parsed contract with the supplied name. Throws an exception if the contract /// does not exist. ContractDefinition const& contractDefinition(std::string const& _contractName) const; + /// @returns the metadata JSON as a compact string for the given contract. std::string createMetadata(Contract const& _contract) const; + + /// @returns the metadata CBOR for the given serialised metadata JSON. + static bytes createCBORMetadata(std::string _metadata, bool _experimentalMode); + + /// @returns the computer source mapping string. std::string computeSourceMapping(eth::AssemblyItems const& _items) const; + + /// @returns the contract ABI as a JSON object. + /// This will generate the JSON object and store it in the Contract object if it is not present yet. Json::Value const& contractABI(Contract const&) const; + + /// @returns the Natspec User documentation as a JSON object. + /// This will generate the JSON object and store it in the Contract object if it is not present yet. Json::Value const& natspecUser(Contract const&) const; + + /// @returns the Natspec Developer documentation as a JSON object. + /// This will generate the JSON object and store it in the Contract object if it is not present yet. Json::Value const& natspecDev(Contract const&) const; /// @returns the offset of the entry point of the given function into the list of assembly items @@ -307,13 +329,6 @@ private: FunctionDefinition const& _function ) const; - struct Remapping - { - std::string context; - std::string prefix; - std::string target; - }; - ReadCallback::Callback m_readFile; ReadCallback::Callback m_smtQuery; bool m_optimize = false; @@ -326,8 +341,9 @@ private: std::vector<Remapping> m_remappings; std::map<std::string const, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; - std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::vector<Source const*> m_sourceOrder; + /// This is updated during compilation. + std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::map<std::string const, Contract> m_contracts; ErrorList m_errorList; ErrorReporter m_errorReporter; diff --git a/libsolidity/interface/EVMVersion.h b/libsolidity/interface/EVMVersion.h index b68e1f4e..657727ac 100644 --- a/libsolidity/interface/EVMVersion.h +++ b/libsolidity/interface/EVMVersion.h @@ -75,6 +75,7 @@ public: bool supportsReturndata() const { return *this >= byzantium(); } bool hasStaticCall() const { return *this >= byzantium(); } bool hasBitwiseShifting() const { return *this >= constantinople(); } + bool hasCreate2() const { return *this >= constantinople(); } /// Whether we have to retain the costs for the call opcode itself (false), /// or whether we can just forward easily all remaining gas (true). diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index d1a0030f..fd53587a 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -92,6 +92,12 @@ public: void clear(); + /// @returns true iff there is any error (ignores warnings). + bool hasErrors() const + { + return m_errorCount > 0; + } + private: void error(Error::Type _type, SourceLocation const& _location, diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index a837dce6..ecadd0b7 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -49,9 +49,6 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip case Type::Warning: m_typeName = "Warning"; break; - default: - solAssert(false, ""); - break; } if (!_location.isEmpty()) diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h index 7c66d572..629b8f3f 100644 --- a/libsolidity/interface/Exceptions.h +++ b/libsolidity/interface/Exceptions.h @@ -117,7 +117,7 @@ public: if (occurrences > 32) { infos.resize(32); - _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences."; + _message += " Truncated from " + std::to_string(occurrences) + " to the first 32 occurrences."; } } diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index a532f86e..1f20366e 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -24,7 +24,7 @@ #include <map> #include <functional> #include <memory> -#include <libdevcore/SHA3.h> +#include <libdevcore/Keccak256.h> #include <libevmasm/ControlFlowGraph.h> #include <libevmasm/KnownState.h> #include <libevmasm/PathGasMeter.h> @@ -160,8 +160,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( ); } - PathGasMeter meter(_items, m_evmVersion); - return meter.estimateMax(0, state); + return PathGasMeter::estimateMax(_items, m_evmVersion, 0, state); } GasEstimator::GasConsumption GasEstimator::functionalEstimation( @@ -183,7 +182,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( if (parametersSize > 0) state->feedItem(swapInstruction(parametersSize)); - return PathGasMeter(_items, m_evmVersion).estimateMax(_offset, state); + return PathGasMeter::estimateMax(_items, m_evmVersion, _offset, state); } set<ASTNode const*> GasEstimator::finestNodesAtLocation( diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 7f7084ef..11dde349 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -36,6 +36,19 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) Json::Value doc; Json::Value methods(Json::objectValue); + auto constructorDefinition(_contractDef.constructor()); + if (constructorDefinition) + { + string value = extractDoc(constructorDefinition->annotation().docTags, "notice"); + if (!value.empty()) + // add the constructor, only if we have any documentation to add + methods["constructor"] = Json::Value(value); + } + + string notice = extractDoc(_contractDef.annotation().docTags, "notice"); + if (!notice.empty()) + doc["notice"] = Json::Value(notice); + for (auto const& it: _contractDef.interfaceFunctions()) if (it.second->hasDeclaration()) if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) @@ -65,34 +78,26 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) auto title = extractDoc(_contractDef.annotation().docTags, "title"); if (!title.empty()) doc["title"] = title; + auto dev = extractDoc(_contractDef.annotation().docTags, "dev"); + if (!dev.empty()) + doc["details"] = Json::Value(dev); + + auto constructorDefinition(_contractDef.constructor()); + if (constructorDefinition) + { + Json::Value constructor(devDocumentation(constructorDefinition->annotation().docTags)); + if (!constructor.empty()) + // add the constructor, only if we have any documentation to add + methods["constructor"] = constructor; + } for (auto const& it: _contractDef.interfaceFunctions()) { if (!it.second->hasDeclaration()) continue; - Json::Value method; if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) { - auto dev = extractDoc(fun->annotation().docTags, "dev"); - if (!dev.empty()) - method["details"] = Json::Value(dev); - - auto author = extractDoc(fun->annotation().docTags, "author"); - if (!author.empty()) - method["author"] = author; - - auto ret = extractDoc(fun->annotation().docTags, "return"); - if (!ret.empty()) - method["return"] = ret; - - Json::Value params(Json::objectValue); - auto paramRange = fun->annotation().docTags.equal_range("param"); - for (auto i = paramRange.first; i != paramRange.second; ++i) - params[i->second.paramName] = Json::Value(i->second.content); - - if (!params.empty()) - method["params"] = params; - + Json::Value method(devDocumentation(fun->annotation().docTags)); if (!method.empty()) // add the function, only if we have any documentation to add methods[it.second->externalSignature()] = method; @@ -111,3 +116,31 @@ string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const& value += i->second.content; return value; } + +Json::Value Natspec::devDocumentation(std::multimap<std::string, DocTag> const &_tags) +{ + Json::Value json(Json::objectValue); + auto dev = extractDoc(_tags, "dev"); + if (!dev.empty()) + json["details"] = Json::Value(dev); + + auto author = extractDoc(_tags, "author"); + if (!author.empty()) + json["author"] = author; + + // for constructors, the "return" node will never exist. invalid tags + // will already generate an error within dev::solidity::DocStringAnalyzer. + auto ret = extractDoc(_tags, "return"); + if (!ret.empty()) + json["return"] = ret; + + Json::Value params(Json::objectValue); + auto paramRange = _tags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + params[i->second.paramName] = Json::Value(i->second.content); + + if (!params.empty()) + json["params"] = params; + + return json; +} diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 0701f821..0be4dda2 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -45,7 +45,7 @@ public: /// @param _contractDef The contract definition /// @return A JSON representation of the contract's user documentation static Json::Value userDocumentation(ContractDefinition const& _contractDef); - /// Genereates the Developer's documentation of the contract + /// Generates the Developer's documentation of the contract /// @param _contractDef The contract definition /// @return A JSON representation /// of the contract's developer documentation @@ -54,6 +54,12 @@ public: private: /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name); + + /// Helper-function that will create a json object with dev specific annotations, if present. + /// @param _tags docTags that are used. + /// @return A JSON representation + /// of the contract's developer documentation + static Json::Value devDocumentation(std::multimap<std::string, DocTag> const &_tags); }; } //solidity NS diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 0f014372..865907e2 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -55,8 +55,15 @@ void SourceReferenceFormatter::printSourceLocation(SourceLocation const* _locati } if (line.length() > 150) { - line = " ... " + line.substr(startColumn, locationLength) + " ... "; - startColumn = 5; + int len = line.length(); + line = line.substr(max(0, startColumn - 35), min(startColumn, 35) + min(locationLength + 35, len - startColumn)); + if (startColumn + locationLength + 35 < len) + line += " ..."; + if (startColumn > 35) + { + line = " ... " + line; + startColumn = 40; + } endColumn = startColumn + locationLength; } diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index ee9b1440..c8b03a94 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -25,7 +25,7 @@ #include <libsolidity/ast/ASTJsonConverter.h> #include <libevmasm/Instruction.h> #include <libdevcore/JSON.h> -#include <libdevcore/SHA3.h> +#include <libdevcore/Keccak256.h> #include <boost/algorithm/string.hpp> @@ -117,7 +117,7 @@ bool hashMatchesContent(string const& _hash, string const& _content) { return dev::h256(_hash) == dev::keccak256(_content); } - catch (dev::BadHexCharacter) + catch (dev::BadHexCharacter const&) { return false; } @@ -280,6 +280,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) for (auto const& url: sources[sourceName]["urls"]) { + if (!url.isString()) + return formatFatalError("JSONError", "URL must be a string."); ReadCallback::Result result = m_readFile(url.asString()); if (result.success) { @@ -320,21 +322,45 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (settings.isMember("evmVersion")) { - boost::optional<EVMVersion> version = EVMVersion::fromString(settings.get("evmVersion", {}).asString()); + if (!settings["evmVersion"].isString()) + return formatFatalError("JSONError", "evmVersion must be a string."); + boost::optional<EVMVersion> version = EVMVersion::fromString(settings["evmVersion"].asString()); if (!version) return formatFatalError("JSONError", "Invalid EVM version requested."); m_compilerStack.setEVMVersion(*version); } - vector<string> remappings; + vector<CompilerStack::Remapping> remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) - remappings.push_back(remapping.asString()); + { + if (!remapping.isString()) + return formatFatalError("JSONError", "Remapping entry must be a string."); + if (auto r = CompilerStack::parseRemapping(remapping.asString())) + remappings.emplace_back(std::move(*r)); + else + return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); + } m_compilerStack.setRemappings(remappings); - Json::Value optimizerSettings = settings.get("optimizer", Json::Value()); - bool const optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool(); - unsigned const optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt(); - m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); + if (settings.isMember("optimizer")) + { + Json::Value optimizerSettings = settings["optimizer"]; + if (optimizerSettings.isMember("enabled")) + { + if (!optimizerSettings["enabled"].isBool()) + return formatFatalError("JSONError", "The \"enabled\" setting must be a boolean."); + + bool const optimize = optimizerSettings["enabled"].asBool(); + unsigned optimizeRuns = 200; + if (optimizerSettings.isMember("runs")) + { + if (!optimizerSettings["runs"].isUInt()) + return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); + optimizeRuns = optimizerSettings["runs"].asUInt(); + } + m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); + } + } map<string, h160> libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); @@ -344,9 +370,11 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { auto const& jsonSourceName = jsonLibraries[sourceName]; if (!jsonSourceName.isObject()) - return formatFatalError("JSONError", "library entry is not a JSON object."); + return formatFatalError("JSONError", "Library entry is not a JSON object."); for (auto const& library: jsonSourceName.getMemberNames()) { + if (!jsonSourceName[library].isString()) + return formatFatalError("JSONError", "Library address must be a string."); string address = jsonSourceName[library].asString(); if (!boost::starts_with(address, "0x")) @@ -366,7 +394,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // @TODO use libraries only for the given source libraries[library] = h160(address); } - catch (dev::BadHexCharacter) + catch (dev::BadHexCharacter const&) { return formatFatalError( "JSONError", @@ -481,7 +509,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; /// Inconsistent state - stop here to receive error reports from users - if (!compilationSuccess && (errors.size() == 0)) + if (!compilationSuccess && errors.empty()) return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); Json::Value output = Json::objectValue; @@ -567,7 +595,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) return output; } -Json::Value StandardCompiler::compile(Json::Value const& _input) +Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept { try { @@ -591,7 +619,7 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) } } -string StandardCompiler::compile(string const& _input) +string StandardCompiler::compile(string const& _input) noexcept { Json::Value input; string errors; @@ -600,9 +628,9 @@ string StandardCompiler::compile(string const& _input) if (!jsonParseStrict(_input, input, &errors)) return jsonCompactPrint(formatFatalError("JSONError", errors)); } - catch(...) + catch (...) { - return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}"; + return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}"; } // cout << "Input: " << input.toStyledString() << endl; @@ -613,8 +641,8 @@ string StandardCompiler::compile(string const& _input) { return jsonCompactPrint(output); } - catch(...) + catch (...) { - return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}"; + 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 index 11a0b4c2..fc9c3a59 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -31,7 +31,7 @@ namespace solidity { /** - * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput. + * Standard JSON compiler interface, which expects a JSON input and returns a JSON output. * See docs/using-the-compiler#compiler-input-and-output-json-description. */ class StandardCompiler: boost::noncopyable @@ -47,10 +47,10 @@ public: /// 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); + Json::Value compile(Json::Value const& _input) noexcept; /// 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); + std::string compile(std::string const& _input) noexcept; private: Json::Value compileInternal(Json::Value const& _input); diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index a35bfd29..b5f68ce8 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -55,13 +55,13 @@ bytes dev::solidity::binaryVersion() ret = ret * 10 + (VersionString[i] - '0'); return ret; }; - ret.push_back(byte(parseDecimal())); + ret.push_back(uint8_t(parseDecimal())); solAssert(i < VersionString.size() && VersionString[i] == '.', ""); ++i; - ret.push_back(byte(parseDecimal())); + ret.push_back(uint8_t(parseDecimal())); solAssert(i < VersionString.size() && VersionString[i] == '.', ""); ++i; - ret.push_back(byte(parseDecimal())); + ret.push_back(uint8_t(parseDecimal())); solAssert(i < VersionString.size() && (VersionString[i] == '-' || VersionString[i] == '+'), ""); ++i; size_t commitpos = VersionString.find("commit."); |