diff options
author | chriseth <chris@ethereum.org> | 2017-07-03 20:52:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-03 20:52:29 +0800 |
commit | 76d3b7c5a160e1f550c710e6850ee6f116142ca1 (patch) | |
tree | 93c96f7073617b4f56c8c355cdc30701aec4818b /libsolidity | |
parent | 78969364608ba60d1654f4d1738886d13112b6cd (diff) | |
parent | 2222ddecf49b5b901f63b9e7449ee76c9f51c47a (diff) | |
download | dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar.gz dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar.bz2 dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar.lz dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar.xz dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.tar.zst dexon-solidity-76d3b7c5a160e1f550c710e6850ee6f116142ca1.zip |
Merge pull request #2510 from ethereum/develop
Version 0.4.12
Diffstat (limited to 'libsolidity')
73 files changed, 3131 insertions, 3379 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index bcc47e5a..2342f0f9 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -7,10 +7,12 @@ aux_source_directory(formal SRC_LIST) aux_source_directory(interface SRC_LIST) aux_source_directory(parsing SRC_LIST) aux_source_directory(inlineasm SRC_LIST) +# Until we have a clear separation, libjulia has to be included here +aux_source_directory(../libjulia SRC_LIST) set(EXECUTABLE solidity) -file(GLOB HEADERS "*/*.h") +file(GLOB HEADERS "*/*.h" "../libjulia/backends/evm/*") include_directories(BEFORE ..) add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 58261144..9a846b31 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -23,6 +23,7 @@ #include <libsolidity/analysis/DocStringAnalyser.h> #include <libsolidity/ast/AST.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/parsing/DocStringParser.h> using namespace std; @@ -39,7 +40,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) bool DocStringAnalyser::visit(ContractDefinition const& _node) { - static const set<string> validTags = set<string>{"author", "title", "dev", "notice", "why3"}; + static const set<string> validTags = set<string>{"author", "title", "dev", "notice"}; parseDocStrings(_node, _node.annotation(), validTags, "contracts"); return true; @@ -65,16 +66,6 @@ bool DocStringAnalyser::visit(EventDefinition const& _node) return true; } -bool DocStringAnalyser::visitNode(ASTNode const& _node) -{ - if (auto node = dynamic_cast<Statement const*>(&_node)) - { - static const set<string> validTags = {"why3"}; - parseDocStrings(*node, node->annotation(), validTags, "statements"); - } - return true; -} - void DocStringAnalyser::handleCallable( CallableDeclaration const& _callable, Documented const& _node, @@ -110,7 +101,7 @@ void DocStringAnalyser::parseDocStrings( DocStringParser parser; if (_node.documentation() && !_node.documentation()->empty()) { - if (!parser.parse(*_node.documentation(), m_errors)) + if (!parser.parse(*_node.documentation(), m_errorReporter)) m_errorOccured = true; _annotation.docTags = parser.tags(); } @@ -121,8 +112,6 @@ void DocStringAnalyser::parseDocStrings( void DocStringAnalyser::appendError(string const& _description) { - auto err = make_shared<Error>(Error::Type::DocstringParsingError); - *err << errinfo_comment(_description); - m_errors.push_back(err); m_errorOccured = true; + m_errorReporter.docstringParsingError(_description); } diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index bfc8befc..158b4060 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -30,6 +30,8 @@ namespace dev namespace solidity { +class ErrorReporter; + /** * Parses and analyses the doc strings. * Stores the parsing results in the AST annotations and reports errors. @@ -37,7 +39,7 @@ namespace solidity class DocStringAnalyser: private ASTConstVisitor { public: - DocStringAnalyser(ErrorList& _errors): m_errors(_errors) {} + DocStringAnalyser(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool analyseDocStrings(SourceUnit const& _sourceUnit); private: @@ -46,8 +48,6 @@ private: virtual bool visit(ModifierDefinition const& _modifier) override; virtual bool visit(EventDefinition const& _event) override; - virtual bool visitNode(ASTNode const&) override; - void handleCallable( CallableDeclaration const& _callable, Documented const& _node, @@ -64,7 +64,7 @@ private: void appendError(std::string const& _description); bool m_errorOccured = false; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 336dc894..aac90311 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -24,7 +24,9 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/TypeChecker.h> -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ErrorReporter.h> + +#include <boost/algorithm/string.hpp> using namespace std; @@ -36,10 +38,10 @@ namespace solidity NameAndTypeResolver::NameAndTypeResolver( vector<Declaration const*> const& _globals, map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, - ErrorList& _errors + ErrorReporter& _errorReporter ) : m_scopes(_scopes), - m_errors(_errors) + m_errorReporter(_errorReporter) { if (!m_scopes[nullptr]) m_scopes[nullptr].reset(new DeclarationContainer()); @@ -52,11 +54,11 @@ bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode con // The helper registers all declarations in m_scopes as a side-effect of its construction. try { - DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors, _currentScope); + DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope); } catch (FatalError const&) { - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. return false; } @@ -73,7 +75,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So string const& path = imp->annotation().absolutePath; if (!_sourceUnits.count(path)) { - reportDeclarationError( + m_errorReporter.declarationError( imp->location(), "Import \"" + path + "\" (referenced as \"" + imp->path() + "\") not found." ); @@ -88,7 +90,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So auto declarations = scope->second->resolveName(alias.first->name(), false); if (declarations.empty()) { - reportDeclarationError( + m_errorReporter.declarationError( imp->location(), "Declaration \"" + alias.first->name() + @@ -106,7 +108,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So ASTString const* name = alias.second ? alias.second.get() : &declaration->name(); if (!target.registerDeclaration(*declaration, name)) { - reportDeclarationError( + m_errorReporter.declarationError( imp->location(), "Identifier \"" + *name + "\" already declared." ); @@ -119,7 +121,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So for (auto const& declaration: nameAndDeclaration.second) if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first)) { - reportDeclarationError( + m_errorReporter.declarationError( imp->location(), "Identifier \"" + nameAndDeclaration.first + "\" already declared." ); @@ -137,7 +139,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi } catch (FatalError const&) { - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. return false; } @@ -152,7 +154,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) } catch (FatalError const&) { - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. return false; } @@ -214,7 +216,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) if (!parameter) - reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context."); + m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context."); if (uniqueFunctions.end() == find_if( uniqueFunctions.begin(), @@ -232,6 +234,26 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( return uniqueFunctions; } +void NameAndTypeResolver::warnVariablesNamedLikeInstructions() +{ + for (auto const& instruction: c_instructions) + { + string const instructionName{boost::algorithm::to_lower_copy(instruction.first)}; + auto declarations = nameFromCurrentScope(instructionName); + for (Declaration const* const declaration: declarations) + { + solAssert(!!declaration, ""); + if (dynamic_cast<MagicVariableDeclaration const* const>(declaration)) + // Don't warn the user for what the user did not. + continue; + m_errorReporter.warning( + declaration->location(), + "Variable is shadowed in inline assembly by an instruction of the same name" + ); + } + } +} + bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode) { if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node)) @@ -290,7 +312,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res { if (m_scopes.count(&_node)) m_currentScope = m_scopes[&_node].get(); - return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); + return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node); } } @@ -328,11 +350,10 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) secondDeclarationLocation = declaration->location(); } - reportDeclarationError( + m_errorReporter.declarationError( secondDeclarationLocation, - "Identifier already declared.", - firstDeclarationLocation, - "The previous declaration is here:" + SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation), + "Identifier already declared." ); } } @@ -347,19 +368,19 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) UserDefinedTypeName const& baseName = baseSpecifier->name(); auto base = dynamic_cast<ContractDefinition const*>(baseName.annotation().referencedDeclaration); if (!base) - reportFatalTypeError(baseName.createTypeError("Contract expected.")); + m_errorReporter.fatalTypeError(baseName.location(), "Contract expected."); // "push_front" has the effect that bases mentioned later can overwrite members of bases // mentioned earlier input.back().push_front(base); vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts; if (basesBases.empty()) - reportFatalTypeError(baseName.createTypeError("Definition of base has to precede definition of derived contract")); + m_errorReporter.fatalTypeError(baseName.location(), "Definition of base has to precede definition of derived contract"); input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end())); } input.back().push_front(&_contract); vector<ContractDefinition const*> result = cThreeMerge(input); if (result.empty()) - reportFatalTypeError(_contract.createTypeError("Linearization of inheritance graph impossible")); + m_errorReporter.fatalTypeError(_contract.location(), "Linearization of inheritance graph impossible"); _contract.annotation().linearizedBaseContracts = result; _contract.annotation().contractDependencies.insert(result.begin() + 1, result.end()); } @@ -415,61 +436,15 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer return result; } -void NameAndTypeResolver::reportDeclarationError( - SourceLocation _sourceLoction, - string const& _description, - SourceLocation _secondarySourceLocation, - string const& _secondaryDescription -) -{ - auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error? - *err << - errinfo_sourceLocation(_sourceLoction) << - errinfo_comment(_description) << - errinfo_secondarySourceLocation( - SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation) - ); - - m_errors.push_back(err); -} - -void NameAndTypeResolver::reportDeclarationError(SourceLocation _sourceLocation, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error? - *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description); - - m_errors.push_back(err); -} - -void NameAndTypeResolver::reportFatalDeclarationError( - SourceLocation _sourceLocation, - string const& _description -) -{ - reportDeclarationError(_sourceLocation, _description); - BOOST_THROW_EXCEPTION(FatalError()); -} - -void NameAndTypeResolver::reportTypeError(Error const& _e) -{ - m_errors.push_back(make_shared<Error>(_e)); -} - -void NameAndTypeResolver::reportFatalTypeError(Error const& _e) -{ - reportTypeError(_e); - BOOST_THROW_EXCEPTION(FatalError()); -} - DeclarationRegistrationHelper::DeclarationRegistrationHelper( map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, ASTNode& _astRoot, - ErrorList& _errors, + ErrorReporter& _errorReporter, ASTNode const* _currentScope ): m_scopes(_scopes), m_currentScope(_currentScope), - m_errors(_errors) + m_errorReporter(_errorReporter) { _astRoot.accept(*this); solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); @@ -633,11 +608,10 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio secondDeclarationLocation = _declaration.location(); } - declarationError( + m_errorReporter.declarationError( secondDeclarationLocation, - "Identifier already declared.", - firstDeclarationLocation, - "The previous declaration is here:" + SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation), + "Identifier already declared." ); } @@ -665,40 +639,5 @@ string DeclarationRegistrationHelper::currentCanonicalName() const return ret; } -void DeclarationRegistrationHelper::declarationError( - SourceLocation _sourceLocation, - string const& _description, - SourceLocation _secondarySourceLocation, - string const& _secondaryDescription -) -{ - auto err = make_shared<Error>(Error::Type::DeclarationError); - *err << - errinfo_sourceLocation(_sourceLocation) << - errinfo_comment(_description) << - errinfo_secondarySourceLocation( - SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation) - ); - - m_errors.push_back(err); -} - -void DeclarationRegistrationHelper::declarationError(SourceLocation _sourceLocation, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::DeclarationError); - *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description); - - m_errors.push_back(err); -} - -void DeclarationRegistrationHelper::fatalDeclarationError( - SourceLocation _sourceLocation, - string const& _description -) -{ - declarationError(_sourceLocation, _description); - BOOST_THROW_EXCEPTION(FatalError()); -} - } } diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 038a887b..84628778 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -35,6 +35,8 @@ namespace dev namespace solidity { +class ErrorReporter; + /** * Resolves name references, typenames and sets the (explicitly given) types for all variable * declarations. @@ -48,7 +50,7 @@ public: NameAndTypeResolver( std::vector<Declaration const*> const& _globals, std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes, - ErrorList& _errors + ErrorReporter& _errorReporter ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. @@ -88,6 +90,9 @@ public: std::vector<Declaration const*> const& _declarations ); + /// Generate and store warnings about variables that are named like instructions. + void warnVariablesNamedLikeInstructions(); + private: /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); @@ -103,24 +108,6 @@ private: template <class _T> static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge); - // creates the Declaration error and adds it in the errors list - void reportDeclarationError( - SourceLocation _sourceLoction, - std::string const& _description, - SourceLocation _secondarySourceLocation, - std::string const& _secondaryDescription - ); - // creates the Declaration error and adds it in the errors list - void reportDeclarationError(SourceLocation _sourceLocation, std::string const& _description); - // creates the Declaration error and adds it in the errors list and throws FatalError - void reportFatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description); - - // creates the Declaration error and adds it in the errors list - void reportTypeError(Error const& _e); - // creates the Declaration error and adds it in the errors list and throws FatalError - void reportFatalTypeError(Error const& _e); - - /// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration, /// where nullptr denotes the global scope. Note that structs are not scope since they do /// not contain code. @@ -128,7 +115,7 @@ private: std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes; DeclarationContainer* m_currentScope = nullptr; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; }; /** @@ -145,7 +132,7 @@ public: DeclarationRegistrationHelper( std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes, ASTNode& _astRoot, - ErrorList& _errors, + ErrorReporter& _errorReporter, ASTNode const* _currentScope = nullptr ); @@ -175,23 +162,11 @@ private: /// @returns the canonical name of the current scope. std::string currentCanonicalName() const; - // creates the Declaration error and adds it in the errors list - void declarationError( - SourceLocation _sourceLocation, - std::string const& _description, - SourceLocation _secondarySourceLocation, - std::string const& _secondaryDescription - ); - - // creates the Declaration error and adds it in the errors list - void declarationError(SourceLocation _sourceLocation, std::string const& _description); - // creates the Declaration error and adds it in the errors list and throws FatalError - void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description); std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes; ASTNode const* m_currentScope = nullptr; VariableScope* m_currentFunction = nullptr; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index e8da3ca4..fbc72e52 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -18,6 +18,7 @@ #include <libsolidity/analysis/PostTypeChecker.h> #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/SemVerHandler.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> #include <boost/range/adaptor/map.hpp> @@ -32,17 +33,7 @@ using namespace dev::solidity; bool PostTypeChecker::check(ASTNode const& _astRoot) { _astRoot.accept(*this); - return Error::containsOnlyWarnings(m_errors); -} - -void PostTypeChecker::typeError(SourceLocation const& _location, std::string const& _description) -{ - auto err = make_shared<Error>(Error::Type::TypeError); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); - - m_errors.push_back(err); + return Error::containsOnlyWarnings(m_errorReporter.errors()); } bool PostTypeChecker::visit(ContractDefinition const&) @@ -57,7 +48,11 @@ void PostTypeChecker::endVisit(ContractDefinition const&) solAssert(!m_currentConstVariable, ""); for (auto declaration: m_constVariables) if (auto identifier = findCycle(declaration)) - typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + "."); + m_errorReporter.typeError( + declaration->location(), + "The value of the constant " + declaration->name() + + " has a cyclic dependency via " + identifier->name() + "." + ); m_constVariables.clear(); m_constVariableDependencies.clear(); diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index 8774f413..dbdf50e0 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -28,6 +28,8 @@ namespace dev namespace solidity { +class ErrorReporter; + /** * This module performs analyses on the AST that are done after type checking and assignments of types: * - whether there are circular references in constant state variables @@ -37,7 +39,7 @@ class PostTypeChecker: private ASTConstVisitor { public: /// @param _errors the reference to the list of errors and warnings to add them found during type checking. - PostTypeChecker(ErrorList& _errors): m_errors(_errors) {} + PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool check(ASTNode const& _astRoot); @@ -55,10 +57,10 @@ private: VariableDeclaration const* findCycle( VariableDeclaration const* _startingFrom, - std::set<VariableDeclaration const*> const& _seen = {} + std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{} ); - ErrorList& m_errors; + ErrorReporter& m_errorReporter; VariableDeclaration const* m_currentConstVariable = nullptr; std::vector<VariableDeclaration const*> m_constVariables; ///< Required for determinism. diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 9433976a..2a5f27df 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -28,6 +28,7 @@ #include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/interface/ErrorReporter.h> #include <boost/algorithm/string.hpp> @@ -111,7 +112,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) case VariableDeclaration::Visibility::External: break; default: - typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); } if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) @@ -161,13 +162,16 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) { + m_resolver.warnVariablesNamedLikeInstructions(); + // Errors created in this stage are completely ignored because we do not yet know // the type and size of external identifiers, which would result in false errors. // The only purpose of this step is to fill the inline assembly annotation with // external references. - ErrorList errorsIgnored; - assembly::ExternalIdentifierAccess::Resolver resolver = - [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) { + ErrorList errors; + ErrorReporter errorsIgnored(errors); + julia::ExternalIdentifierAccess::Resolver resolver = + [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool _crossesFunctionBoundary) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot"); bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset"); @@ -186,6 +190,12 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) } if (declarations.size() != 1) return size_t(-1); + if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front())) + if (var->isLocalVariable() && _crossesFunctionBoundary) + { + declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function."); + return size_t(-1); + } _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot; _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset; _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front(); @@ -194,7 +204,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations()); + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations()); return false; } @@ -303,29 +313,25 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description) { - auto err = make_shared<Error>(Error::Type::TypeError); - *err << errinfo_sourceLocation(_location) << errinfo_comment(_description); m_errorOccurred = true; - m_errors.push_back(err); + m_errorReporter.typeError(_location, _description); } void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description) { - typeError(_location, _description); - BOOST_THROW_EXCEPTION(FatalError()); + m_errorOccurred = true; + m_errorReporter.fatalTypeError(_location, _description); } void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description) { - auto err = make_shared<Error>(Error::Type::DeclarationError); - *err << errinfo_sourceLocation(_location) << errinfo_comment(_description); m_errorOccurred = true; - m_errors.push_back(err); + m_errorReporter.declarationError(_location, _description); } void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description) { - declarationError(_location, _description); - BOOST_THROW_EXCEPTION(FatalError()); + m_errorOccurred = true; + m_errorReporter.fatalDeclarationError(_location, _description); } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index dce343d3..fef2e73f 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -33,6 +33,7 @@ namespace dev namespace solidity { +class ErrorReporter; class NameAndTypeResolver; /** @@ -43,11 +44,11 @@ class ReferencesResolver: private ASTConstVisitor { public: ReferencesResolver( - ErrorList& _errors, + ErrorReporter& _errorReporter, NameAndTypeResolver& _resolver, bool _resolveInsideCode = false ): - m_errors(_errors), + m_errorReporter(_errorReporter), m_resolver(_resolver), m_resolveInsideCode(_resolveInsideCode) {} @@ -74,16 +75,16 @@ private: /// Adds a new error to the list of errors. void typeError(SourceLocation const& _location, std::string const& _description); - /// Adds a new error to the list of errors and throws to abort type checking. + /// Adds a new error to the list of errors and throws to abort reference resolving. void fatalTypeError(SourceLocation const& _location, std::string const& _description); /// Adds a new error to the list of errors. - void declarationError(const SourceLocation& _location, std::string const& _description); + void declarationError(SourceLocation const& _location, std::string const& _description); - /// Adds a new error to the list of errors and throws to abort type checking. - void fatalDeclarationError(const SourceLocation& _location, std::string const& _description); + /// Adds a new error to the list of errors and throws to abort reference resolving. + void fatalDeclarationError(SourceLocation const& _location, std::string const& _description); - ErrorList& m_errors; + ErrorReporter& m_errorReporter; NameAndTypeResolver& m_resolver; /// Stack of return parameters. std::vector<ParameterList const*> m_returnParameters; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 369376fa..b1b31163 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -21,18 +21,18 @@ */ #include <libsolidity/analysis/StaticAnalyzer.h> -#include <memory> #include <libsolidity/ast/AST.h> +#include <libsolidity/interface/ErrorReporter.h> +#include <memory> using namespace std; using namespace dev; using namespace dev::solidity; - bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); - return Error::containsOnlyWarnings(m_errors); + return Error::containsOnlyWarnings(m_errorReporter.errors()); } bool StaticAnalyzer::visit(ContractDefinition const& _contract) @@ -63,7 +63,7 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&) m_nonPayablePublic = false; for (auto const& var: m_localVarUseCount) if (var.second == 0) - warning(var.first->location(), "Unused local variable"); + m_errorReporter.warning(var.first->location(), "Unused local variable"); m_localVarUseCount.clear(); } @@ -105,7 +105,11 @@ bool StaticAnalyzer::visit(Return const& _return) bool StaticAnalyzer::visit(ExpressionStatement const& _statement) { if (_statement.expression().annotation().isPure) - warning(_statement.location(), "Statement has no effect."); + m_errorReporter.warning( + _statement.location(), + "Statement has no effect." + ); + return true; } @@ -114,17 +118,36 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) if (m_nonPayablePublic && !m_library) if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value") - warning(_memberAccess.location(), "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?"); + m_errorReporter.warning( + _memberAccess.location(), + "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?" + ); + + if (_memberAccess.memberName() == "callcode") + if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) + if (type->kind() == FunctionType::Kind::BareCallCode) + m_errorReporter.warning( + _memberAccess.location(), + "\"callcode\" has been deprecated in favour of \"delegatecall\"." + ); return true; } -void StaticAnalyzer::warning(SourceLocation const& _location, string const& _description) +bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly) { - auto err = make_shared<Error>(Error::Type::Warning); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); + if (!m_currentFunction) + return true; - m_errors.push_back(err); + for (auto const& ref: _inlineAssembly.annotation().externalReferences) + { + if (auto var = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) + { + solAssert(!var->name().empty(), ""); + if (var->isLocalVariable()) + m_localVarUseCount[var] += 1; + } + } + + return true; } diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index ab72e7d9..cd6913b5 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -44,15 +44,13 @@ class StaticAnalyzer: private ASTConstVisitor { public: /// @param _errors the reference to the list of errors and warnings to add them found during static analysis. - explicit StaticAnalyzer(ErrorList& _errors): m_errors(_errors) {} + explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} /// Performs static analysis on the given source unit and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings bool analyze(SourceUnit const& _sourceUnit); private: - /// Adds a new warning to the list of errors. - void warning(SourceLocation const& _location, std::string const& _description); virtual bool visit(ContractDefinition const& _contract) override; virtual void endVisit(ContractDefinition const& _contract) override; @@ -65,8 +63,9 @@ private: virtual bool visit(Identifier const& _identifier) override; virtual bool visit(Return const& _return) override; virtual bool visit(MemberAccess const& _memberAccess) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; /// Flag that indicates whether the current contract definition is a library. bool m_library = false; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 94e82a87..02e2fdcf 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -19,6 +19,7 @@ #include <memory> #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/SemVerHandler.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> using namespace std; @@ -29,27 +30,7 @@ using namespace dev::solidity; bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) { _astRoot.accept(*this); - return Error::containsOnlyWarnings(m_errors); -} - -void SyntaxChecker::warning(SourceLocation const& _location, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::Warning); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); - - m_errors.push_back(err); -} - -void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description) -{ - auto err = make_shared<Error>(Error::Type::SyntaxError); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); - - m_errors.push_back(err); + return Error::containsOnlyWarnings(m_errorReporter.errors()); } bool SyntaxChecker::visit(SourceUnit const&) @@ -74,11 +55,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) to_string(recommendedVersion.patch()); string(";\""); - auto err = make_shared<Error>(Error::Type::Warning); - *err << - errinfo_sourceLocation(_sourceUnit.location()) << - errinfo_comment(errorString); - m_errors.push_back(err); + m_errorReporter.warning(_sourceUnit.location(), errorString); } } @@ -87,7 +64,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) solAssert(!_pragma.tokens().empty(), ""); solAssert(_pragma.tokens().size() == _pragma.literals().size(), ""); if (_pragma.tokens()[0] != Token::Identifier || _pragma.literals()[0] != "solidity") - syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); + m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); else { vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); @@ -96,7 +73,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) auto matchExpression = parser.parse(); SemVerVersion currentVersion{string(VersionString)}; if (!matchExpression.matches(currentVersion)) - syntaxError( + m_errorReporter.syntaxError( _pragma.location(), "Source file requires different compiler version (current compiler is " + string(VersionString) + " - note that nightly builds are considered to be " @@ -116,7 +93,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&) void SyntaxChecker::endVisit(ModifierDefinition const& _modifier) { if (!m_placeholderFound) - syntaxError(_modifier.body().location(), "Modifier body does not contain '_'."); + m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'."); m_placeholderFound = false; } @@ -146,7 +123,7 @@ bool SyntaxChecker::visit(Continue const& _continueStatement) { if (m_inLoopDepth <= 0) // we're not in a for/while loop, report syntax error - syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop."); + m_errorReporter.syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop."); return true; } @@ -154,14 +131,14 @@ bool SyntaxChecker::visit(Break const& _breakStatement) { if (m_inLoopDepth <= 0) // we're not in a for/while loop, report syntax error - syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop."); + m_errorReporter.syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop."); return true; } bool SyntaxChecker::visit(UnaryOperation const& _operation) { if (_operation.getOperator() == Token::Add) - warning(_operation.location(), "Use of unary + is deprecated."); + m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated."); return true; } @@ -171,3 +148,15 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) return true; } +bool SyntaxChecker::visit(FunctionTypeName const& _node) +{ + for (auto const& decl: _node.parameterTypeList()->parameters()) + if (!decl->name().empty()) + m_errorReporter.warning(decl->location(), "Naming function type parameters is deprecated."); + + for (auto const& decl: _node.returnParameterTypeList()->parameters()) + if (!decl->name().empty()) + m_errorReporter.warning(decl->location(), "Naming function type return parameters is deprecated."); + + return true; +} diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 8d7dcdd3..ec6ac434 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -38,14 +38,11 @@ class SyntaxChecker: private ASTConstVisitor { public: /// @param _errors the reference to the list of errors and warnings to add them found during type checking. - SyntaxChecker(ErrorList& _errors): m_errors(_errors) {} + SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool checkSyntax(ASTNode const& _astRoot); private: - /// Adds a new error to the list of errors. - void warning(SourceLocation const& _location, std::string const& _description); - void syntaxError(SourceLocation const& _location, std::string const& _description); virtual bool visit(SourceUnit const& _sourceUnit) override; virtual void endVisit(SourceUnit const& _sourceUnit) override; @@ -66,7 +63,9 @@ private: virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; - ErrorList& m_errors; + virtual bool visit(FunctionTypeName const& _node) override; + + ErrorReporter& m_errorReporter; /// Flag that indicates whether a function modifier actually contains '_'. bool m_placeholderFound = false; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 38cdc1f8..1563467c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -22,11 +22,13 @@ #include <libsolidity/analysis/TypeChecker.h> #include <memory> +#include <boost/algorithm/string/predicate.hpp> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> #include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/interface/ErrorReporter.h> using namespace std; using namespace dev; @@ -43,10 +45,10 @@ bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { // We got a fatal error which required to stop further type checking, but we can // continue normally from here. - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. } - return Error::containsOnlyWarnings(m_errors); + return Error::containsOnlyWarnings(m_errorReporter.errors()); } TypePointer const& TypeChecker::type(Expression const& _expression) const @@ -81,11 +83,11 @@ bool TypeChecker::visit(ContractDefinition const& _contract) if (function) { if (!function->returnParameters().empty()) - typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); + m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); if (function->isDeclaredConst()) - typeError(function->location(), "Constructor cannot be defined as constant."); + m_errorReporter.typeError(function->location(), "Constructor cannot be defined as constant."); if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal) - typeError(function->location(), "Constructor must be public or internal."); + m_errorReporter.typeError(function->location(), "Constructor must be public or internal."); } FunctionDefinition const* fallbackFunction = nullptr; @@ -95,21 +97,19 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { if (fallbackFunction) { - auto err = make_shared<Error>(Error::Type::DeclarationError); - *err << errinfo_comment("Only one fallback function is allowed."); - m_errors.push_back(err); + m_errorReporter.declarationError(function->location(), "Only one fallback function is allowed."); } else { fallbackFunction = function; if (_contract.isLibrary()) - typeError(fallbackFunction->location(), "Libraries cannot have fallback functions."); + m_errorReporter.typeError(fallbackFunction->location(), "Libraries cannot have fallback functions."); if (fallbackFunction->isDeclaredConst()) - typeError(fallbackFunction->location(), "Fallback function cannot be declared constant."); + m_errorReporter.typeError(fallbackFunction->location(), "Fallback function cannot be declared constant."); if (!fallbackFunction->parameters().empty()) - typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters."); + m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters."); if (!fallbackFunction->returnParameters().empty()) - typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values."); + m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values."); } } if (!function->isImplemented()) @@ -127,7 +127,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { FixedHash<4> const& hash = it.first; if (hashes.count(hash)) - typeError( + m_errorReporter.typeError( _contract.location(), string("Function signature hash collision for ") + it.second->externalSignature() ); @@ -156,12 +156,11 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con for (; it != functions[_contract.name()].end(); ++it) ssl.append("Another declaration is here:", (*it)->location()); - auto err = make_shared<Error>(Error(Error::Type::DeclarationError)); - *err << - errinfo_sourceLocation(functions[_contract.name()].front()->location()) << - errinfo_comment("More than one constructor defined.") << - errinfo_secondarySourceLocation(ssl); - m_errors.push_back(err); + m_errorReporter.declarationError( + functions[_contract.name()].front()->location(), + ssl, + "More than one constructor defined." + ); } for (auto const& it: functions) { @@ -170,13 +169,14 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con for (size_t j = i + 1; j < overloads.size(); ++j) if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j]))) { - auto err = make_shared<Error>(Error(Error::Type::DeclarationError)); - *err << - errinfo_sourceLocation(overloads[j]->location()) << - errinfo_comment("Function with same name and arguments defined twice.") << - errinfo_secondarySourceLocation(SecondarySourceLocation().append( - "Other declaration is here:", overloads[i]->location())); - m_errors.push_back(err); + m_errorReporter.declarationError( + overloads[j]->location(), + SecondarySourceLocation().append( + "Other declaration is here:", + overloads[i]->location() + ), + "Function with same name and arguments defined twice." + ); } } } @@ -213,7 +213,7 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont else if (it->second) { if (!function->isImplemented()) - typeError(function->location(), "Redeclaring an already implemented function as abstract"); + m_errorReporter.typeError(function->location(), "Redeclaring an already implemented function as abstract"); } else if (function->isImplemented()) it->second = true; @@ -285,7 +285,7 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr continue; // constructors can neither be overridden nor override anything string const& name = function->name(); if (modifiers.count(name)) - typeError(modifiers[name]->location(), "Override changes function to modifier."); + m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier."); FunctionType functionType(*function); // function should not change the return type for (FunctionDefinition const* overriding: functions[name]) @@ -299,7 +299,7 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr overriding->isPayable() != function->isPayable() || overridingType != functionType ) - typeError(overriding->location(), "Override changes extended function signature."); + m_errorReporter.typeError(overriding->location(), "Override changes extended function signature."); } functions[name].push_back(function); } @@ -310,9 +310,9 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr if (!override) override = modifier; else if (ModifierType(*override) != ModifierType(*modifier)) - typeError(override->location(), "Override changes modifier signature."); + m_errorReporter.typeError(override->location(), "Override changes modifier signature."); if (!functions[name].empty()) - typeError(override->location(), "Override changes modifier to function."); + m_errorReporter.typeError(override->location(), "Override changes modifier to function."); } } } @@ -347,7 +347,7 @@ void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _co for (size_t i = 0; i < it.second.size(); ++i) for (size_t j = i + 1; j < it.second.size(); ++j) if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second)) - typeError( + m_errorReporter.typeError( it.second[j].first->location(), "Function overload clash during conversion to external types for arguments." ); @@ -357,11 +357,40 @@ void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) { solAssert(_contract.isLibrary(), ""); if (!_contract.baseContracts().empty()) - typeError(_contract.location(), "Library is not allowed to inherit."); + m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit."); for (auto const& var: _contract.stateVariables()) if (!var->isConstant()) - typeError(var->location(), "Library cannot have non-constant state variables"); + m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); +} + +void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) +{ + TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide())); + TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide())); + + bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front()); + size_t storageToStorageCopies = 0; + size_t toStorageCopies = 0; + for (size_t i = 0; i < lhs.components().size(); ++i) + { + ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get()); + if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer()) + continue; + size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i); + solAssert(rhsPos < rhs.components().size(), ""); + toStorageCopies++; + if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage)) + storageToStorageCopies++; + } + if (storageToStorageCopies >= 1 && toStorageCopies >= 2) + m_errorReporter.warning( + _assignment.location(), + "This assignment performs two copies to storage. Since storage copies do not first " + "copy to a temporary location, one of them might be overwritten before the second " + "is executed and thus may have unexpected effects. It is safer to perform the copies " + "separately or assign to storage pointers first." + ); } void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) @@ -370,16 +399,16 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) solAssert(base, "Base contract not available."); if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) - typeError(_inheritance.location(), "Interfaces cannot inherit."); + m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit."); if (base->isLibrary()) - typeError(_inheritance.location(), "Libraries cannot be inherited from."); + m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from."); auto const& arguments = _inheritance.arguments(); TypePointers parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); if (!arguments.empty() && parameterTypes.size() != arguments.size()) { - typeError( + m_errorReporter.typeError( _inheritance.location(), "Wrong argument count for constructor call: " + toString(arguments.size()) + @@ -392,7 +421,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) for (size_t i = 0; i < arguments.size(); ++i) if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) - typeError( + m_errorReporter.typeError( arguments[i]->location(), "Invalid type for argument in constructor call. " "Invalid implicit conversion from " + @@ -409,17 +438,17 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) _usingFor.libraryName().annotation().referencedDeclaration ); if (!library || !library->isLibrary()) - typeError(_usingFor.libraryName().location(), "Library name expected."); + m_errorReporter.typeError(_usingFor.libraryName().location(), "Library name expected."); } bool TypeChecker::visit(StructDefinition const& _struct) { if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) - typeError(_struct.location(), "Structs cannot be defined in interfaces."); + m_errorReporter.typeError(_struct.location(), "Structs cannot be defined in interfaces."); for (ASTPointer<VariableDeclaration> const& member: _struct.members()) if (!type(*member)->canBeStored()) - typeError(member->location(), "Type cannot be used in struct."); + m_errorReporter.typeError(member->location(), "Type cannot be used in struct."); // Check recursion, fatal error if detected. using StructPointer = StructDefinition const*; @@ -427,7 +456,7 @@ bool TypeChecker::visit(StructDefinition const& _struct) function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents) { if (_parents.count(_struct)) - fatalTypeError(_struct->location(), "Recursive struct definition."); + m_errorReporter.fatalTypeError(_struct->location(), "Recursive struct definition."); StructPointersSet parents = _parents; parents.insert(_struct); for (ASTPointer<VariableDeclaration> const& member: _struct->members()) @@ -452,34 +481,49 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (_function.isPayable()) { if (isLibraryFunction) - typeError(_function.location(), "Library functions cannot be payable."); + m_errorReporter.typeError(_function.location(), "Library functions cannot be payable."); if (!_function.isConstructor() && !_function.name().empty() && !_function.isPartOfExternalInterface()) - typeError(_function.location(), "Internal functions cannot be payable."); + m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); if (_function.isDeclaredConst()) - typeError(_function.location(), "Functions cannot be constant and payable at the same time."); + m_errorReporter.typeError(_function.location(), "Functions cannot be constant and payable at the same time."); } for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters()) { if (!type(*var)->canLiveOutsideStorage()) - typeError(var->location(), "Type is required to live outside storage."); + m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) - fatalTypeError(var->location(), "Internal type is not allowed for public or external functions."); + m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions."); + + var->accept(*this); } + set<Declaration const*> modifiers; for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers()) + { visitManually( *modifier, _function.isConstructor() ? dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : vector<ContractDefinition const*>() ); + Declaration const* decl = &dereference(*modifier->name()); + if (modifiers.count(decl)) + { + if (dynamic_cast<ContractDefinition const*>(decl)) + m_errorReporter.declarationError(modifier->location(), "Base constructor already provided."); + else + m_errorReporter.declarationError(modifier->location(), "Modifier already used for this function."); + } + else + modifiers.insert(decl); + } if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) { if (_function.isImplemented()) - typeError(_function.location(), "Functions in interfaces cannot have an implementation."); + m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation."); if (_function.visibility() < FunctionDefinition::Visibility::Public) - typeError(_function.location(), "Functions in interfaces cannot be internal or private."); + m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private."); if (_function.isConstructor()) - typeError(_function.location(), "Constructor cannot be defined in interfaces."); + m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces."); } if (_function.isImplemented()) _function.body().accept(*this); @@ -488,8 +532,13 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) - typeError(_variable.location(), "Variables cannot be declared in interfaces."); + // Forbid any variable declarations inside interfaces unless they are part of + // a function's input/output parameters. + if ( + m_scope->contractKind() == ContractDefinition::ContractKind::Interface + && !_variable.isCallableParameter() + ) + m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); // Variables can be declared without type (with "var"), in which case the first assignment // sets the type. @@ -505,19 +554,19 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (_variable.isConstant()) { if (!_variable.isStateVariable()) - typeError(_variable.location(), "Illegal use of \"constant\" specifier."); + m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier."); if (!_variable.type()->isValueType()) { bool allowed = false; if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get())) allowed = arrayType->isString(); if (!allowed) - typeError(_variable.location(), "Constants of non-value type not yet implemented."); + m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented."); } if (!_variable.value()) - typeError(_variable.location(), "Uninitialized \"constant\" variable."); + m_errorReporter.typeError(_variable.location(), "Uninitialized \"constant\" variable."); else if (!_variable.value()->annotation().isPure) - warning( + m_errorReporter.warning( _variable.value()->location(), "Initial value for constant variable has to be compile-time constant. " "This will fail to compile with the next breaking version change." @@ -527,20 +576,20 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) { if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData)) if (!varType->canLiveOutsideStorage()) - typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage."); + m_errorReporter.typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage."); } else if ( _variable.visibility() >= VariableDeclaration::Visibility::Public && !FunctionType(_variable).interfaceFunctionType() ) - typeError(_variable.location(), "Internal type is not allowed for public state variables."); + m_errorReporter.typeError(_variable.location(), "Internal type is not allowed for public state variables."); return false; } bool TypeChecker::visit(EnumDefinition const& _enum) { if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) - typeError(_enum.location(), "Enumerable cannot be declared in interfaces."); + m_errorReporter.typeError(_enum.location(), "Enumerable cannot be declared in interfaces."); return false; } @@ -572,12 +621,12 @@ void TypeChecker::visitManually( } if (!parameters) { - typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class."); + m_errorReporter.typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class."); return; } if (parameters->size() != arguments.size()) { - typeError( + m_errorReporter.typeError( _modifier.location(), "Wrong argument count for modifier invocation: " + toString(arguments.size()) + @@ -589,7 +638,7 @@ void TypeChecker::visitManually( } for (size_t i = 0; i < _modifier.arguments().size(); ++i) if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i]))) - typeError( + m_errorReporter.typeError( arguments[i]->location(), "Invalid type for argument in modifier invocation. " "Invalid implicit conversion from " + @@ -608,13 +657,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) if (var->isIndexed()) numIndexed++; if (_eventDef.isAnonymous() && numIndexed > 4) - typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event."); + m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event."); else if (!_eventDef.isAnonymous() && numIndexed > 3) - typeError(_eventDef.location(), "More than 3 indexed arguments for event."); + m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event."); if (!type(*var)->canLiveOutsideStorage()) - typeError(var->location(), "Type is required to live outside storage."); + m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (!type(*var)->interfaceType(false)) - typeError(var->location(), "Internal type is not allowed as event parameter type."); + m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type."); } return false; } @@ -624,16 +673,17 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type); if (fun.kind() == FunctionType::Kind::External) if (!fun.canBeUsedExternally(false)) - typeError(_funType.location(), "External function type uses internal types."); + m_errorReporter.typeError(_funType.location(), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { // External references have already been resolved in a prior stage and stored in the annotation. // We run the resolve step again regardless. - assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&]( + julia::ExternalIdentifierAccess::Resolver identifierAccess = [&]( assembly::Identifier const& _identifier, - assembly::IdentifierContext _context + julia::IdentifierContext _context, + bool ) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); @@ -647,43 +697,43 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) { - typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); + m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); return size_t(-1); } - else if (_context != assembly::IdentifierContext::RValue) + else if (_context != julia::IdentifierContext::RValue) { - typeError(_identifier.location, "Storage variables cannot be assigned to."); + m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to."); return size_t(-1); } } else if (var->isConstant()) { - typeError(_identifier.location, "Constant variables not supported by inline assembly."); + m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly."); return size_t(-1); } else if (!var->isLocalVariable()) { - typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); + m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); return size_t(-1); } else if (var->type()->dataStoredIn(DataLocation::Storage)) { - typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables."); + m_errorReporter.typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables."); return size_t(-1); } else if (var->type()->sizeOnStack() != 1) { - typeError(_identifier.location, "Only types that use one stack slot are supported."); + m_errorReporter.typeError(_identifier.location, "Only types that use one stack slot are supported."); return size_t(-1); } } - else if (_context == assembly::IdentifierContext::LValue) + else if (_context == julia::IdentifierContext::LValue) { - typeError(_identifier.location, "Only local variables can be assigned to in inline assembly."); + m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly."); return size_t(-1); } - if (_context == assembly::IdentifierContext::RValue) + if (_context == julia::IdentifierContext::RValue) { solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); if (dynamic_cast<FunctionDefinition const*>(declaration)) @@ -696,7 +746,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { if (!contract->isLibrary()) { - typeError(_identifier.location, "Expected a library."); + m_errorReporter.typeError(_identifier.location, "Expected a library."); return size_t(-1); } } @@ -710,7 +760,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, - m_errors, + m_errorReporter, + false, identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) @@ -753,7 +804,7 @@ void TypeChecker::endVisit(Return const& _return) ParameterList const* params = _return.annotation().functionReturnParameters; if (!params) { - typeError(_return.location(), "Return arguments not allowed."); + m_errorReporter.typeError(_return.location(), "Return arguments not allowed."); return; } TypePointers returnTypes; @@ -762,9 +813,9 @@ void TypeChecker::endVisit(Return const& _return) if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression()).get())) { if (tupleType->components().size() != params->parameters().size()) - typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); + m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes))) - typeError( + m_errorReporter.typeError( _return.expression()->location(), "Return argument type " + type(*_return.expression())->toString() + @@ -774,12 +825,12 @@ void TypeChecker::endVisit(Return const& _return) ); } else if (params->parameters().size() != 1) - typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); + m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); else { TypePointer const& expected = type(*params->parameters().front()); if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) - typeError( + m_errorReporter.typeError( _return.expression()->location(), "Return argument type " + type(*_return.expression())->toString() + @@ -796,20 +847,20 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) { // No initial value is only permitted for single variables with specified type. if (_statement.declarations().size() != 1 || !_statement.declarations().front()) - fatalTypeError(_statement.location(), "Assignment necessary for type detection."); + m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection."); VariableDeclaration const& varDecl = *_statement.declarations().front(); if (!varDecl.annotation().type) - fatalTypeError(_statement.location(), "Assignment necessary for type detection."); + m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection."); if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get())) { if (ref->dataStoredIn(DataLocation::Storage)) - warning( + m_errorReporter.warning( varDecl.location(), "Uninitialized storage pointer. Did you mean '<type> memory " + varDecl.name() + "'?" ); } else if (dynamic_cast<MappingType const*>(type(varDecl).get())) - typeError( + m_errorReporter.typeError( varDecl.location(), "Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable." ); @@ -835,7 +886,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (variables.empty()) { if (!valueTypes.empty()) - fatalTypeError( + m_errorReporter.fatalTypeError( _statement.location(), "Too many components (" + toString(valueTypes.size()) + @@ -843,7 +894,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) ); } else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back()) - fatalTypeError( + m_errorReporter.fatalTypeError( _statement.location(), "Wildcard both at beginning and end of variable declaration list is only allowed " "if the number of components is equal." @@ -852,7 +903,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (!variables.empty() && (!variables.back() || !variables.front())) --minNumValues; if (valueTypes.size() < minNumValues) - fatalTypeError( + m_errorReporter.fatalTypeError( _statement.location(), "Not enough components (" + toString(valueTypes.size()) + @@ -860,7 +911,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) toString(minNumValues) + ")." ); if (valueTypes.size() > variables.size() && variables.front() && variables.back()) - fatalTypeError( + m_errorReporter.fatalTypeError( _statement.location(), "Too many components (" + toString(valueTypes.size()) + @@ -891,7 +942,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (!var.annotation().type) { if (valueComponentType->category() == Type::Category::RationalNumber) - fatalTypeError( + m_errorReporter.fatalTypeError( _statement.initialValue()->location(), "Invalid rational " + valueComponentType->toString() + @@ -901,10 +952,42 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) solAssert(false, ""); } else if (*var.annotation().type == TupleType()) - typeError( + m_errorReporter.typeError( var.location(), "Cannot declare variable with void (empty tuple) type." ); + else if (valueComponentType->category() == Type::Category::RationalNumber) + { + string typeName = var.annotation().type->toString(true); + string extension; + if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type.get())) + { + int numBits = type->numBits(); + bool isSigned = type->isSigned(); + string minValue; + string maxValue; + if (isSigned) + { + numBits--; + minValue = "-" + bigint(bigint(1) << numBits).str(); + } + else + minValue = "0"; + maxValue = bigint((bigint(1) << numBits) - 1).str(); + extension = ", which can hold values between " + minValue + " and " + maxValue; + } + else + solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type.get()), "Unknown type."); + + m_errorReporter.warning( + _statement.location(), + "The type of this variable was inferred as " + + typeName + + extension + + ". This is probably not desired. Use an explicit type to silence this warning." + ); + } + var.accept(*this); } else @@ -917,7 +1000,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() && valueComponentType->mobileType() ) - typeError( + m_errorReporter.typeError( _statement.location(), "Type " + valueComponentType->toString() + @@ -928,7 +1011,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) " or use an explicit conversion." ); else - typeError( + m_errorReporter.typeError( _statement.location(), "Type " + valueComponentType->toString() + @@ -946,7 +1029,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) { if (type(_statement.expression())->category() == Type::Category::RationalNumber) if (!dynamic_cast<RationalNumberType const&>(*type(_statement.expression())).mobileType()) - typeError(_statement.expression().location(), "Invalid rational number."); + m_errorReporter.typeError(_statement.expression().location(), "Invalid rational number."); if (auto call = dynamic_cast<FunctionCall const*>(&_statement.expression())) { @@ -958,9 +1041,9 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) kind == FunctionType::Kind::BareCallCode || kind == FunctionType::Kind::BareDelegateCall ) - warning(_statement.location(), "Return value of low-level calls not used."); + m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used."); else if (kind == FunctionType::Kind::Send) - warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead."); + m_errorReporter.warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead."); } } } @@ -975,14 +1058,14 @@ bool TypeChecker::visit(Conditional const& _conditional) TypePointer trueType = type(_conditional.trueExpression())->mobileType(); TypePointer falseType = type(_conditional.falseExpression())->mobileType(); if (!trueType) - fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type."); + m_errorReporter.fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type."); if (!falseType) - fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type."); + m_errorReporter.fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type."); TypePointer commonType = Type::commonType(trueType, falseType); if (!commonType) { - typeError( + m_errorReporter.typeError( _conditional.location(), "True expression's type " + trueType->toString() + @@ -1002,7 +1085,7 @@ bool TypeChecker::visit(Conditional const& _conditional) _conditional.falseExpression().annotation().isPure; if (_conditional.annotation().lValueRequested) - typeError( + m_errorReporter.typeError( _conditional.location(), "Conditional expression as left value is not supported yet." ); @@ -1018,17 +1101,19 @@ bool TypeChecker::visit(Assignment const& _assignment) if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get())) { if (_assignment.assignmentOperator() != Token::Assign) - typeError( + m_errorReporter.typeError( _assignment.location(), "Compound assignment is not allowed for tuple types." ); // Sequenced assignments of tuples is not valid, make the result a "void" type. _assignment.annotation().type = make_shared<TupleType>(); expectType(_assignment.rightHandSide(), *tupleType); + + checkDoubleStorageAssignment(_assignment); } else if (t->category() == Type::Category::Mapping) { - typeError(_assignment.location(), "Mappings cannot be assigned to."); + m_errorReporter.typeError(_assignment.location(), "Mappings cannot be assigned to."); _assignment.rightHandSide().accept(*this); } else if (_assignment.assignmentOperator() == Token::Assign) @@ -1042,7 +1127,7 @@ bool TypeChecker::visit(Assignment const& _assignment) type(_assignment.rightHandSide()) ); if (!resultType || *resultType != *t) - typeError( + m_errorReporter.typeError( _assignment.location(), "Operator " + string(Token::toString(_assignment.assignmentOperator())) + @@ -1063,7 +1148,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (_tuple.annotation().lValueRequested) { if (_tuple.isInlineArray()) - fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue."); + m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue."); for (auto const& component: components) if (component) { @@ -1087,7 +1172,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) { // Outside of an lvalue-context, the only situation where a component can be empty is (x,). if (!components[i] && !(i == 1 && components.size() == 2)) - fatalTypeError(_tuple.location(), "Tuple component cannot be empty."); + m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty."); else if (components[i]) { components[i]->accept(*this); @@ -1097,7 +1182,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (_tuple.isInlineArray()) { if ((i == 0 || inlineArrayType) && !types[i]->mobileType()) - fatalTypeError(components[i]->location(), "Invalid mobile type."); + m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type."); if (i == 0) inlineArrayType = types[i]->mobileType(); @@ -1114,7 +1199,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (_tuple.isInlineArray()) { if (!inlineArrayType) - fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements."); + m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements."); _tuple.annotation().type = make_shared<ArrayType>(DataLocation::Memory, inlineArrayType, types.size()); } else @@ -1146,7 +1231,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation) TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op); if (!t) { - typeError( + m_errorReporter.typeError( _operation.location(), "Unary operator " + string(Token::toString(op)) + @@ -1167,7 +1252,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); if (!commonType) { - typeError( + m_errorReporter.typeError( _operation.location(), "Operator " + string(Token::toString(_operation.getOperator())) + @@ -1200,7 +1285,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) commonType->category() == Type::Category::FixedPoint && dynamic_cast<FixedPointType const&>(*commonType).numBits() != 256 )) - warning( + m_errorReporter.warning( _operation.location(), "Result of exponentiation has type " + commonType->toString() + " and thus " "might overflow. Silence this warning by converting the literal to the " @@ -1238,20 +1323,24 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (auto const* typeType = dynamic_cast<TypeType const*>(expressionType.get())) { - _functionCall.annotation().isStructConstructorCall = (typeType->actualType()->category() == Type::Category::Struct); - _functionCall.annotation().isTypeConversion = !_functionCall.annotation().isStructConstructorCall; + if (typeType->actualType()->category() == Type::Category::Struct) + _functionCall.annotation().kind = FunctionCallKind::StructConstructorCall; + else + _functionCall.annotation().kind = FunctionCallKind::TypeConversion; + } else - _functionCall.annotation().isStructConstructorCall = _functionCall.annotation().isTypeConversion = false; + _functionCall.annotation().kind = FunctionCallKind::FunctionCall; + solAssert(_functionCall.annotation().kind != FunctionCallKind::Unset, ""); - if (_functionCall.annotation().isTypeConversion) + if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) { TypeType const& t = dynamic_cast<TypeType const&>(*expressionType); TypePointer resultType = t.actualType(); if (arguments.size() != 1) - typeError(_functionCall.location(), "Exactly one argument expected for explicit type conversion."); + m_errorReporter.typeError(_functionCall.location(), "Exactly one argument expected for explicit type conversion."); else if (!isPositionalCall) - typeError(_functionCall.location(), "Type conversion cannot allow named arguments."); + m_errorReporter.typeError(_functionCall.location(), "Type conversion cannot allow named arguments."); else { TypePointer const& argType = type(*arguments.front()); @@ -1260,7 +1349,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // (data location cannot yet be specified for type conversions) resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType); if (!argType->isExplicitlyConvertibleTo(*resultType)) - typeError(_functionCall.location(), "Explicit type conversion not allowed."); + m_errorReporter.typeError(_functionCall.location(), "Explicit type conversion not allowed."); } _functionCall.annotation().type = resultType; _functionCall.annotation().isPure = isPure; @@ -1274,7 +1363,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) /// For error message: Struct members that were removed during conversion to memory. set<string> membersRemovedForStructConstructor; - if (_functionCall.annotation().isStructConstructorCall) + if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) { TypeType const& t = dynamic_cast<TypeType const&>(*expressionType); auto const& structType = dynamic_cast<StructType const&>(*t.actualType()); @@ -1282,18 +1371,15 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) membersRemovedForStructConstructor = structType.membersMissingInMemory(); _functionCall.annotation().isPure = isPure; } - else - { - functionType = dynamic_pointer_cast<FunctionType const>(expressionType); + else if ((functionType = dynamic_pointer_cast<FunctionType const>(expressionType))) _functionCall.annotation().isPure = isPure && _functionCall.expression().annotation().isPure && functionType->isPure(); - } if (!functionType) { - typeError(_functionCall.location(), "Type is not callable"); + m_errorReporter.typeError(_functionCall.location(), "Type is not callable"); _functionCall.annotation().type = make_shared<TupleType>(); return false; } @@ -1312,13 +1398,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) toString(parameterTypes.size()) + "."; // Extend error message in case we try to construct a struct with mapping member. - if (_functionCall.annotation().isStructConstructorCall && !membersRemovedForStructConstructor.empty()) + if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall && !membersRemovedForStructConstructor.empty()) { msg += " Members that have to be skipped in memory:"; for (auto const& member: membersRemovedForStructConstructor) msg += " " + member; } - typeError(_functionCall.location(), msg); + m_errorReporter.typeError(_functionCall.location(), msg); } else if (isPositionalCall) { @@ -1330,10 +1416,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) { if (auto t = dynamic_cast<RationalNumberType const*>(argType.get())) if (!t->mobileType()) - typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); + m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); } else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) - typeError( + m_errorReporter.typeError( arguments[i]->location(), "Invalid type for argument in function call. " "Invalid implicit conversion from " + @@ -1349,14 +1435,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // call by named arguments auto const& parameterNames = functionType->parameterNames(); if (functionType->takesArbitraryParameters()) - typeError( + m_errorReporter.typeError( _functionCall.location(), "Named arguments cannnot be used for functions that take arbitrary parameters." ); else if (parameterNames.size() > argumentNames.size()) - typeError(_functionCall.location(), "Some argument names are missing."); + m_errorReporter.typeError(_functionCall.location(), "Some argument names are missing."); else if (parameterNames.size() < argumentNames.size()) - typeError(_functionCall.location(), "Too many arguments."); + m_errorReporter.typeError(_functionCall.location(), "Too many arguments."); else { // check duplicate names @@ -1366,7 +1452,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (*argumentNames[i] == *argumentNames[j]) { duplication = true; - typeError(arguments[i]->location(), "Duplicate named argument."); + m_errorReporter.typeError(arguments[i]->location(), "Duplicate named argument."); } // check actual types @@ -1380,7 +1466,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) found = true; // check type convertible if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[j])) - typeError( + m_errorReporter.typeError( arguments[i]->location(), "Invalid type for argument in function call. " "Invalid implicit conversion from " + @@ -1393,7 +1479,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } if (!found) - typeError( + m_errorReporter.typeError( _functionCall.location(), "Named argument does not match function declaration." ); @@ -1414,11 +1500,11 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName)); if (!contract) - fatalTypeError(_newExpression.location(), "Identifier is not a contract."); + m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); if (!contract->annotation().isFullyImplemented) - typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); + m_errorReporter.typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); if (!contract->constructorIsPublic()) - typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); + m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); solAssert(!!m_scope, ""); m_scope->annotation().contractDependencies.insert(contract); @@ -1427,7 +1513,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) "Linearized base contracts not yet available." ); if (contractDependenciesAreCyclic(*m_scope)) - typeError( + m_errorReporter.typeError( _newExpression.location(), "Circular reference for contract creation (cannot create instance of derived or same contract)." ); @@ -1437,12 +1523,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) else if (type->category() == Type::Category::Array) { if (!type->canLiveOutsideStorage()) - fatalTypeError( + m_errorReporter.fatalTypeError( _newExpression.typeName().location(), "Type cannot live outside storage." ); if (!type->isDynamicallySized()) - typeError( + m_errorReporter.typeError( _newExpression.typeName().location(), "Length has to be placed in parentheses after the array type for new expression." ); @@ -1457,7 +1543,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) _newExpression.annotation().isPure = true; } else - fatalTypeError(_newExpression.location(), "Contract or array type expected."); + m_errorReporter.fatalTypeError(_newExpression.location(), "Contract or array type expected."); } bool TypeChecker::visit(MemberAccess const& _memberAccess) @@ -1488,13 +1574,13 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) exprType ); if (!storageType->members(m_scope).membersByName(memberName).empty()) - fatalTypeError( + m_errorReporter.fatalTypeError( _memberAccess.location(), "Member \"" + memberName + "\" is not available in " + exprType->toString() + " outside of storage." ); - fatalTypeError( + m_errorReporter.fatalTypeError( _memberAccess.location(), "Member \"" + memberName + "\" not found or not visible " "after argument-dependent lookup in " + exprType->toString() + @@ -1502,7 +1588,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); } else if (possibleMembers.size() > 1) - fatalTypeError( + m_errorReporter.fatalTypeError( _memberAccess.location(), "Member \"" + memberName + "\" not unique " "after argument-dependent lookup in " + exprType->toString() + @@ -1515,7 +1601,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (auto funType = dynamic_cast<FunctionType const*>(annotation.type.get())) if (funType->bound() && !exprType->isImplicitlyConvertibleTo(*funType->selfType())) - typeError( + m_errorReporter.typeError( _memberAccess.location(), "Function \"" + memberName + "\" cannot be called on an object of type " + exprType->toString() + " (expected " + funType->selfType()->toString() + ")" @@ -1563,10 +1649,10 @@ bool TypeChecker::visit(IndexAccess const& _access) { ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType); if (!index) - typeError(_access.location(), "Index expression cannot be omitted."); + m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted."); else if (actualType.isString()) { - typeError(_access.location(), "Index access for string is not possible."); + m_errorReporter.typeError(_access.location(), "Index access for string is not possible."); index->accept(*this); } else @@ -1576,7 +1662,7 @@ bool TypeChecker::visit(IndexAccess const& _access) { if (!numberType->isFractional()) // error is reported above if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr)) - typeError(_access.location(), "Out of bounds array access."); + m_errorReporter.typeError(_access.location(), "Out of bounds array access."); } } resultType = actualType.baseType(); @@ -1587,7 +1673,7 @@ bool TypeChecker::visit(IndexAccess const& _access) { MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType); if (!index) - typeError(_access.location(), "Index expression cannot be omitted."); + m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted."); else expectType(*index, *actualType.keyType()); resultType = actualType.valueType(); @@ -1609,7 +1695,7 @@ bool TypeChecker::visit(IndexAccess const& _access) length->literalValue(nullptr) )); else - fatalTypeError(index->location(), "Integer constant expected."); + m_errorReporter.fatalTypeError(index->location(), "Integer constant expected."); } break; } @@ -1617,20 +1703,20 @@ bool TypeChecker::visit(IndexAccess const& _access) { FixedBytesType const& bytesType = dynamic_cast<FixedBytesType const&>(*baseType); if (!index) - typeError(_access.location(), "Index expression cannot be omitted."); + m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted."); else { expectType(*index, IntegerType(256)); if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index).get())) if (bytesType.numBytes() <= integerType->literalValue(nullptr)) - typeError(_access.location(), "Out of bounds array access."); + m_errorReporter.typeError(_access.location(), "Out of bounds array access."); } resultType = make_shared<FixedBytesType>(1); isLValue = false; // @todo this heavily depends on how it is embedded break; } default: - fatalTypeError( + m_errorReporter.fatalTypeError( _access.baseExpression().location(), "Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")" ); @@ -1660,14 +1746,14 @@ bool TypeChecker::visit(Identifier const& _identifier) candidates.push_back(declaration); } if (candidates.empty()) - fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup."); + m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup."); else if (candidates.size() == 1) annotation.referencedDeclaration = candidates.front(); else - fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup."); + m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup."); } else if (annotation.overloadedDeclarations.empty()) - fatalTypeError(_identifier.location(), "No candidates for overload resolution found."); + m_errorReporter.fatalTypeError(_identifier.location(), "No candidates for overload resolution found."); else if (annotation.overloadedDeclarations.size() == 1) annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin(); else @@ -1683,11 +1769,11 @@ bool TypeChecker::visit(Identifier const& _identifier) candidates.push_back(declaration); } if (candidates.empty()) - fatalTypeError(_identifier.location(), "No matching declaration found after argument-dependent lookup."); + m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after argument-dependent lookup."); else if (candidates.size() == 1) annotation.referencedDeclaration = candidates.front(); else - fatalTypeError(_identifier.location(), "No unique declaration found after argument-dependent lookup."); + m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after argument-dependent lookup."); } } solAssert( @@ -1697,7 +1783,7 @@ bool TypeChecker::visit(Identifier const& _identifier) annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.type = annotation.referencedDeclaration->type(); if (!annotation.type) - fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); + m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration)) @@ -1717,21 +1803,21 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { if (_literal.passesAddressChecksum()) - { _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address); - return; - } else - warning( + m_errorReporter.warning( _literal.location(), "This looks like an address but has an invalid checksum. " "If this is not used as an address, please prepend '00'." ); } - _literal.annotation().type = Type::forLiteral(_literal); - _literal.annotation().isPure = true; if (!_literal.annotation().type) - fatalTypeError(_literal.location(), "Invalid literal value."); + _literal.annotation().type = Type::forLiteral(_literal); + + if (!_literal.annotation().type) + m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); + + _literal.annotation().isPure = true; } bool TypeChecker::contractDependenciesAreCyclic( @@ -1772,7 +1858,7 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte dynamic_pointer_cast<RationalNumberType const>(type(_expression))->isFractional() && type(_expression)->mobileType() ) - typeError( + m_errorReporter.typeError( _expression.location(), "Type " + type(_expression)->toString() + @@ -1783,7 +1869,7 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte " or use an explicit conversion." ); else - typeError( + m_errorReporter.typeError( _expression.location(), "Type " + type(_expression)->toString() + @@ -1791,7 +1877,23 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expectedType.toString() + "." ); - } + } + + if ( + type(_expression)->category() == Type::Category::RationalNumber && + _expectedType.category() == Type::Category::FixedBytes + ) + { + auto literal = dynamic_cast<Literal const*>(&_expression); + + if (literal && !literal->isHexNumber()) + m_errorReporter.warning( + _expression.location(), + "Decimal literal assigned to bytesXX variable will be left-aligned. " + "Use an explicit conversion to silence this warning." + ); + } + } void TypeChecker::requireLValue(Expression const& _expression) @@ -1800,33 +1902,8 @@ void TypeChecker::requireLValue(Expression const& _expression) _expression.accept(*this); if (_expression.annotation().isConstant) - typeError(_expression.location(), "Cannot assign to a constant variable."); + m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable."); else if (!_expression.annotation().isLValue) - typeError(_expression.location(), "Expression has to be an lvalue."); + m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue."); } -void TypeChecker::typeError(SourceLocation const& _location, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::TypeError); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); - - m_errors.push_back(err); -} - -void TypeChecker::warning(SourceLocation const& _location, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::Warning); - *err << - errinfo_sourceLocation(_location) << - errinfo_comment(_description); - - m_errors.push_back(err); -} - -void TypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description) -{ - typeError(_location, _description); - BOOST_THROW_EXCEPTION(FatalError()); -} diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 88559f44..ee43d13a 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -22,7 +22,6 @@ #pragma once -#include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTForward.h> @@ -33,6 +32,7 @@ namespace dev namespace solidity { +class ErrorReporter; /** * The module that performs type analysis on the AST, checks the applicability of operations on @@ -43,7 +43,7 @@ class TypeChecker: private ASTConstVisitor { public: /// @param _errors the reference to the list of errors and warnings to add them found during type checking. - TypeChecker(ErrorList& _errors): m_errors(_errors) {} + TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} /// Performs type checking on the given contract and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -56,14 +56,6 @@ public: TypePointer const& type(VariableDeclaration const& _variable) const; private: - /// Adds a new error to the list of errors. - void typeError(SourceLocation const& _location, std::string const& _description); - - /// Adds a new warning to the list of errors. - void warning(SourceLocation const& _location, std::string const& _description); - - /// Adds a new error to the list of errors and throws to abort type checking. - void fatalTypeError(SourceLocation const& _location, std::string const& _description); virtual bool visit(ContractDefinition const& _contract) override; /// Checks that two functions defined in this contract with the same name have different @@ -77,6 +69,9 @@ private: void checkContractExternalTypeClashes(ContractDefinition const& _contract); /// Checks that all requirements for a library are fulfilled if this is a library. void checkLibraryRequirements(ContractDefinition const& _contract); + /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage. + /// Should only be called if the left hand side is tuple-typed. + void checkDoubleStorageAssignment(Assignment const& _assignment); virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; @@ -127,7 +122,7 @@ private: ContractDefinition const* m_scope = nullptr; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 03112d2d..403f4b79 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -20,10 +20,8 @@ * Solidity abstract syntax tree. */ -#include <libsolidity/interface/Utils.h> #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ASTVisitor.h> -#include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/AST_accept.h> #include <libdevcore/SHA3.h> @@ -532,18 +530,26 @@ IdentifierAnnotation& Identifier::annotation() const return dynamic_cast<IdentifierAnnotation&>(*m_annotation); } +bool Literal::isHexNumber() const +{ + if (token() != Token::Number) + return false; + return boost::starts_with(value(), "0x"); +} + bool Literal::looksLikeAddress() const { if (subDenomination() != SubDenomination::None) return false; - string lit = value(); - return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; + if (!isHexNumber()) + return false; + + return abs(int(value().length()) - 42) <= 1; } bool Literal::passesAddressChecksum() const { - string lit = value(); - solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix"); - return dev::passesAddressChecksum(lit, true); + solAssert(isHexNumber(), "Expected hex number"); + return dev::passesAddressChecksum(value(), true); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 02234ffc..e8831dc0 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -29,7 +29,6 @@ #include <boost/noncopyable.hpp> #include <libevmasm/SourceLocation.h> #include <libevmasm/Instruction.h> -#include <libsolidity/interface/Utils.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/parsing/Token.h> #include <libsolidity/ast/Types.h> @@ -583,8 +582,7 @@ public: bool isPayable() const { return m_isPayable; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); } - Block const& body() const { return *m_body; } - + Block const& body() const { solAssert(m_body, ""); return *m_body; } virtual bool isVisibleInContract() const override { return Declaration::isVisibleInContract() && !isConstructor() && !name().empty(); @@ -874,6 +872,8 @@ public: std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); } std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); } + ASTPointer<ParameterList> const& parameterTypeList() const { return m_parameterTypes; } + ASTPointer<ParameterList> const& returnParameterTypeList() const { return m_returnTypes; } Declaration::Visibility visibility() const { @@ -1314,7 +1314,7 @@ private: /** * Tuple, parenthesized expression, or bracketed expression. - * Examples: (1, 2), (x,), (x), (), [1, 2], + * Examples: (1, 2), (x,), (x), (), [1, 2], * Individual components might be empty shared pointers (as in the second example). * The respective types in lvalue context are: 2-tuple, 2-tuple (with wildcard), type of x, 0-tuple * Not in lvalue context: 2-tuple, _1_-tuple, type of x, 0-tuple. @@ -1327,8 +1327,8 @@ public: std::vector<ASTPointer<Expression>> const& _components, bool _isArray ): - Expression(_location), - m_components(_components), + Expression(_location), + m_components(_components), m_isArray(_isArray) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -1590,6 +1590,9 @@ public: SubDenomination subDenomination() const { return m_subDenomination; } + /// @returns true if this is a number with a hex prefix. + bool isHexNumber() const; + /// @returns true if this looks like a checksummed address. bool looksLikeAddress() const; /// @returns true if it passes the address checksum test. diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index a7d89248..45a6dd1a 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -200,12 +200,17 @@ struct BinaryOperationAnnotation: ExpressionAnnotation TypePointer commonType; }; +enum class FunctionCallKind +{ + Unset, + FunctionCall, + TypeConversion, + StructConstructorCall +}; + struct FunctionCallAnnotation: ExpressionAnnotation { - /// Whether this is an explicit type conversion. - bool isTypeConversion = false; - /// Whether this is a struct constructor call. - bool isStructConstructorCall = false; + FunctionCallKind kind = FunctionCallKind::Unset; }; } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 9ea23687..a90debb2 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -24,7 +24,8 @@ #include <boost/algorithm/string/join.hpp> #include <libdevcore/UTF8.h> #include <libsolidity/ast/AST.h> -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmPrinter.h> using namespace std; @@ -33,49 +34,86 @@ namespace dev namespace solidity { -void ASTJsonConverter::addJsonNode( +ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices): + m_legacy(_legacy), + m_sourceIndices(_sourceIndices) +{ +} + + +void ASTJsonConverter::setJsonNode( ASTNode const& _node, string const& _nodeName, - initializer_list<pair<string const, Json::Value const>> _attributes, - bool _hasChildren = false + initializer_list<pair<string, Json::Value>>&& _attributes ) { - ASTJsonConverter::addJsonNode( + ASTJsonConverter::setJsonNode( _node, _nodeName, - std::vector<pair<string const, Json::Value const>>(_attributes), - _hasChildren + std::vector<pair<string, Json::Value>>(std::move(_attributes)) ); } - -void ASTJsonConverter::addJsonNode( + +void ASTJsonConverter::setJsonNode( ASTNode const& _node, - string const& _nodeName, - std::vector<pair<string const, Json::Value const>> const& _attributes, - bool _hasChildren = false + string const& _nodeType, + std::vector<pair<string, Json::Value>>&& _attributes ) { - Json::Value node; - - node["id"] = Json::UInt64(_node.id()); - node["src"] = sourceLocationToString(_node.location()); - node["name"] = _nodeName; - if (_attributes.size() != 0) + m_currentValue = Json::objectValue; + m_currentValue["id"] = nodeId(_node); + m_currentValue["src"] = sourceLocationToString(_node.location()); + if (!m_legacy) { - Json::Value attrs; + m_currentValue["nodeType"] = _nodeType; for (auto& e: _attributes) - attrs[e.first] = e.second; - node["attributes"] = attrs; + m_currentValue[e.first] = std::move(e.second); } - - m_jsonNodePtrs.top()->append(node); - - if (_hasChildren) + else { - Json::Value& addedNode = (*m_jsonNodePtrs.top())[m_jsonNodePtrs.top()->size() - 1]; - Json::Value children(Json::arrayValue); - addedNode["children"] = children; - m_jsonNodePtrs.push(&addedNode["children"]); + m_currentValue["name"] = _nodeType; + Json::Value attrs(Json::objectValue); + if ( + //these nodeTypes need to have a children-node even if it is empty + (_nodeType == "VariableDeclaration") || + (_nodeType == "ParameterList") || + (_nodeType == "Block") || + (_nodeType == "InlineAssembly") || + (_nodeType == "Throw") + ) + { + Json::Value children(Json::arrayValue); + m_currentValue["children"] = children; + } + + for (auto& e: _attributes) + { + if ( + (!e.second.isNull()) && + ( + (e.second.isObject() && e.second.isMember("name")) || + (e.second.isArray() && e.second[0].isObject() && e.second[0].isMember("name")) || + (e.first == "declarations") // (in the case (_,x)= ... there's a nullpointer at [0] + ) + ) + { + if (e.second.isObject()) + m_currentValue["children"].append(std::move(e.second)); + if (e.second.isArray()) + for (auto& child: e.second) + if (!child.isNull()) + m_currentValue["children"].append(std::move(child)); + } + else + { + if (e.first == "typeDescriptions") + attrs["type"] = Json::Value(e.second["typeString"]); + else + attrs[e.first] = std::move(e.second); + } + } + if (!attrs.empty()) + m_currentValue["attributes"] = std::move(attrs); } } @@ -90,34 +128,89 @@ string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex); } -ASTJsonConverter::ASTJsonConverter( - ASTNode const& _ast, - map<string, unsigned> _sourceIndices -): m_ast(&_ast), m_sourceIndices(_sourceIndices) +string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath) const +{ + return boost::algorithm::join(_namePath, "."); +} + +Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp) { + Json::Value typeDescriptions(Json::objectValue); + typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString()) : Json::nullValue; + typeDescriptions["typeIdentifier"] = _tp ? Json::Value(_tp->identifier()) : Json::nullValue; + return typeDescriptions; + +} +Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps) +{ + if (_tps) + { + Json::Value arguments(Json::arrayValue); + for (auto const& tp: *_tps) + arguments.append(typePointerToJson(tp)); + return arguments; + } + else + return Json::nullValue; } -void ASTJsonConverter::print(ostream& _stream) +void ASTJsonConverter::appendExpressionAttributes( + std::vector<pair<string, Json::Value>>& _attributes, + ExpressionAnnotation const& _annotation +) { - process(); - _stream << m_astJson; + std::vector<pair<string, Json::Value>> exprAttributes = { + make_pair("typeDescriptions", typePointerToJson(_annotation.type)), + make_pair("isConstant", _annotation.isConstant), + make_pair("isPure", _annotation.isPure), + make_pair("isLValue", _annotation.isLValue), + make_pair("lValueRequested", _annotation.lValueRequested), + make_pair("argumentTypes", typePointerToJson(_annotation.argumentTypes)) + }; + _attributes += exprAttributes; } -Json::Value const& ASTJsonConverter::json() +Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) { - process(); - return m_astJson; + Json::Value tuple(Json::objectValue); + tuple["src"] = sourceLocationToString(_info.first->location); + tuple["declaration"] = idOrNull(_info.second.declaration); + tuple["isSlot"] = Json::Value(_info.second.isSlot); + tuple["isOffset"] = Json::Value(_info.second.isOffset); + tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize)); + return tuple; } -bool ASTJsonConverter::visit(SourceUnit const&) +void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node) { - Json::Value children(Json::arrayValue); + _stream << toJson(_node); +} - m_astJson["name"] = "SourceUnit"; - m_astJson["children"] = children; - m_jsonNodePtrs.push(&m_astJson["children"]); +Json::Value ASTJsonConverter::toJson(ASTNode const& _node) +{ + _node.accept(*this); + return std::move(m_currentValue); +} - return true; +bool ASTJsonConverter::visit(SourceUnit const& _node) +{ + Json::Value exportedSymbols = Json::objectValue; + for (auto const& sym: _node.annotation().exportedSymbols) + { + exportedSymbols[sym.first] = Json::arrayValue; + for (Declaration const* overload: sym.second) + exportedSymbols[sym.first].append(nodeId(*overload)); + } + setJsonNode( + _node, + "SourceUnit", + { + make_pair("absolutePath", _node.annotation().path), + make_pair("exportedSymbols", move(exportedSymbols)), + make_pair("nodes", toJson(_node.nodes())) + } + ); + return false; } bool ASTJsonConverter::visit(PragmaDirective const& _node) @@ -125,556 +218,516 @@ bool ASTJsonConverter::visit(PragmaDirective const& _node) Json::Value literals(Json::arrayValue); for (auto const& literal: _node.literals()) literals.append(literal); - addJsonNode(_node, "PragmaDirective", { make_pair("literals", literals) }); - return true; + setJsonNode( _node, "PragmaDirective", { + make_pair("literals", std::move(literals)) + }); + return false; } bool ASTJsonConverter::visit(ImportDirective const& _node) { - addJsonNode(_node, "ImportDirective", { make_pair("file", _node.path())}); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("file", _node.path()), + make_pair("absolutePath", _node.annotation().absolutePath), + make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)), + make_pair("scope", idOrNull(_node.scope())) + }; + attributes.push_back(make_pair("unitAlias", _node.name())); + Json::Value symbolAliases(Json::arrayValue); + for (auto const& symbolAlias: _node.symbolAliases()) + { + Json::Value tuple(Json::objectValue); + solAssert(symbolAlias.first, ""); + tuple["foreign"] = nodeId(*symbolAlias.first); + tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue; + symbolAliases.append(tuple); + } + attributes.push_back(make_pair("symbolAliases", std::move(symbolAliases))); + setJsonNode(_node, "ImportDirective", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(ContractDefinition const& _node) { - Json::Value linearizedBaseContracts(Json::arrayValue); - for (auto const& baseContract: _node.annotation().linearizedBaseContracts) - linearizedBaseContracts.append(Json::UInt64(baseContract->id())); - addJsonNode(_node, "ContractDefinition", { + setJsonNode(_node, "ContractDefinition", { make_pair("name", _node.name()), - make_pair("isLibrary", _node.isLibrary()), + make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), + make_pair("contractKind", contractKind(_node.contractKind())), make_pair("fullyImplemented", _node.annotation().isFullyImplemented), - make_pair("linearizedBaseContracts", linearizedBaseContracts), - }, true); - return true; + make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), + make_pair("baseContracts", toJson(_node.baseContracts())), + make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)), + make_pair("nodes", toJson(_node.subNodes())), + make_pair("scope", idOrNull(_node.scope())) + }); + return false; } bool ASTJsonConverter::visit(InheritanceSpecifier const& _node) { - addJsonNode(_node, "InheritanceSpecifier", {}, true); - return true; + setJsonNode(_node, "InheritanceSpecifier", { + make_pair("baseName", toJson(_node.name())), + make_pair("arguments", toJson(_node.arguments())) + }); + return false; } bool ASTJsonConverter::visit(UsingForDirective const& _node) { - addJsonNode(_node, "UsingForDirective", {}, true); - return true; + setJsonNode(_node, "UsingForDirective", { + make_pair("libraryName", toJson(_node.libraryName())), + make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue) + }); + return false; } bool ASTJsonConverter::visit(StructDefinition const& _node) { - addJsonNode(_node, "StructDefinition", { make_pair("name", _node.name()) }, true); - return true; + setJsonNode(_node, "StructDefinition", { + make_pair("name", _node.name()), + make_pair("visibility", visibility(_node.visibility())), + make_pair("canonicalName", _node.annotation().canonicalName), + make_pair("members", toJson(_node.members())), + make_pair("scope", idOrNull(_node.scope())) + }); + return false; } bool ASTJsonConverter::visit(EnumDefinition const& _node) { - addJsonNode(_node, "EnumDefinition", { make_pair("name", _node.name()) }, true); - return true; + setJsonNode(_node, "EnumDefinition", { + make_pair("name", _node.name()), + make_pair("canonicalName", _node.annotation().canonicalName), + make_pair("members", toJson(_node.members())) + }); + return false; } bool ASTJsonConverter::visit(EnumValue const& _node) { - addJsonNode(_node, "EnumValue", { make_pair("name", _node.name()) }); - return true; + setJsonNode(_node, "EnumValue", { + make_pair("name", _node.name()) + }); + return false; } bool ASTJsonConverter::visit(ParameterList const& _node) { - addJsonNode(_node, "ParameterList", {}, true); - return true; + setJsonNode(_node, "ParameterList", { + make_pair("parameters", toJson(_node.parameters())) + }); + return false; } bool ASTJsonConverter::visit(FunctionDefinition const& _node) { - addJsonNode(_node, "FunctionDefinition", { + std::vector<pair<string, Json::Value>> attributes = { make_pair("name", _node.name()), - make_pair("constant", _node.isDeclaredConst()), + make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), make_pair("payable", _node.isPayable()), - make_pair("visibility", visibility(_node.visibility())) - }, true); - return true; + make_pair("visibility", visibility(_node.visibility())), + make_pair("parameters", toJson(_node.parameterList())), + make_pair("isConstructor", _node.isConstructor()), + make_pair("returnParameters", toJson(*_node.returnParameterList())), + make_pair("modifiers", toJson(_node.modifiers())), + make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue), + make_pair("implemented", _node.isImplemented()), + make_pair("scope", idOrNull(_node.scope())) + }; + setJsonNode(_node, "FunctionDefinition", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(VariableDeclaration const& _node) { - std::vector<pair<string const, Json::Value const>> attributes = { + std::vector<pair<string, Json::Value>> attributes = { make_pair("name", _node.name()), - make_pair("type", type(_node)), + make_pair("typeName", toJsonOrNull(_node.typeName())), make_pair("constant", _node.isConstant()), + make_pair("stateVariable", _node.isStateVariable()), make_pair("storageLocation", location(_node.referenceLocation())), - make_pair("visibility", visibility(_node.visibility())) - }; + make_pair("visibility", visibility(_node.visibility())), + make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), + make_pair("scope", idOrNull(_node.scope())), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) + }; if (m_inEvent) attributes.push_back(make_pair("indexed", _node.isIndexed())); - addJsonNode(_node, "VariableDeclaration", attributes, true); - return true; - + setJsonNode(_node, "VariableDeclaration", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(ModifierDefinition const& _node) { - addJsonNode(_node, "ModifierDefinition", { make_pair("name", _node.name()) }, true); - return true; + setJsonNode(_node, "ModifierDefinition", { + make_pair("name", _node.name()), + make_pair("visibility", visibility(_node.visibility())), + make_pair("parameters", toJson(_node.parameterList())), + make_pair("body", toJson(_node.body())) + }); + return false; } bool ASTJsonConverter::visit(ModifierInvocation const& _node) { - addJsonNode(_node, "ModifierInvocation", {}, true); - return true; + setJsonNode(_node, "ModifierInvocation", { + make_pair("modifierName", toJson(*_node.name())), + make_pair("arguments", toJson(_node.arguments())) + }); + return false; } bool ASTJsonConverter::visit(TypeName const&) { - return true; + solAssert(false, "AST node of abstract type used."); + return false; } bool ASTJsonConverter::visit(EventDefinition const& _node) { m_inEvent = true; - addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true); - return true; + setJsonNode(_node, "EventDefinition", { + make_pair("name", _node.name()), + make_pair("parameters", toJson(_node.parameterList())), + make_pair("anonymous", _node.isAnonymous()) + }); + return false; } bool ASTJsonConverter::visit(ElementaryTypeName const& _node) { - addJsonNode(_node, "ElementaryTypeName", { make_pair("name", _node.typeName().toString()) }); - return true; + setJsonNode(_node, "ElementaryTypeName", { + make_pair("name", _node.typeName().toString()), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) + }); + return false; } bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) { - addJsonNode(_node, "UserDefinedTypeName", { - make_pair("name", boost::algorithm::join(_node.namePath(), ".")) + setJsonNode(_node, "UserDefinedTypeName", { + make_pair("name", namePathToString(_node.namePath())), + make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), + make_pair("contractScope", idOrNull(_node.annotation().contractScope)), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) }); - return true; + return false; } bool ASTJsonConverter::visit(FunctionTypeName const& _node) { - addJsonNode(_node, "FunctionTypeName", { + setJsonNode(_node, "FunctionTypeName", { make_pair("payable", _node.isPayable()), make_pair("visibility", visibility(_node.visibility())), - make_pair("constant", _node.isDeclaredConst()) - }, true); - return true; + make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), + make_pair("parameterTypes", toJson(*_node.parameterTypeList())), + make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) + }); + return false; } bool ASTJsonConverter::visit(Mapping const& _node) { - addJsonNode(_node, "Mapping", {}, true); - return true; + setJsonNode(_node, "Mapping", { + make_pair("keyType", toJson(_node.keyType())), + make_pair("valueType", toJson(_node.valueType())), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) + }); + return false; } bool ASTJsonConverter::visit(ArrayTypeName const& _node) { - addJsonNode(_node, "ArrayTypeName", {}, true); - return true; + setJsonNode(_node, "ArrayTypeName", { + make_pair("baseType", toJson(_node.baseType())), + make_pair("length", toJsonOrNull(_node.length())), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) + }); + return false; } bool ASTJsonConverter::visit(InlineAssembly const& _node) { - addJsonNode(_node, "InlineAssembly", {}, true); - return true; + Json::Value externalReferences(Json::arrayValue); + for (auto const& it : _node.annotation().externalReferences) + { + if (it.first) + { + Json::Value tuple(Json::objectValue); + tuple[it.first->name] = inlineAssemblyIdentifierToJson(it); + externalReferences.append(tuple); + } + } + setJsonNode(_node, "InlineAssembly", { + make_pair("operations", Json::Value(assembly::AsmPrinter()(_node.operations()))), + make_pair("externalReferences", std::move(externalReferences)) + }); + return false; } bool ASTJsonConverter::visit(Block const& _node) { - addJsonNode(_node, "Block", {}, true); - return true; + setJsonNode(_node, "Block", { + make_pair("statements", toJson(_node.statements())) + }); + return false; } bool ASTJsonConverter::visit(PlaceholderStatement const& _node) { - addJsonNode(_node, "PlaceholderStatement", {}); - return true; + setJsonNode(_node, "PlaceholderStatement", {}); + return false; } bool ASTJsonConverter::visit(IfStatement const& _node) { - addJsonNode(_node, "IfStatement", {}, true); - return true; + setJsonNode(_node, "IfStatement", { + make_pair("condition", toJson(_node.condition())), + make_pair("trueBody", toJson(_node.trueStatement())), + make_pair("falseBody", toJsonOrNull(_node.falseStatement())) + }); + return false; } bool ASTJsonConverter::visit(WhileStatement const& _node) { - addJsonNode( + setJsonNode( _node, _node.isDoWhile() ? "DoWhileStatement" : "WhileStatement", - {}, - true); - return true; + { + make_pair("condition", toJson(_node.condition())), + make_pair("body", toJson(_node.body())) + } + ); + return false; } bool ASTJsonConverter::visit(ForStatement const& _node) { - addJsonNode(_node, "ForStatement", {}, true); - return true; + setJsonNode(_node, "ForStatement", { + make_pair("initializationExpression", toJsonOrNull(_node.initializationExpression())), + make_pair("condition", toJsonOrNull(_node.condition())), + make_pair("loopExpression", toJsonOrNull(_node.loopExpression())), + make_pair("body", toJson(_node.body())) + }); + return false; } bool ASTJsonConverter::visit(Continue const& _node) { - addJsonNode(_node, "Continue", {}); - return true; + setJsonNode(_node, "Continue", {}); + return false; } bool ASTJsonConverter::visit(Break const& _node) { - addJsonNode(_node, "Break", {}); - return true; + setJsonNode(_node, "Break", {}); + return false; } bool ASTJsonConverter::visit(Return const& _node) { - addJsonNode(_node, "Return", {}, true);; - return true; + setJsonNode(_node, "Return", { + make_pair("expression", toJsonOrNull(_node.expression())), + make_pair("functionReturnParameters", idOrNull(_node.annotation().functionReturnParameters)) + }); + return false; } bool ASTJsonConverter::visit(Throw const& _node) { - addJsonNode(_node, "Throw", {}, true);; - return true; + setJsonNode(_node, "Throw", {});; + return false; } bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node) { - addJsonNode(_node, "VariableDeclarationStatement", {}, true); - return true; + Json::Value varDecs(Json::arrayValue); + for (auto const& v: _node.annotation().assignments) + varDecs.append(idOrNull(v)); + setJsonNode(_node, "VariableDeclarationStatement", { + make_pair("assignments", std::move(varDecs)), + make_pair("declarations", toJson(_node.declarations())), + make_pair("initialValue", toJsonOrNull(_node.initialValue())) + }); + return false; } bool ASTJsonConverter::visit(ExpressionStatement const& _node) { - addJsonNode(_node, "ExpressionStatement", {}, true); - return true; + setJsonNode(_node, "ExpressionStatement", { + make_pair("expression", toJson(_node.expression())) + }); + return false; } bool ASTJsonConverter::visit(Conditional const& _node) { - addJsonNode(_node, "Conditional", {}, true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("condition", toJson(_node.condition())), + make_pair("trueExpression", toJson(_node.trueExpression())), + make_pair("falseExpression", toJson(_node.falseExpression())) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "Conditional", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(Assignment const& _node) { - addJsonNode(_node, "Assignment", - { make_pair("operator", Token::toString(_node.assignmentOperator())), - make_pair("type", type(_node)) }, - true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("operator", Token::toString(_node.assignmentOperator())), + make_pair("leftHandSide", toJson(_node.leftHandSide())), + make_pair("rightHandSide", toJson(_node.rightHandSide())) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode( _node, "Assignment", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(TupleExpression const& _node) { - addJsonNode(_node, "TupleExpression",{}, true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("isInlineArray", Json::Value(_node.isInlineArray())), + make_pair("components", toJson(_node.components())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "TupleExpression", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(UnaryOperation const& _node) { - addJsonNode(_node, "UnaryOperation", - { make_pair("prefix", _node.isPrefixOperation()), - make_pair("operator", Token::toString(_node.getOperator())), - make_pair("type", type(_node)) }, - true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("prefix", _node.isPrefixOperation()), + make_pair("operator", Token::toString(_node.getOperator())), + make_pair("subExpression", toJson(_node.subExpression())) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "UnaryOperation", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(BinaryOperation const& _node) { - addJsonNode(_node, "BinaryOperation", { + std::vector<pair<string, Json::Value>> attributes = { make_pair("operator", Token::toString(_node.getOperator())), - make_pair("type", type(_node)) - }, true); - return true; + make_pair("leftExpression", toJson(_node.leftExpression())), + make_pair("rightExpression", toJson(_node.rightExpression())), + make_pair("commonType", typePointerToJson(_node.annotation().commonType)), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "BinaryOperation", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(FunctionCall const& _node) { - addJsonNode(_node, "FunctionCall", { - make_pair("type_conversion", _node.annotation().isTypeConversion), - make_pair("type", type(_node)) - }, true); - return true; + Json::Value names(Json::arrayValue); + for (auto const& name: _node.names()) + names.append(Json::Value(*name)); + std::vector<pair<string, Json::Value>> attributes = { + make_pair("expression", toJson(_node.expression())), + make_pair("names", std::move(names)), + make_pair("arguments", toJson(_node.arguments())) + }; + if (m_legacy) + { + attributes.push_back(make_pair("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall)); + attributes.push_back(make_pair("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion)); + } + else + attributes.push_back(make_pair("kind", functionCallKind(_node.annotation().kind))); + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "FunctionCall", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(NewExpression const& _node) { - addJsonNode(_node, "NewExpression", { make_pair("type", type(_node)) }, true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("typeName", toJson(_node.typeName())) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "NewExpression", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(MemberAccess const& _node) { - addJsonNode(_node, "MemberAccess", { - make_pair("member_name", _node.memberName()), - make_pair("type", type(_node)) - }, true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair(m_legacy ? "member_name" : "memberName", _node.memberName()), + make_pair("expression", toJson(_node.expression())), + make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "MemberAccess", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(IndexAccess const& _node) { - addJsonNode(_node, "IndexAccess", { make_pair("type", type(_node)) }, true); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair("baseExpression", toJson(_node.baseExpression())), + make_pair("indexExpression", toJsonOrNull(_node.indexExpression())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "IndexAccess", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(Identifier const& _node) { - addJsonNode(_node, "Identifier", - { make_pair("value", _node.name()), make_pair("type", type(_node)) }); - return true; + Json::Value overloads(Json::arrayValue); + for (auto const& dec: _node.annotation().overloadedDeclarations) + overloads.append(nodeId(*dec)); + setJsonNode(_node, "Identifier", { + make_pair(m_legacy ? "value" : "name", _node.name()), + make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), + make_pair("overloadedDeclarations", overloads), + make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)), + make_pair("argumentTypes", typePointerToJson(_node.annotation().argumentTypes)) + }); + return false; } bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) { - addJsonNode(_node, "ElementaryTypeNameExpression", { - make_pair("value", _node.typeName().toString()), - make_pair("type", type(_node)) - }); - return true; + std::vector<pair<string, Json::Value>> attributes = { + make_pair(m_legacy ? "value" : "typeName", _node.typeName().toString()) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(Literal const& _node) { - char const* tokenString = Token::toString(_node.token()); Json::Value value{_node.value()}; if (!dev::validateUTF8(_node.value())) value = Json::nullValue; Token::Value subdenomination = Token::Value(_node.subDenomination()); - addJsonNode(_node, "Literal", { - make_pair("token", tokenString ? tokenString : Json::Value()), + std::vector<pair<string, Json::Value>> attributes = { + make_pair(m_legacy ? "token" : "kind", literalTokenKind(_node.token())), make_pair("value", value), - make_pair("hexvalue", toHex(_node.value())), + make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(_node.value())), make_pair( "subdenomination", subdenomination == Token::Illegal ? Json::nullValue : Json::Value{Token::toString(subdenomination)} - ), - make_pair("type", type(_node)) - }); - return true; + ) + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "Literal", std::move(attributes)); + return false; } -void ASTJsonConverter::endVisit(SourceUnit const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(PragmaDirective const&) -{ -} - -void ASTJsonConverter::endVisit(ImportDirective const&) -{ -} - -void ASTJsonConverter::endVisit(ContractDefinition const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(InheritanceSpecifier const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(UsingForDirective const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(StructDefinition const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(EnumDefinition const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(EnumValue const&) -{ -} - -void ASTJsonConverter::endVisit(ParameterList const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(FunctionDefinition const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(VariableDeclaration const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(ModifierDefinition const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(ModifierInvocation const&) -{ - goUp(); -} void ASTJsonConverter::endVisit(EventDefinition const&) { m_inEvent = false; - goUp(); -} - -void ASTJsonConverter::endVisit(TypeName const&) -{ -} - -void ASTJsonConverter::endVisit(ElementaryTypeName const&) -{ -} - -void ASTJsonConverter::endVisit(UserDefinedTypeName const&) -{ -} - -void ASTJsonConverter::endVisit(FunctionTypeName const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Mapping const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(ArrayTypeName const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(InlineAssembly const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Block const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(PlaceholderStatement const&) -{ -} - -void ASTJsonConverter::endVisit(IfStatement const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(WhileStatement const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(ForStatement const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Continue const&) -{ -} - -void ASTJsonConverter::endVisit(Break const&) -{ -} - -void ASTJsonConverter::endVisit(Return const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Throw const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(VariableDeclarationStatement const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(ExpressionStatement const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Conditional const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Assignment const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(TupleExpression const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(UnaryOperation const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(BinaryOperation const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(FunctionCall const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(NewExpression const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(MemberAccess const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(IndexAccess const&) -{ - goUp(); -} - -void ASTJsonConverter::endVisit(Identifier const&) -{ -} - -void ASTJsonConverter::endVisit(ElementaryTypeNameExpression const&) -{ -} - -void ASTJsonConverter::endVisit(Literal const&) -{ -} - -void ASTJsonConverter::process() -{ - if (!processed) - m_ast->accept(*this); - processed = true; } string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility) @@ -709,6 +762,52 @@ string ASTJsonConverter::location(VariableDeclaration::Location _location) } } +string ASTJsonConverter::contractKind(ContractDefinition::ContractKind _kind) +{ + switch (_kind) + { + case ContractDefinition::ContractKind::Interface: + return "interface"; + case ContractDefinition::ContractKind::Contract: + return "contract"; + case ContractDefinition::ContractKind::Library: + return "library"; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of contract.")); + } +} + +string ASTJsonConverter::functionCallKind(FunctionCallKind _kind) +{ + switch (_kind) + { + case FunctionCallKind::FunctionCall: + return "functionCall"; + case FunctionCallKind::TypeConversion: + return "typeConversion"; + case FunctionCallKind::StructConstructorCall: + return "structConstructorCall"; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of function call .")); + } +} + +string ASTJsonConverter::literalTokenKind(Token::Value _token) +{ + switch (_token) + { + case dev::solidity::Token::Number: + return "number"; + case dev::solidity::Token::StringLiteral: + return "string"; + case dev::solidity::Token::TrueLiteral: + case dev::solidity::Token::FalseLiteral: + return "bool"; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of literal token.")); + } +} + string ASTJsonConverter::type(Expression const& _expression) { return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown"; diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index bd5e6560..27114c2a 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -26,7 +26,6 @@ #include <stack> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/Utils.h> #include <libsolidity/ast/ASTAnnotations.h> #include <json/json.h> @@ -42,15 +41,23 @@ class ASTJsonConverter: public ASTConstVisitor { public: /// Create a converter to JSON for the given abstract syntax tree. + /// @a _legacy if true, use legacy format /// @a _sourceIndices is used to abbreviate source names in source locations. explicit ASTJsonConverter( - ASTNode const& _ast, + bool _legacy, std::map<std::string, unsigned> _sourceIndices = std::map<std::string, unsigned>() ); /// Output the json representation of the AST to _stream. - void print(std::ostream& _stream); - Json::Value const& json(); - + void print(std::ostream& _stream, ASTNode const& _node); + Json::Value toJson(ASTNode const& _node); + template <class T> + Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes) + { + Json::Value ret(Json::arrayValue); + for (auto const& n: _nodes) + ret.append(n ? toJson(*n) : Json::nullValue); + return ret; + } bool visit(SourceUnit const& _node) override; bool visit(PragmaDirective const& _node) override; bool visit(ImportDirective const& _node) override; @@ -97,82 +104,61 @@ public: bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(Literal const& _node) override; - void endVisit(SourceUnit const&) override; - void endVisit(PragmaDirective const&) override; - void endVisit(ImportDirective const&) override; - void endVisit(ContractDefinition const&) override; - void endVisit(InheritanceSpecifier const&) override; - void endVisit(UsingForDirective const&) override; - void endVisit(StructDefinition const&) override; - void endVisit(EnumDefinition const&) override; - void endVisit(EnumValue const&) override; - void endVisit(ParameterList const&) override; - void endVisit(FunctionDefinition const&) override; - void endVisit(VariableDeclaration const&) override; - void endVisit(ModifierDefinition const&) override; - void endVisit(ModifierInvocation const&) override; void endVisit(EventDefinition const&) override; - void endVisit(TypeName const&) override; - void endVisit(ElementaryTypeName const&) override; - void endVisit(UserDefinedTypeName const&) override; - void endVisit(FunctionTypeName const&) override; - void endVisit(Mapping const&) override; - void endVisit(ArrayTypeName const&) override; - void endVisit(InlineAssembly const&) override; - void endVisit(Block const&) override; - void endVisit(PlaceholderStatement const&) override; - void endVisit(IfStatement const&) override; - void endVisit(WhileStatement const&) override; - void endVisit(ForStatement const&) override; - void endVisit(Continue const&) override; - void endVisit(Break const&) override; - void endVisit(Return const&) override; - void endVisit(Throw const&) override; - void endVisit(VariableDeclarationStatement const&) override; - void endVisit(ExpressionStatement const&) override; - void endVisit(Conditional const&) override; - void endVisit(Assignment const&) override; - void endVisit(TupleExpression const&) override; - void endVisit(UnaryOperation const&) override; - void endVisit(BinaryOperation const&) override; - void endVisit(FunctionCall const&) override; - void endVisit(NewExpression const&) override; - void endVisit(MemberAccess const&) override; - void endVisit(IndexAccess const&) override; - void endVisit(Identifier const&) override; - void endVisit(ElementaryTypeNameExpression const&) override; - void endVisit(Literal const&) override; private: - void process(); - void addJsonNode( + void setJsonNode( ASTNode const& _node, std::string const& _nodeName, - std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes, - bool _hasChildren + std::initializer_list<std::pair<std::string, Json::Value>>&& _attributes ); - void addJsonNode( + void setJsonNode( ASTNode const& _node, std::string const& _nodeName, - std::vector<std::pair<std::string const, Json::Value const>> const& _attributes, - bool _hasChildren + std::vector<std::pair<std::string, Json::Value>>&& _attributes ); std::string sourceLocationToString(SourceLocation const& _location) const; + std::string namePathToString(std::vector<ASTString> const& _namePath) const; + Json::Value idOrNull(ASTNode const* _pt) + { + return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue; + } + Json::Value toJsonOrNull(ASTNode const* _node) + { + return _node ? toJson(*_node) : Json::nullValue; + } + Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info); std::string visibility(Declaration::Visibility const& _visibility); std::string location(VariableDeclaration::Location _location); + std::string contractKind(ContractDefinition::ContractKind _kind); + std::string functionCallKind(FunctionCallKind _kind); + std::string literalTokenKind(Token::Value _token); std::string type(Expression const& _expression); std::string type(VariableDeclaration const& _varDecl); - inline void goUp() + int nodeId(ASTNode const& _node) { - solAssert(!m_jsonNodePtrs.empty(), "Uneven json nodes stack. Internal error."); - m_jsonNodePtrs.pop(); + return _node.id(); } - + template<class Container> + Json::Value getContainerIds(Container const& container) + { + Json::Value tmp(Json::arrayValue); + for (auto const& element: container) + { + solAssert(element, ""); + tmp.append(nodeId(*element)); + } + return tmp; + } + Json::Value typePointerToJson(TypePointer _tp); + Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps); + void appendExpressionAttributes( + std::vector<std::pair<std::string, Json::Value>> &_attributes, + ExpressionAnnotation const& _annotation + ); + bool m_legacy = false; ///< if true, use legacy format bool m_inEvent = false; ///< whether we are currently inside an event or not - bool processed = false; - Json::Value m_astJson; - std::stack<Json::Value*> m_jsonNodePtrs; - ASTNode const* m_ast; + Json::Value m_currentValue; std::map<std::string, unsigned> m_sourceIndices; }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 41ee6498..7dc6c4a6 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -22,7 +22,6 @@ #include <libsolidity/ast/Types.h> -#include <libsolidity/interface/Utils.h> #include <libsolidity/ast/AST.h> #include <libdevcore/CommonIO.h> @@ -2183,6 +2182,8 @@ string FunctionType::identifier() const case Kind::ArrayPush: id += "arraypush"; break; case Kind::ByteArrayPush: id += "bytearraypush"; break; case Kind::ObjectCreation: id += "objectcreation"; break; + case Kind::Assert: id += "assert"; break; + case Kind::Require: id += "require";break; default: solAssert(false, "Unknown function location."); break; } if (isConstant()) @@ -2247,6 +2248,16 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const return TypePointer(); } +TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) + return TypePointer(); + FunctionType const& other = dynamic_cast<FunctionType const&>(*_other); + if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) + return commonType(shared_from_this(), _other); + return TypePointer(); +} + string FunctionType::canonicalName(bool) const { solAssert(m_kind == Kind::External, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c4ffc44c..f7a73ab5 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -933,6 +933,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; @@ -1038,6 +1039,7 @@ public: virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; virtual bool canLiveOutsideStorage() const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer encodingType() const override { return std::make_shared<IntegerType>(256); @@ -1116,11 +1118,7 @@ public: explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override - { - return TypePointer(); - } - + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -1178,6 +1176,7 @@ public: virtual std::string identifier() const override { return "t_inaccessible"; } virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index bdd29abd..67ca22f1 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -25,7 +25,7 @@ #include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/ast/Types.h> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/Exceptions.h> #include <libsolidity/codegen/LValue.h> using namespace std; @@ -449,7 +449,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; if (_sourceType.isDynamicallySized()) { - // actual array data is stored at SHA3(storage_offset) + // actual array data is stored at KECCAK256(storage_offset) m_context << Instruction::SWAP1; utils.computeHashStatic(); m_context << Instruction::SWAP1; @@ -731,7 +731,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const _context << Instruction::POP; } - // Change of length for a regular array (i.e. length at location, data at sha3(location)). + // Change of length for a regular array (i.e. length at location, data at KECCAK256(location)). // stack: ref new_length old_length // store new length _context << Instruction::DUP2; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 51dd9fd2..6875bda1 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -25,8 +25,12 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/Version.h> -#include <libsolidity/inlineasm/AsmData.h> -#include <libsolidity/inlineasm/AsmStack.h> +#include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/parsing/Scanner.h> +#include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <boost/algorithm/string/replace.hpp> @@ -120,6 +124,7 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present"); m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } @@ -240,6 +245,20 @@ CompilerContext& CompilerContext::appendConditionalInvalid() return *this; } +CompilerContext& CompilerContext::appendRevert() +{ + return *this << u256(0) << u256(0) << Instruction::REVERT; +} + +CompilerContext& CompilerContext::appendConditionalRevert() +{ + *this << Instruction::ISZERO; + eth::AssemblyItem afterTag = appendConditionalJump(); + appendRevert(); + *this << afterTag; + return *this; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack<ASTNode const*> newStack; @@ -264,12 +283,13 @@ void CompilerContext::appendInlineAssembly( assembly = &replacedAssembly; } - unsigned startStackHeight = stackHeight(); + int startStackHeight = stackHeight(); - assembly::ExternalIdentifierAccess identifierAccess; + julia::ExternalIdentifierAccess identifierAccess; identifierAccess.resolve = [&]( assembly::Identifier const& _identifier, - assembly::IdentifierContext + julia::IdentifierContext, + bool ) { auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); @@ -277,31 +297,42 @@ void CompilerContext::appendInlineAssembly( }; identifierAccess.generateCode = [&]( assembly::Identifier const& _identifier, - assembly::IdentifierContext _context, - eth::Assembly& _assembly + julia::IdentifierContext _context, + julia::AbstractAssembly& _assembly ) { auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); solAssert(it != _localVariables.end(), ""); - unsigned stackDepth = _localVariables.end() - it; - int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; - if (_context == assembly::IdentifierContext::LValue) + int stackDepth = _localVariables.end() - it; + int stackDiff = _assembly.stackHeight() - startStackHeight + stackDepth; + if (_context == julia::IdentifierContext::LValue) stackDiff -= 1; if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << - errinfo_comment("Stack too deep, try removing local variables.") + errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") ); - if (_context == assembly::IdentifierContext::RValue) - _assembly.append(dupInstruction(stackDiff)); + if (_context == julia::IdentifierContext::RValue) + _assembly.appendInstruction(dupInstruction(stackDiff)); else { - _assembly.append(swapInstruction(stackDiff)); - _assembly.append(Instruction::POP); + _assembly.appendInstruction(swapInstruction(stackDiff)); + _assembly.appendInstruction(Instruction::POP); } }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); + ErrorList errors; + ErrorReporter errorReporter(errors); + auto scanner = make_shared<Scanner>(CharStream(*assembly), "--CODEGEN--"); + auto parserResult = assembly::Parser(errorReporter).parse(scanner); + solAssert(parserResult, "Failed to parse inline assembly block."); + solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block."); + + assembly::AsmAnalysisInfo analysisInfo; + assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve); + solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block."); + solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); + assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index c37142c9..1968c1e1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -136,13 +136,15 @@ public: /// Appends a JUMP to a new tag and @returns the tag eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack - CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); + CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Appends an INVALID instruction - CompilerContext& appendInvalid(); + CompilerContext& appendInvalid(); /// Appends a conditional INVALID instruction - CompilerContext& appendConditionalInvalid(); - /// Returns an "ErrorTag" - eth::AssemblyItem errorTag() { return m_asm->errorTag(); } + CompilerContext& appendConditionalInvalid(); + /// Appends a REVERT(0, 0) call + CompilerContext& appendRevert(); + /// Appends a conditional REVERT(0, 0) call + CompilerContext& appendConditionalRevert(); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. @@ -151,10 +153,10 @@ public: eth::AssemblyItem newTag() { return m_asm->newTag(); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. - eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; } - void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); } + eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); } + void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); } /// Pushes the offset of the subroutine. - void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); } + void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); } /// Pushes the size of the final program void appendProgramSize() { m_asm->appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dc0b340c..7067ddd5 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -128,7 +128,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << Instruction::DUP1; storeStringData(bytesConstRef(str->value())); if (_padToWordBoundaries) - m_context << u256(((str->value().size() + 31) / 32) * 32); + m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32)); else m_context << u256(str->value().size()); m_context << Instruction::ADD; @@ -180,6 +180,9 @@ void CompilerUtils::encodeToMemory( t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(); } + if (_givenTypes.empty()) + return; + // Stack during operation: // <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem> // The values dyn_head_i are added during the first loop and they point to the head part @@ -299,40 +302,15 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) m_context << Instruction::SWAP1 << Instruction::POP; } -void CompilerUtils::memoryCopyPrecompile() -{ - // Stack here: size target source - - m_context.appendInlineAssembly(R"( - { - let words := div(add(len, 31), 32) - let cost := add(15, mul(3, words)) - jumpi(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len))) - } - )", - { "len", "dst", "src" }, - map<string, string> { - { "$identityContractAddress", toString(identityContractAddress) } - } - ); - m_context << Instruction::POP << Instruction::POP << Instruction::POP; -} - void CompilerUtils::memoryCopy32() { // Stack here: size target source m_context.appendInlineAssembly(R"( { - jumpi(end, eq(len, 0)) - start: - mstore(dst, mload(src)) - jumpi(end, iszero(gt(len, 32))) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start) - end: + for { let i := 0 } lt(i, len) { i := add(i, 32) } { + mstore(add(dst, i), mload(add(src, i))) + } } )", { "len", "dst", "src" } @@ -346,21 +324,22 @@ void CompilerUtils::memoryCopy() m_context.appendInlineAssembly(R"( { - // copy 32 bytes at once - start32: - jumpi(end32, lt(len, 32)) - mstore(dst, mload(src)) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start32) - end32: + // copy 32 bytes at once + for + {} + iszero(lt(len, 32)) + { + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + } + { mstore(dst, mload(src)) } - // copy the remainder (0 < len < 32) - let mask := sub(exp(256, sub(32, len)), 1) - let srcpart := and(mload(src), not(mask)) - let dstpart := and(mload(dst), mask) - mstore(dst, or(srcpart, dstpart)) + // copy the remainder (0 < len < 32) + let mask := sub(exp(256, sub(32, len)), 1) + let srcpart := and(mload(src), not(mask)) + let dstpart := and(mload(dst), mask) + mstore(dst, or(srcpart, dstpart)) } )", { "len", "dst", "src" } @@ -374,13 +353,16 @@ void CompilerUtils::splitExternalFunctionType(bool _leftAligned) // address (right aligned), function identifier (right aligned) if (_leftAligned) { - m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV; + m_context << Instruction::DUP1; + rightShiftNumberOnStack(64 + 32, false); // <input> <address> - m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; + m_context << Instruction::SWAP1; + rightShiftNumberOnStack(64, false); } else { - m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + m_context << Instruction::DUP1; + rightShiftNumberOnStack(32, false); m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; } m_context << u256(0xffffffffUL) << Instruction::AND; @@ -392,10 +374,10 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned) m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; if (!_leftAligned) m_context << ((u256(1) << 160) - 1) << Instruction::AND; - m_context << (u256(1) << 32) << Instruction::MUL; + leftShiftNumberOnStack(32); m_context << Instruction::OR; if (_leftAligned) - m_context << (u256(1) << 64) << Instruction::MUL; + leftShiftNumberOnStack(64); } void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) @@ -404,14 +386,21 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) // If there is a runtime context, we have to merge both labels into the same // stack slot in case we store it in storage. if (CompilerContext* rtc = m_context.runtimeContext()) + { + leftShiftNumberOnStack(32); m_context << - (u256(1) << 32) << - Instruction::MUL << rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << Instruction::OR; + } } -void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits) +void CompilerUtils::convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded, + bool _chopSignBits, + bool _asPartOfArgumentDecoding +) { // For a type extension, we need to remove all higher-order bits that we might have ignored in // previous operations. @@ -440,7 +429,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp // conversion from bytes to integer. no need to clean the high bit // only to shift right because of opposite alignment IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); - m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << Instruction::SWAP1 << Instruction::DIV; + rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8, false); if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); } @@ -469,7 +458,10 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalInvalid(); + if (_asPartOfArgumentDecoding) + m_context.appendConditionalRevert(); + else + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } break; @@ -488,7 +480,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) if (targetBytesType.numBytes() * 8 > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); - m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << Instruction::MUL; + leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); } else if (targetTypeCategory == Type::Category::Enum) { @@ -953,7 +945,7 @@ unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _varia void CompilerUtils::computeHashStatic() { storeInMemory(0); - m_context << u256(32) << u256(0) << Instruction::SHA3; + m_context << u256(32) << u256(0) << Instruction::KECCAK256; } void CompilerUtils::storeStringData(bytesConstRef _data) @@ -998,13 +990,13 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda { bool leftAligned = _type.category() == Type::Category::FixedBytes; // add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); - m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; + int shiftFactor = (32 - numBytes) * 8; + rightShiftNumberOnStack(shiftFactor, false); if (leftAligned) - m_context << shiftFactor << Instruction::MUL; + leftShiftNumberOnStack(shiftFactor); } if (_fromCalldata) - convertType(_type, _type, true); + convertType(_type, _type, true, false, true); return numBytes; } @@ -1019,6 +1011,18 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND; } +void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) +{ + solAssert(_bits < 256, ""); + m_context << (u256(1) << _bits) << Instruction::MUL; +} + +void CompilerUtils::rightShiftNumberOnStack(unsigned _bits, bool _isSigned) +{ + solAssert(_bits < 256, ""); + m_context << (u256(1) << _bits) << Instruction::SWAP1 << (_isSigned ? Instruction::SDIV : Instruction::DIV); +} + unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) { unsigned numBytes = _type.calldataEncodedSize(_padToWords); @@ -1031,7 +1035,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) convertType(_type, _type, true); if (numBytes != 32 && !leftAligned && !_padToWords) // shift the value accordingly before storing - m_context << (u256(1) << ((32 - numBytes) * 8)) << Instruction::MUL; + leftShiftNumberOnStack((32 - numBytes) * 8); } return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index b9ed6757..fb169463 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -109,15 +109,13 @@ public: /// Stack post: <updated_memptr> void zeroInitialiseMemoryArray(ArrayType const& _type); - /// Uses a CALL to the identity contract to perform a memory-to-memory copy. - /// Stack pre: <size> <target> <source> - /// Stack post: - void memoryCopyPrecompile(); /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length. + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy32(); /// Copies data in memory (regions cannot overlap). + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy(); @@ -139,7 +137,15 @@ public: /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. /// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer. - void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false); + /// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT, + /// otherwise they are flagged with INVALID. + void convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded = false, + bool _chopSignBits = false, + bool _asPartOfArgumentDecoding = false + ); /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. @@ -170,7 +176,13 @@ public: static unsigned sizeOnStack(std::vector<T> const& _variables); static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes); - /// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type. + /// Helper function to shift top value on the stack to the left. + void leftShiftNumberOnStack(unsigned _bits); + + /// Helper function to shift top value on the stack to the right. + void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false); + + /// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type. void computeHashStatic(); /// Bytes we need to the start of call data. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 34ef13c0..c358a519 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -21,15 +21,20 @@ */ #include <libsolidity/codegen/ContractCompiler.h> -#include <algorithm> -#include <boost/range/adaptor/reversed.hpp> -#include <libevmasm/Instruction.h> -#include <libevmasm/Assembly.h> -#include <libevmasm/GasMeter.h> #include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/ast/AST.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/codegen/ExpressionCompiler.h> #include <libsolidity/codegen/CompilerUtils.h> + +#include <libevmasm/Instruction.h> +#include <libevmasm/Assembly.h> +#include <libevmasm/GasMeter.h> + +#include <boost/range/adaptor/reversed.hpp> + +#include <algorithm> + using namespace std; using namespace dev; using namespace dev::solidity; @@ -106,7 +111,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -262,16 +267,22 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << notFound; if (fallback) { + m_context.setStackOffset(0); if (!fallback->isPayable()) appendCallValueCheck(); + // Return tag is used to jump out of the function. eth::AssemblyItem returnTag = m_context.pushNewTag(); fallback->accept(*this); m_context << returnTag; - appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); + solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); + solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); + // Return tag gets consumed. + m_context.adjustStackOffset(-1); + m_context << Instruction::STOP; } else - m_context.appendInvalid(); + m_context.appendRevert(); for (auto const& it: interfaceFunctions) { @@ -280,16 +291,29 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); m_context << callDataUnpackerEntryPoints.at(it.first); + m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. if (!functionType->isPayable() && !_contract.isLibrary()) appendCallValueCheck(); + // Return tag is used to jump out of the function. eth::AssemblyItem returnTag = m_context.pushNewTag(); - m_context << CompilerUtils::dataStartOffset; - appendCalldataUnpacker(functionType->parameterTypes()); + if (!functionType->parameterTypes().empty()) + { + // Parameter for calldataUnpacker + m_context << CompilerUtils::dataStartOffset; + appendCalldataUnpacker(functionType->parameterTypes()); + } m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); m_context << returnTag; + // Return tag and input parameters get consumed. + m_context.adjustStackOffset( + CompilerUtils(m_context).sizeOnStack(functionType->returnParameterTypes()) - + CompilerUtils(m_context).sizeOnStack(functionType->parameterTypes()) - + 1 + ); + // Consumes the return parameters. appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary()); } } @@ -363,7 +387,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter // copy to memory // move calldata type up again CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack()); - CompilerUtils(m_context).convertType(*calldataType, arrayType); + CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true); // fetch next pointer again CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack()); } @@ -519,40 +543,42 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { - ErrorList errors; - assembly::CodeGenerator codeGen(errors); unsigned startStackHeight = m_context.stackHeight(); - assembly::ExternalIdentifierAccess identifierAccess; - identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) + julia::ExternalIdentifierAccess identifierAccess; + identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); if (ref == _inlineAssembly.annotation().externalReferences.end()) return size_t(-1); return ref->second.valueSize; }; - identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly) + identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, julia::IdentifierContext _context, julia::AbstractAssembly& _assembly) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); Declaration const* decl = ref->second.declaration; solAssert(!!decl, ""); - if (_context == assembly::IdentifierContext::RValue) + if (_context == julia::IdentifierContext::RValue) { - int const depositBefore = _assembly.deposit(); + int const depositBefore = _assembly.stackHeight(); solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) { solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); functionDef = &m_context.resolveVirtualFunction(*functionDef); - _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag(); + solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), ""); + _assembly.appendLabelReference(size_t(functionEntryLabel.data())); // If there is a runtime context, we have to merge both labels into the same // stack slot in case we store it in storage. if (CompilerContext* rtc = m_context.runtimeContext()) { - _assembly.append(u256(1) << 32); - _assembly.append(Instruction::MUL); - _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); - _assembly.append(Instruction::OR); + _assembly.appendConstant(u256(1) << 32); + _assembly.appendInstruction(Instruction::MUL); + auto runtimeEntryLabel = rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()); + solAssert(runtimeEntryLabel.data() <= std::numeric_limits<size_t>::max(), ""); + _assembly.appendLabelReference(size_t(runtimeEntryLabel.data())); + _assembly.appendInstruction(Instruction::OR); } } else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) @@ -570,7 +596,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) } else if (m_context.isLocalVariable(decl)) { - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); + int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable); if (ref->second.isSlot || ref->second.isOffset) { solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); @@ -587,7 +613,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) // only slot, offset is zero if (ref->second.isOffset) { - _assembly.append(u256(0)); + _assembly.appendConstant(u256(0)); return; } } @@ -601,7 +627,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) errinfo_comment("Stack too deep, try removing local variables.") ); solAssert(variable->type()->sizeOnStack() == 1, ""); - _assembly.append(dupInstruction(stackDiff)); + _assembly.appendInstruction(dupInstruction(stackDiff)); } else solAssert(false, ""); @@ -610,11 +636,11 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); solAssert(contract->isLibrary(), ""); - _assembly.appendLibraryAddress(contract->fullyQualifiedName()); + _assembly.appendLinkerSymbol(contract->fullyQualifiedName()); } else solAssert(false, "Invalid declaration type."); - solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), ""); + solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), ""); } else { @@ -626,25 +652,24 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) "Can only assign to stack variables in inline assembly." ); solAssert(variable->type()->sizeOnStack() == 1, ""); - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1; + int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1; if (stackDiff > 16 || stackDiff < 1) BOOST_THROW_EXCEPTION( CompilerError() << errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") + errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") ); - _assembly.append(swapInstruction(stackDiff)); - _assembly.append(Instruction::POP); + _assembly.appendInstruction(swapInstruction(stackDiff)); + _assembly.appendInstruction(Instruction::POP); } }; solAssert(_inlineAssembly.annotation().analysisInfo, ""); - codeGen.assemble( + assembly::CodeGenerator::assemble( _inlineAssembly.operations(), *_inlineAssembly.annotation().analysisInfo, m_context.nonConstAssembly(), identifierAccess ); - solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested."); m_context.setStackOffset(startStackHeight); return false; } @@ -799,8 +824,7 @@ bool ContractCompiler::visit(Throw const& _throw) { CompilerContext::LocationSetter locationSetter(m_context, _throw); // Do not send back an error detail. - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); return false; } @@ -873,6 +897,7 @@ void ContractCompiler::appendModifierOrFunctionCode() solAssert(m_currentFunction, ""); unsigned stackSurplus = 0; Block const* codeBlock = nullptr; + vector<VariableDeclaration const*> addedVariables; m_modifierDepth++; @@ -896,6 +921,7 @@ void ContractCompiler::appendModifierOrFunctionCode() for (unsigned i = 0; i < modifier.parameters().size(); ++i) { m_context.addVariable(*modifier.parameters()[i]); + addedVariables.push_back(modifier.parameters()[i].get()); compileExpression( *modifierInvocation->arguments()[i], modifier.parameters()[i]->annotation().type @@ -922,6 +948,8 @@ void ContractCompiler::appendModifierOrFunctionCode() m_returnTags.pop_back(); CompilerUtils(m_context).popStackSlots(stackSurplus); + for (auto var: addedVariables) + m_context.removeVariable(*var); } m_modifierDepth--; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index f018b311..82518e8c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -32,6 +32,7 @@ #include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/codegen/LValue.h> #include <libevmasm/GasMeter.h> + using namespace std; namespace dev @@ -87,6 +88,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& FunctionType accessorType(_varDecl); TypePointers paramTypes = accessorType.parameterTypes(); + m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes)); // retrieve the position of the variable auto const& location = m_context.storageLocationOfVariable(_varDecl); @@ -110,7 +112,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // move key to memory. utils().copyToStackTop(paramTypes.size() - i, 1); utils().storeInMemory(0); - m_context << u256(64) << u256(0) << Instruction::SHA3; + m_context << u256(64) << u256(0) << Instruction::KECCAK256; // push offset m_context << u256(0); returnType = mappingType->valueType(); @@ -434,7 +436,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { CompilerContext::LocationSetter locationSetter(m_context, _functionCall); - if (_functionCall.annotation().isTypeConversion) + if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) { solAssert(_functionCall.arguments().size() == 1, ""); solAssert(_functionCall.names().empty(), ""); @@ -445,7 +447,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } FunctionTypePointer functionType; - if (_functionCall.annotation().isStructConstructorCall) + if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) { auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast<StructType const&>(*type.actualType()); @@ -476,7 +478,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(found, ""); } - if (_functionCall.annotation().isStructConstructorCall) + if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) { TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast<StructType const&>(*type.actualType()); @@ -524,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (m_context.runtimeContext()) // We have a runtime context, so we need the creation part. - m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + utils().rightShiftNumberOnStack(32, false); else // Extract the runtime part. m_context << ((u256(1) << 32) - 1) << Instruction::AND; @@ -586,7 +588,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::CREATE; // Check if zero (out of stack or not enough balance). m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -650,7 +652,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { // Check if zero (out of stack or not enough balance). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } break; case FunctionType::Kind::Selfdestruct: @@ -659,9 +661,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::SELFDESTRUCT; break; case FunctionType::Kind::Revert: - // memory offset returned - zero length - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); break; case FunctionType::Kind::SHA3: { @@ -674,7 +674,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().fetchFreeMemoryPointer(); utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); utils().toSizeAfterFreeMemoryPointer(); - m_context << Instruction::SHA3; + m_context << Instruction::KECCAK256; break; } case FunctionType::Kind::Log0: @@ -721,7 +721,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) true ); utils().toSizeAfterFreeMemoryPointer(); - m_context << Instruction::SHA3; + m_context << Instruction::KECCAK256; } else utils().convertType( @@ -887,9 +887,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) auto success = m_context.appendConditionalJump(); if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error - m_context << Instruction::INVALID; + m_context.appendInvalid(); else - m_context << u256(0) << u256(0) << Instruction::REVERT; + m_context.appendRevert(); // the success branch m_context << success; break; @@ -1214,7 +1214,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) utils().storeInMemoryDynamic(IntegerType(256)); m_context << u256(0); } - m_context << Instruction::SHA3; + m_context << Instruction::KECCAK256; m_context << u256(0); setLValueToStorageItem(_indexAccess); } @@ -1269,7 +1269,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context.appendConditionalInvalid(); m_context << Instruction::BYTE; - m_context << (u256(1) << (256 - 8)) << Instruction::MUL; + utils().leftShiftNumberOnStack(256 - 8); } else if (baseType.category() == Type::Category::TypeType) { @@ -1367,6 +1367,7 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) { + solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); if (_operator == Token::Equal || _operator == Token::NotEqual) { if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) @@ -1694,7 +1695,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); existenceChecked = true; } @@ -1730,7 +1731,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { //Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } utils().popStackSlots(remainsSize); diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index d0a8ac15..3b8cf1c6 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -28,7 +28,7 @@ #include <libevmasm/SourceLocation.h> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/codegen/LValue.h> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/Exceptions.h> namespace dev { namespace eth diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index a74a3d74..e19cf41e 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -186,7 +186,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) { - m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; + CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * m_dataType->storageBytes()); cleaned = true; } else if ( @@ -267,9 +267,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc else if (m_dataType->category() == Type::Category::FixedBytes) { solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes"); - m_context - << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) - << Instruction::SWAP1 << Instruction::DIV; + CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes(), false); } else { diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp deleted file mode 100644 index b6f17907..00000000 --- a/libsolidity/formal/Why3Translator.cpp +++ /dev/null @@ -1,902 +0,0 @@ -/* - 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 Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#include <libsolidity/formal/Why3Translator.h> -#include <boost/algorithm/string/predicate.hpp> - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -bool Why3Translator::process(SourceUnit const& _source) -{ - try - { - if (m_lines.size() != 1 || !m_lines.back().contents.empty()) - fatalError(_source, "Multiple source units not yet supported"); - appendPreface(); - _source.accept(*this); - } - catch (NoFormalType&) - { - solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions."); - } - catch (FatalError& /*_e*/) - { - solAssert(m_errorOccured, ""); - } - return !m_errorOccured; -} - -string Why3Translator::translation() const -{ - string result; - for (auto const& line: m_lines) - result += string(line.indentation, '\t') + line.contents + "\n"; - return result; -} - -void Why3Translator::error(ASTNode const& _node, string const& _description) -{ - auto err = make_shared<Error>(Error::Type::Why3TranslatorError); - *err << - errinfo_sourceLocation(_node.location()) << - errinfo_comment(_description); - m_errors.push_back(err); - m_errorOccured = true; -} - -void Why3Translator::fatalError(ASTNode const& _node, string const& _description) -{ - error(_node, _description); - BOOST_THROW_EXCEPTION(FatalError()); -} - -string Why3Translator::toFormalType(Type const& _type) const -{ - if (_type.category() == Type::Category::Bool) - return "bool"; - else if (auto type = dynamic_cast<IntegerType const*>(&_type)) - { - if (!type->isAddress() && !type->isSigned() && type->numBits() == 256) - return "uint256"; - } - else if (auto type = dynamic_cast<ArrayType const*>(&_type)) - { - if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory)) - { - // Not catching NoFormalType exception. Let the caller deal with it. - string base = toFormalType(*type->baseType()); - return "array " + base; - } - } - else if (auto mappingType = dynamic_cast<MappingType const*>(&_type)) - { - solAssert(mappingType->keyType(), "A mappingType misses a keyType."); - if (dynamic_cast<IntegerType const*>(&*mappingType->keyType())) - { - //@TODO Use the information from the key type and specify the length of the array as an invariant. - // Also the constructor need to specify the length of the array. - solAssert(mappingType->valueType(), "A mappingType misses a valueType."); - // Not catching NoFormalType exception. Let the caller deal with it. - string valueTypeFormal = toFormalType(*mappingType->valueType()); - return "array " + valueTypeFormal; - } - } - - BOOST_THROW_EXCEPTION(NoFormalType() - << errinfo_noFormalTypeFrom(_type.toString(true))); -} - -void Why3Translator::addLine(string const& _line) -{ - newLine(); - add(_line); - newLine(); -} - -void Why3Translator::add(string const& _str) -{ - m_lines.back().contents += _str; -} - -void Why3Translator::newLine() -{ - if (!m_lines.back().contents.empty()) - m_lines.push_back({"", m_lines.back().indentation}); -} - -void Why3Translator::unindent() -{ - newLine(); - solAssert(m_lines.back().indentation > 0, ""); - m_lines.back().indentation--; -} - -bool Why3Translator::visit(ContractDefinition const& _contract) -{ - if (m_seenContract) - error(_contract, "More than one contract not supported."); - m_seenContract = true; - m_currentContract.contract = &_contract; - if (_contract.isLibrary()) - error(_contract, "Libraries not supported."); - - addLine("module Contract_" + _contract.name()); - indent(); - addLine("use import int.Int"); - addLine("use import ref.Ref"); - addLine("use import map.Map"); - addLine("use import array.Array"); - addLine("use import int.ComputerDivision"); - addLine("use import mach.int.Unsigned"); - addLine("use import UInt256"); - addLine("exception Revert"); - addLine("exception Return"); - - if (_contract.stateVariables().empty()) - addLine("type state = ()"); - else - { - addLine("type state = {"); - indent(); - m_currentContract.stateVariables = _contract.stateVariables(); - for (VariableDeclaration const* variable: m_currentContract.stateVariables) - { - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : ""; - fatalError(*variable, "Type" + typeName + " not supported for state variable."); - } - addLine("mutable _" + variable->name() + ": " + varType); - } - unindent(); - addLine("}"); - } - - addLine("type account = {"); - indent(); - addLine("mutable balance: uint256;"); - addLine("storage: state"); - unindent(); - addLine("}"); - - addLine("val external_call (this: account): bool"); - indent(); - addLine("ensures { result = false -> this = (old this) }"); - addLine("writes { this }"); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - unindent(); - - if (!_contract.baseContracts().empty()) - error(*_contract.baseContracts().front(), "Inheritance not supported."); - if (!_contract.definedStructs().empty()) - error(*_contract.definedStructs().front(), "User-defined types not supported."); - if (!_contract.definedEnums().empty()) - error(*_contract.definedEnums().front(), "User-defined types not supported."); - if (!_contract.events().empty()) - error(*_contract.events().front(), "Events not supported."); - if (!_contract.functionModifiers().empty()) - error(*_contract.functionModifiers().front(), "Modifiers not supported."); - - ASTNode::listAccept(_contract.definedFunctions(), *this); - - return false; -} - -void Why3Translator::endVisit(ContractDefinition const&) -{ - m_currentContract.reset(); - unindent(); - addLine("end"); -} - -bool Why3Translator::visit(FunctionDefinition const& _function) -{ - if (!_function.isImplemented()) - { - error(_function, "Unimplemented functions not supported."); - return false; - } - if (_function.name().empty()) - { - error(_function, "Fallback functions not supported."); - return false; - } - if (!_function.modifiers().empty()) - { - error(_function, "Modifiers not supported."); - return false; - } - - m_localVariables.clear(); - for (auto const& var: _function.parameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.returnParameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.localVariables()) - m_localVariables[var->name()] = var; - - add("let rec _" + _function.name()); - add(" (this: account)"); - for (auto const& param: _function.parameters()) - { - string paramType; - try - { - paramType = toFormalType(*param->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported."); - } - if (param->name().empty()) - error(*param, "Anonymous function parameters not supported."); - add(" (arg_" + param->name() + ": " + paramType + ")"); - } - add(":"); - - indent(); - indent(); - string retString = "("; - for (auto const& retParam: _function.returnParameters()) - { - string paramType; - try - { - paramType = toFormalType(*retParam->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported."); - } - if (retString.size() != 1) - retString += ", "; - retString += paramType; - } - add(retString + ")"); - unindent(); - - addSourceFromDocStrings(_function.annotation()); - if (!m_currentContract.contract) - error(_function, "Only functions inside contracts allowed."); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - - if (_function.isDeclaredConst()) - addLine("ensures { (old this) = this }"); - else - addLine("writes { this }"); - - addLine("="); - - // store the prestate in the case we need to revert - addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in "); - - // initialise local variables - for (auto const& variable: _function.parameters()) - addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in"); - for (auto const& variable: _function.returnParameters()) - { - if (variable->name().empty()) - error(*variable, "Unnamed return variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - for (VariableDeclaration const* variable: _function.localVariables()) - { - if (variable->name().empty()) - error(*variable, "Unnamed variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - addLine("try"); - - _function.body().accept(*this); - add(";"); - addLine("raise Return"); - - string retVals; - for (auto const& variable: _function.returnParameters()) - { - if (!retVals.empty()) - retVals += ", "; - retVals += "!_" + variable->name(); - } - addLine("with Return -> (" + retVals + ") |"); - string reversion = " Revert -> this.balance <- prestate.balance; "; - for (auto const* variable: m_currentContract.stateVariables) - reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; "; - //@TODO in case of reversion the return values are wrong - we need to change the - // return type to include a bool to signify if an exception was thrown. - reversion += "(" + retVals + ")"; - addLine(reversion); - unindent(); - addLine("end"); - addLine(""); - return false; -} - -void Why3Translator::endVisit(FunctionDefinition const&) -{ - m_localVariables.clear(); -} - -bool Why3Translator::visit(Block const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("begin"); - indent(); - for (size_t i = 0; i < _node.statements().size(); ++i) - { - _node.statements()[i]->accept(*this); - if (i != _node.statements().size() - 1) - { - auto it = m_lines.end() - 1; - while (it != m_lines.begin() && it->contents.empty()) - --it; - if (!boost::algorithm::ends_with(it->contents, "begin")) - it->contents += ";"; - } - newLine(); - } - unindent(); - add("end"); - return false; -} - -bool Why3Translator::visit(IfStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - add("if "); - _node.condition().accept(*this); - add(" then"); - visitIndentedUnlessBlock(_node.trueStatement()); - if (_node.falseStatement()) - { - newLine(); - add("else"); - visitIndentedUnlessBlock(*_node.falseStatement()); - } - return false; -} - -bool Why3Translator::visit(WhileStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - // Why3 does not appear to support do-while loops, - // so we will simulate them by performing a while - // loop with the body prepended once. - - if (_node.isDoWhile()) - { - visitIndentedUnlessBlock(_node.body()); - newLine(); - } - - add("while "); - _node.condition().accept(*this); - newLine(); - add("do"); - visitIndentedUnlessBlock(_node.body()); - add("done"); - return false; -} - -bool Why3Translator::visit(Return const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.expression()) - { - solAssert(!!_node.annotation().functionReturnParameters, ""); - auto const& params = _node.annotation().functionReturnParameters->parameters(); - if (params.size() != 1) - { - error(_node, "Directly returning tuples not supported. Rather assign to return variable."); - return false; - } - add("begin _" + params.front()->name() + " := "); - _node.expression()->accept(*this); - add("; raise Return end"); - } - else - add("raise Return"); - return false; -} - -bool Why3Translator::visit(Throw const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("raise Revert"); - return false; -} - -bool Why3Translator::visit(VariableDeclarationStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.declarations().size() != 1) - { - error(_node, "Multiple variables not supported."); - return false; - } - if (_node.initialValue()) - { - add("_" + _node.declarations().front()->name() + " := "); - _node.initialValue()->accept(*this); - } - return false; -} - -bool Why3Translator::visit(ExpressionStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - return true; -} - -bool Why3Translator::visit(Assignment const& _node) -{ - if (_node.assignmentOperator() != Token::Assign) - error(_node, "Compound assignment not supported."); - - _node.leftHandSide().accept(*this); - - add(m_currentLValueIsRef ? " := " : " <- "); - _node.rightHandSide().accept(*this); - - return false; -} - -bool Why3Translator::visit(TupleExpression const& _node) -{ - if (_node.components().size() != 1) - error(_node, "Only tuples with exactly one component supported."); - add("("); - return true; -} - -bool Why3Translator::visit(UnaryOperation const& _unaryOperation) -{ - try - { - toFormalType(*_unaryOperation.annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation."); - } - - switch (_unaryOperation.getOperator()) - { - case Token::Not: // ! - add("(not "); - break; - default: - error(_unaryOperation, "Operator not supported."); - break; - } - - _unaryOperation.subExpression().accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(BinaryOperation const& _binaryOperation) -{ - Expression const& leftExpression = _binaryOperation.leftExpression(); - Expression const& rightExpression = _binaryOperation.rightExpression(); - solAssert(!!_binaryOperation.annotation().commonType, ""); - Type const& commonType = *_binaryOperation.annotation().commonType; - Token::Value const c_op = _binaryOperation.getOperator(); - - if (commonType.category() == Type::Category::RationalNumber) - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType); - if (constantNumber.isFractional()) - error(_binaryOperation, "Fractional numbers not supported."); - else - add("(of_int " + toString(commonType.literalValue(nullptr)) + ")"); - return false; - } - static const map<Token::Value, char const*> optrans({ - {Token::And, " && "}, - {Token::Or, " || "}, - {Token::BitOr, " lor "}, - {Token::BitXor, " lxor "}, - {Token::BitAnd, " land "}, - {Token::Add, " + "}, - {Token::Sub, " - "}, - {Token::Mul, " * "}, - {Token::Div, " / "}, - {Token::Mod, " mod "}, - {Token::Equal, " = "}, - {Token::NotEqual, " <> "}, - {Token::LessThan, " < "}, - {Token::GreaterThan, " > "}, - {Token::LessThanOrEqual, " <= "}, - {Token::GreaterThanOrEqual, " >= "} - }); - if (!optrans.count(c_op)) - { - error(_binaryOperation, "Operator not supported."); - return true; - } - - add("("); - leftExpression.accept(*this); - add(optrans.at(c_op)); - rightExpression.accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(FunctionCall const& _node) -{ - if (_node.annotation().isTypeConversion || _node.annotation().isStructConstructorCall) - { - error(_node, "Only ordinary function calls supported."); - return true; - } - FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); - switch (function.kind()) - { - case FunctionType::Kind::AddMod: - case FunctionType::Kind::MulMod: - { - //@todo require that third parameter is not zero - add("(of_int (mod (Int.("); - add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*"); - add(") (to_int "); - _node.arguments().at(0)->accept(*this); - add(") (to_int "); - _node.arguments().at(1)->accept(*this); - add(")) (to_int "); - _node.arguments().at(2)->accept(*this); - add(")))"); - return false; - } - case FunctionType::Kind::Internal: - { - if (!_node.names().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - //@TODO check type conversions - - add("("); - _node.expression().accept(*this); - add(" state"); - for (auto const& arg: _node.arguments()) - { - add(" "); - arg->accept(*this); - } - add(")"); - return false; - } - case FunctionType::Kind::Bare: - { - if (!_node.arguments().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - add("("); - indent(); - add("let amount = 0 in "); - _node.expression().accept(*this); - addLine("if amount <= this.balance then"); - indent(); - addLine("let balance_precall = this.balance in"); - addLine("begin"); - indent(); - addLine("this.balance <- this.balance - amount;"); - addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true"); - unindent(); - addLine("end"); - unindent(); - addLine("else false"); - - unindent(); - add(")"); - return false; - } - case FunctionType::Kind::SetValue: - { - add("let amount = "); - solAssert(_node.arguments().size() == 1, ""); - _node.arguments()[0]->accept(*this); - add(" in "); - return false; - } - default: - error(_node, "Only internal function calls supported."); - return true; - } -} - -bool Why3Translator::visit(MemberAccess const& _node) -{ - if ( - _node.expression().annotation().type->category() == Type::Category::Array && - _node.memberName() == "length" && - !_node.annotation().lValueRequested - ) - { - add("(of_int "); - _node.expression().accept(*this); - add(".length"); - add(")"); - } - else if ( - _node.memberName() == "call" && - *_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address) - ) - { - // Do nothing, do not even visit the address because this will be an external call - //@TODO ensure that the expression itself does not have side-effects - return false; - } - else - error(_node, "Member access: Only call and array length supported."); - return false; -} - -bool Why3Translator::visit(IndexAccess const& _node) -{ - auto baseType = dynamic_cast<ArrayType const*>(_node.baseExpression().annotation().type.get()); - if (!baseType) - { - error(_node, "Index access only supported for arrays."); - return true; - } - if (_node.annotation().lValueRequested) - { - error(_node, "Assignment to array elements not supported."); - return true; - } - add("("); - _node.baseExpression().accept(*this); - add("[to_int "); - _node.indexExpression()->accept(*this); - add("]"); - add(")"); - - return false; -} - -bool Why3Translator::visit(Identifier const& _identifier) -{ - Declaration const* declaration = _identifier.annotation().referencedDeclaration; - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - add("_" + functionDef->name()); - else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) - { - bool isStateVar = isStateVariable(variable); - bool lvalue = _identifier.annotation().lValueRequested; - if (isStateVar) - add("this.storage."); - else if (!lvalue) - add("!("); - add("_" + variable->name()); - if (!isStateVar && !lvalue) - add(")"); - m_currentLValueIsRef = !isStateVar; - } - else - error(_identifier, "Not supported."); - return false; -} - -bool Why3Translator::visit(Literal const& _literal) -{ - TypePointer type = _literal.annotation().type; - switch (type->category()) - { - case Type::Category::Bool: - if (type->literalValue(&_literal) == 0) - add("false"); - else - add("true"); - break; - case Type::Category::RationalNumber: - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type); - if (constantNumber.isFractional()) - error(_literal, "Fractional numbers not supported."); - else - add("(of_int " + toString(type->literalValue(&_literal)) + ")"); - break; - } - default: - error(_literal, "Not supported."); - } - return false; -} - -bool Why3Translator::visit(PragmaDirective const& _pragma) -{ - if (_pragma.tokens().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals()[0] != "solidity") - error(_pragma, "Not supported"); - else if (_pragma.tokens()[0] != Token::Identifier) - error(_pragma, "A literal 'solidity' is not an identifier. Strange"); - - return false; -} - -bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const -{ - return contains(m_currentContract.stateVariables, _var); -} - -bool Why3Translator::isStateVariable(string const& _name) const -{ - for (auto const& var: m_currentContract.stateVariables) - if (var->name() == _name) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const -{ - for (auto const& var: m_localVariables) - if (var.second == _var) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(string const& _name) const -{ - return m_localVariables.count(_name); -} - -string Why3Translator::copyOfStorage() const -{ - if (m_currentContract.stateVariables.empty()) - return "()"; - string ret = "{"; - bool first = true; - for (auto const* variable: m_currentContract.stateVariables) - { - if (first) - first = false; - else - ret += "; "; - ret += "_" + variable->name() + " = this.storage._" + variable->name(); - } - return ret + "}"; -} - -void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement) -{ - bool isBlock = !!dynamic_cast<Block const*>(&_statement); - if (isBlock) - newLine(); - else - indent(); - _statement.accept(*this); - if (isBlock) - newLine(); - else - unindent(); -} - -void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation) -{ - auto why3Range = _annotation.docTags.equal_range("why3"); - for (auto i = why3Range.first; i != why3Range.second; ++i) - addLine(transformVariableReferences(i->second.content)); -} - -string Why3Translator::transformVariableReferences(string const& _annotation) -{ - string ret; - auto pos = _annotation.begin(); - while (true) - { - auto hash = find(pos, _annotation.end(), '#'); - ret.append(pos, hash); - if (hash == _annotation.end()) - break; - - auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c) - { - return - (_c != '_' && _c != '$') && - !('a' <= _c && _c <= 'z') && - !('A' <= _c && _c <= 'Z') && - !('0' <= _c && _c <= '9'); - }); - string varName(hash + 1, hashEnd); - if (isLocalVariable(varName)) - ret += "(!_" + varName + ")"; - else if (isStateVariable(varName)) - ret += "(this.storage._" + varName + ")"; - //@todo return variables - else - ret.append(hash, hashEnd); - - pos = hashEnd; - } - return ret; -} - -void Why3Translator::appendPreface() -{ - m_lines.push_back(Line{R"( -module UInt256 - use import mach.int.Unsigned - type uint256 - constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - clone export mach.int.Unsigned with - type t = uint256, - constant max = max_uint256 -end - -module Address - use import mach.int.Unsigned - type address - constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *) - clone export mach.int.Unsigned with - type t = address, - constant max = max_address -end - )", 0}); -} diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h deleted file mode 100644 index 03f3bf9c..00000000 --- a/libsolidity/formal/Why3Translator.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - 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 Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#pragma once - -#include <libsolidity/ast/ASTVisitor.h> -#include <libsolidity/interface/Exceptions.h> -#include <string> - -namespace dev -{ -namespace solidity -{ - -class SourceUnit; - -/** - * Simple translator from Solidity to Why3. - * - * @todo detect side effects in sub-expressions and limit them to one per statement. #1043 - * @todo `x = y = z` - * @todo implicit and explicit type conversion - */ -class Why3Translator: private ASTConstVisitor -{ -public: - Why3Translator(ErrorList& _errors): m_lines(std::vector<Line>{{std::string(), 0}}), m_errors(_errors) {} - - /// Appends formalisation of the given source unit to the output. - /// @returns false on error. - bool process(SourceUnit const& _source); - - std::string translation() const; - -private: - /// Creates an error and adds it to errors list. - void error(ASTNode const& _node, std::string const& _description); - /// Reports a fatal error and throws. - void fatalError(ASTNode const& _node, std::string const& _description); - - /// Appends imports and constants use throughout the formal code. - void appendPreface(); - - /// @returns a string representation of the corresponding formal type or throws NoFormalType exception. - std::string toFormalType(Type const& _type) const; - using errinfo_noFormalTypeFrom = boost::error_info<struct tag_noFormalTypeFrom, std::string /* name of the type that cannot be translated */ >; - struct NoFormalType: virtual Exception {}; - - void indent() { newLine(); m_lines.back().indentation++; } - void unindent(); - void addLine(std::string const& _line); - void add(std::string const& _str); - void newLine(); - void appendSemicolon(); - - virtual bool visit(SourceUnit const&) override { return true; } - virtual bool visit(ContractDefinition const& _contract) override; - virtual void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual void endVisit(FunctionDefinition const& _function) override; - virtual bool visit(Block const&) override; - virtual bool visit(IfStatement const& _node) override; - virtual bool visit(WhileStatement const& _node) override; - virtual bool visit(Return const& _node) override; - virtual bool visit(Throw const& _node) override; - virtual bool visit(VariableDeclarationStatement const& _node) override; - virtual bool visit(ExpressionStatement const&) override; - virtual bool visit(Assignment const& _node) override; - virtual bool visit(TupleExpression const& _node) override; - virtual void endVisit(TupleExpression const&) override { add(")"); } - virtual bool visit(UnaryOperation const& _node) override; - virtual bool visit(BinaryOperation const& _node) override; - virtual bool visit(FunctionCall const& _node) override; - virtual bool visit(MemberAccess const& _node) override; - virtual bool visit(IndexAccess const& _node) override; - virtual bool visit(Identifier const& _node) override; - virtual bool visit(Literal const& _node) override; - virtual bool visit(PragmaDirective const& _node) override; - - virtual bool visitNode(ASTNode const& _node) override - { - error(_node, "Code not supported for formal verification."); - return false; - } - - bool isStateVariable(VariableDeclaration const* _var) const; - bool isStateVariable(std::string const& _name) const; - bool isLocalVariable(VariableDeclaration const* _var) const; - bool isLocalVariable(std::string const& _name) const; - - /// @returns a string representing an expression that is a copy of this.storage - std::string copyOfStorage() const; - - /// Visits the given statement and indents it unless it is a block - /// (which does its own indentation). - void visitIndentedUnlessBlock(Statement const& _statement); - - void addSourceFromDocStrings(DocumentedAnnotation const& _annotation); - /// Transforms substring like `#varName` and `#stateVarName` to code that evaluates to their value. - std::string transformVariableReferences(std::string const& _annotation); - - /// True if we have already seen a contract. For now, only a single contract - /// is supported. - bool m_seenContract = false; - bool m_errorOccured = false; - - /// Metadata relating to the current contract - struct ContractMetadata - { - ContractDefinition const* contract = nullptr; - std::vector<VariableDeclaration const*> stateVariables; - - void reset() { contract = nullptr; stateVariables.clear(); } - }; - - ContractMetadata m_currentContract; - bool m_currentLValueIsRef = false; - std::map<std::string, VariableDeclaration const*> m_localVariables; - - struct Line - { - std::string contents; - unsigned indentation; - }; - std::vector<Line> m_lines; - ErrorList& m_errors; -}; - -} -} diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index dad05a78..7e00ffae 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -25,10 +25,10 @@ #include <libsolidity/inlineasm/AsmScope.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> -#include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/ErrorReporter.h> #include <boost/range/adaptor/reversed.hpp> +#include <boost/algorithm/string.hpp> #include <memory> #include <functional> @@ -38,18 +38,15 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; -AsmAnalyzer::AsmAnalyzer( - AsmAnalysisInfo& _analysisInfo, - ErrorList& _errors, - ExternalIdentifierAccess::Resolver const& _resolver -): - m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors) -{ +namespace { + +set<string> const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"}; + } bool AsmAnalyzer::analyze(Block const& _block) { - if (!(ScopeFiller(m_info.scopes, m_errors))(_block)) + if (!(ScopeFiller(m_info, m_errorReporter))(_block)) return false; return (*this)(_block); @@ -57,28 +54,31 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { + solAssert(!m_julia, ""); m_info.stackHeightInfo[&_label] = m_stackHeight; return true; } bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { + solAssert(!m_julia, ""); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; + warnOnInstructions(_instruction.instruction, _instruction.location); return true; } bool AsmAnalyzer::operator()(assembly::Literal const& _literal) { + expectValidType(_literal.type, _literal.location); ++m_stackHeight; - if (!_literal.isNumber && _literal.value.size() > 32) + if (_literal.kind == assembly::LiteralKind::String && _literal.value.size() > 32) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)", - _literal.location - )); + m_errorReporter.typeError( + _literal.location, + "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)" + ); return false; } m_info.stackHeightInfo[&_literal] = m_stackHeight; @@ -87,18 +87,17 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal) bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) { - size_t numErrorsBefore = m_errors.size(); + size_t numErrorsBefore = m_errorReporter.errors().size(); bool success = true; if (m_currentScope->lookup(_identifier.name, Scope::Visitor( [&](Scope::Variable const& _var) { - if (!_var.active) + if (!m_activeVariables.count(&_var)) { - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable " + _identifier.name + " used before it was declared.", - _identifier.location - )); + m_errorReporter.declarationError( + _identifier.location, + "Variable " + _identifier.name + " used before it was declared." + ); success = false; } ++m_stackHeight; @@ -109,11 +108,10 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) }, [&](Scope::Function const&) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Function " + _identifier.name + " used without being called.", - _identifier.location - )); + m_errorReporter.typeError( + _identifier.location, + "Function " + _identifier.name + " used without being called." + ); success = false; } ))) @@ -123,16 +121,15 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) { size_t stackSize(-1); if (m_resolver) - stackSize = m_resolver(_identifier, IdentifierContext::RValue); + { + bool insideFunction = m_currentScope->insideFunction(); + stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue, insideFunction); + } if (stackSize == size_t(-1)) { // Only add an error message if the callback did not do it. - if (numErrorsBefore == m_errors.size()) - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Identifier not found.", - _identifier.location - )); + if (numErrorsBefore == m_errorReporter.errors().size()) + m_errorReporter.declarationError(_identifier.location, "Identifier not found."); success = false; } m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize; @@ -143,15 +140,11 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { + solAssert(!m_julia, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) - { - int const stackHeight = m_stackHeight; - if (!boost::apply_visitor(*this, arg)) + if (!expectExpression(arg)) success = false; - if (!expectDeposit(1, stackHeight, locationOf(arg))) - success = false; - } // Parser already checks that the number of arguments is correct. solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); if (!(*this)(_instr.instruction)) @@ -160,15 +153,15 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) return success; } -bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) +bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { + solAssert(!m_julia, ""); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; } - -bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) +bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) { int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_assignment.value); @@ -181,23 +174,37 @@ bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) { + int const expectedItems = _varDecl.variables.size(); int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_varDecl.value); - solAssert(m_stackHeight - stackHeight == 1, "Invalid value size."); - boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true; + if ((m_stackHeight - stackHeight) != expectedItems) + { + m_errorReporter.declarationError(_varDecl.location, "Variable count mismatch."); + return false; + } + + for (auto const& variable: _varDecl.variables) + { + expectValidType(variable.type, variable.location); + m_activeVariables.insert(&boost::get<Scope::Variable>(m_currentScope->identifiers.at(variable.name))); + } m_info.stackHeightInfo[&_varDecl] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) { - Scope& bodyScope = scope(&_funDef.body); + Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); + solAssert(virtualBlock, ""); + Scope& varScope = scope(virtualBlock); for (auto const& var: _funDef.arguments + _funDef.returns) - boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true; + { + expectValidType(var.type, var.location); + m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name))); + } int const stackHeight = m_stackHeight; m_stackHeight = _funDef.arguments.size() + _funDef.returns.size(); - m_virtualVariablesInNextBlock = m_stackHeight; bool success = (*this)(_funDef.body); @@ -214,141 +221,213 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( [&](Scope::Variable const&) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Attempt to call variable instead of function.", - _funCall.functionName.location - )); + m_errorReporter.typeError( + _funCall.functionName.location, + "Attempt to call variable instead of function." + ); success = false; }, [&](Scope::Label const&) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Attempt to call label instead of function.", - _funCall.functionName.location - )); + m_errorReporter.typeError( + _funCall.functionName.location, + "Attempt to call label instead of function." + ); success = false; }, [&](Scope::Function const& _fun) { - arguments = _fun.arguments; - returns = _fun.returns; + /// TODO: compare types too + arguments = _fun.arguments.size(); + returns = _fun.returns.size(); } ))) { - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Function not found.", - _funCall.functionName.location - )); + m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); success = false; } if (success) { if (_funCall.arguments.size() != arguments) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Expected " + - boost::lexical_cast<string>(arguments) + - " arguments but got " + - boost::lexical_cast<string>(_funCall.arguments.size()) + - ".", - _funCall.functionName.location - )); + m_errorReporter.typeError( + _funCall.functionName.location, + "Expected " + boost::lexical_cast<string>(arguments) + " arguments but got " + + boost::lexical_cast<string>(_funCall.arguments.size()) + "." + ); success = false; } } for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) - { - int const stackHeight = m_stackHeight; - if (!boost::apply_visitor(*this, arg)) - success = false; - if (!expectDeposit(1, stackHeight, locationOf(arg))) + if (!expectExpression(arg)) success = false; - } m_stackHeight += int(returns) - int(arguments); m_info.stackHeightInfo[&_funCall] = m_stackHeight; return success; } +bool AsmAnalyzer::operator()(Switch const& _switch) +{ + bool success = true; + + if (!expectExpression(*_switch.expression)) + success = false; + + set<tuple<LiteralKind, string>> cases; + for (auto const& _case: _switch.cases) + { + if (_case.value) + { + int const initialStackHeight = m_stackHeight; + // We cannot use "expectExpression" here because *_case.value is not a + // Statement and would be converted to a Statement otherwise. + if (!(*this)(*_case.value)) + success = false; + expectDeposit(1, initialStackHeight, _case.value->location); + m_stackHeight--; + + /// Note: the parser ensures there is only one default case + auto val = make_tuple(_case.value->kind, _case.value->value); + if (!cases.insert(val).second) + { + m_errorReporter.declarationError( + _case.location, + "Duplicate case defined" + ); + success = false; + } + } + + if (!(*this)(_case.body)) + success = false; + } + + m_stackHeight--; + m_info.stackHeightInfo[&_switch] = m_stackHeight; + + return success; +} + +bool AsmAnalyzer::operator()(assembly::ForLoop const& _for) +{ + Scope* originalScope = m_currentScope; + + bool success = true; + if (!(*this)(_for.pre)) + success = false; + // The block was closed already, but we re-open it again and stuff the + // condition, the body and the post part inside. + m_stackHeight += scope(&_for.pre).numberOfVariables(); + m_currentScope = &scope(&_for.pre); + + if (!expectExpression(*_for.condition)) + success = false; + m_stackHeight--; + if (!(*this)(_for.body)) + success = false; + if (!(*this)(_for.post)) + success = false; + + m_stackHeight -= scope(&_for.pre).numberOfVariables(); + m_info.stackHeightInfo[&_for] = m_stackHeight; + m_currentScope = originalScope; + + return success; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; + auto previousScope = m_currentScope; m_currentScope = &scope(&_block); - int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock; - m_virtualVariablesInNextBlock = 0; + int const initialStackHeight = m_stackHeight; for (auto const& s: _block.statements) if (!boost::apply_visitor(*this, s)) success = false; - for (auto const& identifier: scope(&_block).identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) - --m_stackHeight; + m_stackHeight -= scope(&_block).numberOfVariables(); int const stackDiff = m_stackHeight - initialStackHeight; if (stackDiff != 0) { - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, + m_errorReporter.declarationError( + _block.location, "Unbalanced stack at the end of a block: " + ( stackDiff > 0 ? to_string(stackDiff) + string(" surplus item(s).") : to_string(-stackDiff) + string(" missing item(s).") - ), - _block.location - )); + ) + ); success = false; } - m_currentScope = m_currentScope->superScope; m_info.stackHeightInfo[&_block] = m_stackHeight; + m_currentScope = previousScope; + return success; +} + +bool AsmAnalyzer::expectExpression(Statement const& _statement) +{ + bool success = true; + int const initialHeight = m_stackHeight; + if (!boost::apply_visitor(*this, _statement)) + success = false; + if (!expectDeposit(1, initialHeight, locationOf(_statement))) + success = false; return success; } +bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) +{ + if (m_stackHeight - _oldHeight != _deposit) + { + m_errorReporter.typeError( + _location, + "Expected expression to return one item to the stack, but did return " + + boost::lexical_cast<string>(m_stackHeight - _oldHeight) + + " items." + ); + return false; + } + return true; +} + bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize) { bool success = true; - size_t numErrorsBefore = m_errors.size(); + size_t numErrorsBefore = m_errorReporter.errors().size(); size_t variableSize(-1); if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) { // Check that it is a variable if (var->type() != typeid(Scope::Variable)) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Assignment requires variable.", - _variable.location - )); + m_errorReporter.typeError(_variable.location, "Assignment requires variable."); success = false; } - else if (!boost::get<Scope::Variable>(*var).active) + else if (!m_activeVariables.count(&boost::get<Scope::Variable>(*var))) { - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable " + _variable.name + " used before it was declared.", - _variable.location - )); + m_errorReporter.declarationError( + _variable.location, + "Variable " + _variable.name + " used before it was declared." + ); success = false; } variableSize = 1; } else if (m_resolver) - variableSize = m_resolver(_variable, IdentifierContext::LValue); + { + bool insideFunction = m_currentScope->insideFunction(); + variableSize = m_resolver(_variable, julia::IdentifierContext::LValue, insideFunction); + } if (variableSize == size_t(-1)) { // Only add message if the callback did not. - if (numErrorsBefore == m_errors.size()) - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable not found or variable not lvalue.", - _variable.location - )); + if (numErrorsBefore == m_errorReporter.errors().size()) + m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue."); success = false; } if (_valueSize == size_t(-1)) @@ -358,43 +437,60 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t if (_valueSize != variableSize && variableSize != size_t(-1)) { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, + m_errorReporter.typeError( + _variable.location, "Variable size (" + to_string(variableSize) + ") and value size (" + to_string(_valueSize) + - ") do not match.", - _variable.location - )); + ") do not match." + ); success = false; } return success; } -bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location) -{ - int stackDiff = m_stackHeight - _oldHeight; - if (stackDiff != _deposit) - { - m_errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Expected instruction(s) to deposit " + - boost::lexical_cast<string>(_deposit) + - " item(s) to the stack, but did deposit " + - boost::lexical_cast<string>(stackDiff) + - " item(s).", - _location - )); - return false; - } - else - return true; -} - Scope& AsmAnalyzer::scope(Block const* _block) { + solAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present."); auto scopePtr = m_info.scopes.at(_block); solAssert(scopePtr, "Scope requested but not present."); return *scopePtr; } +void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) +{ + if (!m_julia) + return; + + if (!builtinTypes.count(type)) + m_errorReporter.typeError( + _location, + "\"" + type + "\" is not a valid type (user defined types are not yet supported)." + ); +} + +void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location) +{ + static set<solidity::Instruction> futureInstructions{ + solidity::Instruction::CREATE2, + solidity::Instruction::RETURNDATACOPY, + solidity::Instruction::RETURNDATASIZE, + solidity::Instruction::STATICCALL + }; + if (futureInstructions.count(_instr)) + m_errorReporter.warning( + _location, + "The \"" + + boost::to_lower_copy(instructionInfo(_instr).name) + + "\" instruction is only available after " + + "the Metropolis hard fork. Before that it acts as an invalid instruction." + ); + + if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI) + m_errorReporter.warning( + _location, + "Jump instructions are low-level EVM features that can lead to " + "incorrect stack access. Because of that they are discouraged. " + "Please consider using \"switch\" or \"for\" statements instead." + ); +} diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 426ee0d2..9b2a8f9c 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -20,10 +20,14 @@ #pragma once -#include <libsolidity/inlineasm/AsmStack.h> - #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/inlineasm/AsmScope.h> + +#include <libjulia/backends/evm/AbstractAssembly.h> + +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> #include <functional> @@ -33,23 +37,10 @@ namespace dev { namespace solidity { +class ErrorReporter; namespace assembly { -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct FunctionalAssignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct Assignment; -struct FunctionDefinition; -struct FunctionCall; - -struct Scope; - struct AsmAnalysisInfo; /** @@ -60,11 +51,12 @@ struct AsmAnalysisInfo; class AsmAnalyzer: public boost::static_visitor<bool> { public: - AsmAnalyzer( + explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, - ErrorList& _errors, - ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver() - ); + ErrorReporter& _errorReporter, + bool _julia = false, + julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() + ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {} bool analyze(assembly::Block const& _block); @@ -73,29 +65,37 @@ public: bool operator()(assembly::Identifier const&); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::Label const& _label); - bool operator()(assembly::Assignment const&); - bool operator()(assembly::FunctionalAssignment const& _functionalAssignment); + bool operator()(assembly::StackAssignment const&); + bool operator()(assembly::Assignment const& _assignment); bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const& _functionCall); + bool operator()(assembly::Switch const& _switch); + bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); private: + /// Visits the statement and expects it to deposit one item onto the stack. + bool expectExpression(Statement const& _statement); + bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); + /// Verifies that a variable to be assigned to exists and has the same size /// as the value, @a _valueSize, unless that is equal to -1. bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1)); - bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); + Scope& scope(assembly::Block const* _block); + void expectValidType(std::string const& type, SourceLocation const& _location); + void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location); - /// This is used when we enter the body of a function definition. There, the parameters - /// and return parameters appear as variables which are already on the stack before - /// we enter the block. - int m_virtualVariablesInNextBlock = 0; int m_stackHeight = 0; - ExternalIdentifierAccess::Resolver const& m_resolver; + julia::ExternalIdentifierAccess::Resolver m_resolver; Scope* m_currentScope = nullptr; + /// Variables that are active at the current point in assembly (as opposed to + /// "part of the scope but not yet declared") + std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; - ErrorList& m_errors; + ErrorReporter& m_errorReporter; + bool m_julia = false; }; } diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h index e21eb2c5..bd3b28c4 100644 --- a/libsolidity/inlineasm/AsmAnalysisInfo.h +++ b/libsolidity/inlineasm/AsmAnalysisInfo.h @@ -20,10 +20,13 @@ #pragma once +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> #include <map> #include <memory> +#include <vector> namespace dev { @@ -32,28 +35,16 @@ namespace solidity namespace assembly { -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct FunctionalAssignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct Assignment; -struct FunctionDefinition; -struct FunctionCall; - struct Scope; -using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>; - struct AsmAnalysisInfo { using StackHeightInfo = std::map<void const*, int>; using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; Scopes scopes; StackHeightInfo stackHeightInfo; + /// Virtual blocks which will be used for scopes for function arguments and return values. + std::map<FunctionDefinition const*, std::shared_ptr<assembly::Block const>> virtualBlocks; }; } diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 9ef3e6e7..74743737 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -32,6 +32,9 @@ #include <libevmasm/SourceLocation.h> #include <libevmasm/Instruction.h> +#include <libjulia/backends/evm/AbstractAssembly.h> +#include <libjulia/backends/evm/EVMCodeTransform.h> + #include <libdevcore/CommonIO.h> #include <boost/range/adaptor/reversed.hpp> @@ -46,268 +49,101 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; -struct GeneratorState -{ - GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly): - errors(_errors), info(_analysisInfo), assembly(_assembly) {} - - size_t newLabelId() - { - return assemblyTagToIdentifier(assembly.newTag()); - } - - size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const - { - u256 id = _tag.data(); - solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large."); - return size_t(id); - } - - ErrorList& errors; - AsmAnalysisInfo info; - eth::Assembly& assembly; -}; - -class CodeTransform: public boost::static_visitor<> +class EthAssemblyAdapter: public julia::AbstractAssembly { public: - /// Create the code transformer which appends assembly to _state.assembly when called - /// with parsed assembly data. - /// @param _identifierAccess used to resolve identifiers external to the inline assembly - explicit CodeTransform( - GeneratorState& _state, - assembly::Block const& _block, - assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess() - ): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit()) + EthAssemblyAdapter(eth::Assembly& _assembly): + m_assembly(_assembly) { } - -private: - CodeTransform( - GeneratorState& _state, - assembly::Block const& _block, - assembly::ExternalIdentifierAccess const& _identifierAccess, - int _initialDeposit - ): - m_state(_state), - m_scope(*m_state.info.scopes.at(&_block)), - m_identifierAccess(_identifierAccess), - m_initialDeposit(_initialDeposit) + virtual void setSourceLocation(SourceLocation const& _location) override { - int blockStartDeposit = m_state.assembly.deposit(); - std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); - - m_state.assembly.setSourceLocation(_block.location); - - // pop variables - for (auto const& identifier: m_scope.identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) - m_state.assembly.append(solidity::Instruction::POP); - - int deposit = m_state.assembly.deposit() - blockStartDeposit; - solAssert(deposit == 0, "Invalid stack height at end of block."); + m_assembly.setSourceLocation(_location); } - -public: - void operator()(assembly::Instruction const& _instruction) - { - m_state.assembly.setSourceLocation(_instruction.location); - m_state.assembly.append(_instruction.instruction); - checkStackHeight(&_instruction); - } - void operator()(assembly::Literal const& _literal) + virtual int stackHeight() const override { return m_assembly.deposit(); } + virtual void appendInstruction(solidity::Instruction _instruction) override { - m_state.assembly.setSourceLocation(_literal.location); - if (_literal.isNumber) - m_state.assembly.append(u256(_literal.value)); - else - { - solAssert(_literal.value.size() <= 32, ""); - m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft))); - } - checkStackHeight(&_literal); + m_assembly.append(_instruction); } - void operator()(assembly::Identifier const& _identifier) + virtual void appendConstant(u256 const& _constant) override { - m_state.assembly.setSourceLocation(_identifier.location); - // First search internals, then externals. - if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor( - [=](Scope::Variable& _var) - { - if (int heightDiff = variableHeightDiff(_var, _identifier.location, false)) - m_state.assembly.append(solidity::dupInstruction(heightDiff)); - else - // Store something to balance the stack - m_state.assembly.append(u256(0)); - }, - [=](Scope::Label& _label) - { - assignLabelIdIfUnset(_label); - m_state.assembly.append(eth::AssemblyItem(eth::PushTag, _label.id)); - }, - [=](Scope::Function&) - { - solAssert(false, "Function not removed during desugaring."); - } - ))) - { - return; - } - solAssert( - m_identifierAccess.generateCode, - "Identifier not found and no external access available." - ); - m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly); - checkStackHeight(&_identifier); + m_assembly.append(_constant); } - void operator()(FunctionalInstruction const& _instr) + /// Append a label. + virtual void appendLabel(LabelID _labelId) override { - for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it) - { - int height = m_state.assembly.deposit(); - boost::apply_visitor(*this, *it); - expectDeposit(1, height); - } - (*this)(_instr.instruction); - checkStackHeight(&_instr); + m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId)); } - void operator()(assembly::FunctionCall const&) + /// Append a label reference. + virtual void appendLabelReference(LabelID _labelId) override { - solAssert(false, "Function call not removed during desugaring phase."); + m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId)); } - void operator()(Label const& _label) + virtual size_t newLabelId() override { - m_state.assembly.setSourceLocation(_label.location); - solAssert(m_scope.identifiers.count(_label.name), ""); - Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name)); - assignLabelIdIfUnset(label); - m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id)); - checkStackHeight(&_label); + return assemblyTagToIdentifier(m_assembly.newTag()); } - void operator()(assembly::Assignment const& _assignment) + virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override { - m_state.assembly.setSourceLocation(_assignment.location); - generateAssignment(_assignment.variableName, _assignment.location); - checkStackHeight(&_assignment); + m_assembly.appendLibraryAddress(_linkerSymbol); } - void operator()(FunctionalAssignment const& _assignment) + virtual void appendJump(int _stackDiffAfter) override { - int height = m_state.assembly.deposit(); - boost::apply_visitor(*this, *_assignment.value); - expectDeposit(1, height); - m_state.assembly.setSourceLocation(_assignment.location); - generateAssignment(_assignment.variableName, _assignment.location); - checkStackHeight(&_assignment); + appendInstruction(solidity::Instruction::JUMP); + m_assembly.adjustDeposit(_stackDiffAfter); } - void operator()(assembly::VariableDeclaration const& _varDecl) + virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override { - int height = m_state.assembly.deposit(); - boost::apply_visitor(*this, *_varDecl.value); - expectDeposit(1, height); - auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name)); - var.stackHeight = height; - var.active = true; + appendLabelReference(_labelId); + appendJump(_stackDiffAfter); } - void operator()(assembly::Block const& _block) + virtual void appendJumpToIf(LabelID _labelId) override { - CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit); - checkStackHeight(&_block); + appendLabelReference(_labelId); + appendInstruction(solidity::Instruction::JUMPI); } - void operator()(assembly::FunctionDefinition const&) + virtual void appendBeginsub(LabelID, int) override { - solAssert(false, "Function definition not removed during desugaring phase."); + // TODO we could emulate that, though + solAssert(false, "BEGINSUB not implemented for EVM 1.0"); } - -private: - void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location) + /// Call a subroutine. + virtual void appendJumpsub(LabelID, int, int) override { - auto var = m_scope.lookup(_variableName.name); - if (var) - { - Scope::Variable const& _var = boost::get<Scope::Variable>(*var); - if (int heightDiff = variableHeightDiff(_var, _location, true)) - m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); - m_state.assembly.append(solidity::Instruction::POP); - } - else - { - solAssert( - m_identifierAccess.generateCode, - "Identifier not found and no external access available." - ); - m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly); - } + // TODO we could emulate that, though + solAssert(false, "JUMPSUB not implemented for EVM 1.0"); } - /// Determines the stack height difference to the given variables. Automatically generates - /// errors if it is not yet in scope or the height difference is too large. Returns 0 on - /// errors and the (positive) stack height difference otherwise. - int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap) + /// Return from a subroutine. + virtual void appendReturnsub(int, int) override { - int heightDiff = m_state.assembly.deposit() - _var.stackHeight; - if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16)) - { - //@TODO move this to analysis phase. - m_state.errors.push_back(make_shared<Error>( - Error::Type::TypeError, - "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")", - _location - )); - return 0; - } - else - return heightDiff; + // TODO we could emulate that, though + solAssert(false, "RETURNSUB not implemented for EVM 1.0"); } - void expectDeposit(int _deposit, int _oldHeight) + virtual void appendAssemblySize() override { - solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit."); + m_assembly.appendProgramSize(); } - void checkStackHeight(void const* _astElement) - { - solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); - solAssert( - m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit, - "Stack height mismatch between analysis and code generation phase." - ); - } - - /// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set. - void assignLabelIdIfUnset(Scope::Label& _label) +private: + LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const { - if (_label.id == Scope::Label::unassignedLabelId) - _label.id = m_state.newLabelId(); - else if (_label.id == Scope::Label::errorLabelId) - _label.id = size_t(m_state.assembly.errorTag().data()); + u256 id = _tag.data(); + solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large."); + return LabelID(id); } - - GeneratorState& m_state; - Scope& m_scope; - ExternalIdentifierAccess m_identifierAccess; - int const m_initialDeposit; + eth::Assembly& m_assembly; }; -eth::Assembly assembly::CodeGenerator::assemble( - Block const& _parsedData, - AsmAnalysisInfo& _analysisInfo, - ExternalIdentifierAccess const& _identifierAccess -) -{ - eth::Assembly assembly; - GeneratorState state(m_errors, _analysisInfo, assembly); - CodeTransform(state, _parsedData, _identifierAccess); - return assembly; -} - void assembly::CodeGenerator::assemble( Block const& _parsedData, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly, - ExternalIdentifierAccess const& _identifierAccess + julia::ExternalIdentifierAccess const& _identifierAccess ) { - GeneratorState state(m_errors, _analysisInfo, _assembly); - CodeTransform(state, _parsedData, _identifierAccess); + EthAssemblyAdapter assemblyAdapter(_assembly); + julia::CodeTransform(assemblyAdapter, _analysisInfo, false, false, _identifierAccess)(_parsedData); } diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h index e830e047..2a36a590 100644 --- a/libsolidity/inlineasm/AsmCodeGen.h +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -23,7 +23,6 @@ #pragma once #include <libsolidity/inlineasm/AsmAnalysis.h> -#include <libsolidity/interface/Exceptions.h> #include <functional> @@ -42,24 +41,13 @@ struct Block; class CodeGenerator { public: - CodeGenerator(ErrorList& _errors): - m_errors(_errors) {} - /// Performs code generation and @returns the result. - eth::Assembly assemble( - Block const& _parsedData, - AsmAnalysisInfo& _analysisInfo, - ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() - ); /// Performs code generation and appends generated to to _assembly. - void assemble( + static void assemble( Block const& _parsedData, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly, - ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() + julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess() ); - -private: - ErrorList& m_errors; }; } diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index d61b5803..db5840bc 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -22,10 +22,13 @@ #pragma once -#include <boost/variant.hpp> +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <libevmasm/Instruction.h> #include <libevmasm/SourceLocation.h> +#include <boost/variant.hpp> + namespace dev { namespace solidity @@ -33,37 +36,39 @@ namespace solidity namespace assembly { -/// What follows are the AST nodes for assembly. +using Type = std::string; + +struct TypedName { SourceLocation location; std::string name; Type type; }; +using TypedNameList = std::vector<TypedName>; /// Direct EVM instruction (except PUSHi and JUMPDEST) struct Instruction { SourceLocation location; solidity::Instruction instruction; }; /// Literal number or string (up to 32 bytes) -struct Literal { SourceLocation location; bool isNumber; std::string value; }; +enum class LiteralKind { Number, Boolean, String }; +struct Literal { SourceLocation location; LiteralKind kind; std::string value; Type type; }; /// External / internal identifier or label reference struct Identifier { SourceLocation location; std::string name; }; -struct FunctionalInstruction; /// Jump label ("name:") struct Label { SourceLocation location; std::string name; }; -/// Assignemnt (":= x", moves stack top into x, potentially multiple slots) -struct Assignment { SourceLocation location; Identifier variableName; }; -struct FunctionalAssignment; -struct VariableDeclaration; -struct FunctionDefinition; -struct FunctionCall; -struct Block; -using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>; -/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand +/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots) +struct StackAssignment { SourceLocation location; Identifier variableName; }; +/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. -struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; }; -/// Functional instruction, e.g. "mul(mload(20), add(2, x))" +struct Assignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; }; +/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; }; struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; }; -/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted -struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr<Statement> value; }; +/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted +struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector<Statement> statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") -struct FunctionDefinition { SourceLocation location; std::string name; std::vector<std::string> arguments; std::vector<std::string> returns; Block body; }; +struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; }; +/// Switch case or default case +struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; }; +/// Switch statement +struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; }; +struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; }; struct LocationExtractor: boost::static_visitor<SourceLocation> { diff --git a/libsolidity/interface/Utils.h b/libsolidity/inlineasm/AsmDataForward.h index 0027759c..4ead7ff5 100644 --- a/libsolidity/interface/Utils.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -16,30 +16,37 @@ */ /** * @author Christian <c@ethdev.com> - * @date 2014 - * Solidity Utilities. + * @date 2016 + * Forward declaration of classes for inline assembly / JULIA AST */ #pragma once -#include <libdevcore/Assertions.h> -#include <libsolidity/interface/Exceptions.h> +#include <boost/variant.hpp> namespace dev { namespace solidity { -struct InternalCompilerError; -struct UnimplementedFeatureError; -} -} - -/// Assertion that throws an InternalCompilerError containing the given description if it is not met. -#define solAssert(CONDITION, DESCRIPTION) \ - assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) +namespace assembly +{ -#define solUnimplementedAssert(CONDITION, DESCRIPTION) \ - assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION) +struct Instruction; +struct Literal; +struct Label; +struct StackAssignment; +struct Identifier; +struct Assignment; +struct VariableDeclaration; +struct FunctionalInstruction; +struct FunctionDefinition; +struct FunctionCall; +struct Switch; +struct ForLoop; +struct Block; + +using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>; -#define solUnimplemented(DESCRIPTION) \ - solUnimplementedAssert(false, DESCRIPTION) +} +} +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index d7f78958..d282a30d 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -21,10 +21,10 @@ */ #include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/parsing/Scanner.h> +#include <libsolidity/interface/ErrorReporter.h> #include <ctype.h> #include <algorithm> -#include <libsolidity/parsing/Scanner.h> -#include <libsolidity/interface/Exceptions.h> using namespace std; using namespace dev; @@ -40,7 +40,7 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann } catch (FatalError const&) { - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. } return nullptr; @@ -50,16 +50,16 @@ assembly::Block Parser::parseBlock() { assembly::Block block = createWithLocation<Block>(); expectToken(Token::LBrace); - while (m_scanner->currentToken() != Token::RBrace) + while (currentToken() != Token::RBrace) block.statements.emplace_back(parseStatement()); block.location.end = endPosition(); - m_scanner->next(); + advance(); return block; } assembly::Statement Parser::parseStatement() { - switch (m_scanner->currentToken()) + switch (currentToken()) { case Token::Let: return parseVariableDeclaration(); @@ -67,23 +67,43 @@ assembly::Statement Parser::parseStatement() return parseFunctionDefinition(); case Token::LBrace: return parseBlock(); + case Token::Switch: + { + assembly::Switch _switch = createWithLocation<assembly::Switch>(); + m_scanner->next(); + _switch.expression = make_shared<Statement>(parseExpression()); + if (_switch.expression->type() == typeid(assembly::Instruction)) + fatalParserError("Instructions are not supported as expressions for switch."); + while (m_scanner->currentToken() == Token::Case) + _switch.cases.emplace_back(parseCase()); + if (m_scanner->currentToken() == Token::Default) + _switch.cases.emplace_back(parseCase()); + if (m_scanner->currentToken() == Token::Default) + fatalParserError("Only one default case allowed."); + else if (m_scanner->currentToken() == Token::Case) + fatalParserError("Case not allowed after default case."); + if (_switch.cases.size() == 0) + fatalParserError("Switch statement without any cases."); + _switch.location.end = _switch.cases.back().body.location.end; + return _switch; + } + case Token::For: + return parseForLoop(); case Token::Assign: { if (m_julia) break; - assembly::Assignment assignment = createWithLocation<assembly::Assignment>(); - m_scanner->next(); + assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>(); + advance(); expectToken(Token::Colon); assignment.variableName.location = location(); - assignment.variableName.name = m_scanner->currentLiteral(); + assignment.variableName.name = currentLiteral(); if (!m_julia && instructions().count(assignment.variableName.name)) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); return assignment; } - case Token::Return: // opcode - case Token::Byte: // opcode default: break; } @@ -91,50 +111,100 @@ assembly::Statement Parser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - Statement statement(parseElementaryOperation()); - switch (m_scanner->currentToken()) + Statement statement(parseElementaryOperation(false)); + switch (currentToken()) { case Token::LParen: - return parseFunctionalInstruction(std::move(statement)); + return parseCall(std::move(statement)); case Token::Colon: { if (statement.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \":\"."); assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement); - m_scanner->next(); + advance(); // identifier:=: should be parsed as identifier: =: (i.e. a label), // while identifier:= (being followed by a non-colon) as identifier := (assignment). - if (m_scanner->currentToken() == Token::Assign && m_scanner->peekNextToken() != Token::Colon) + if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) { - // functional assignment - FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location); + assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location); if (!m_julia && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); - m_scanner->next(); - funAss.variableName = identifier; - funAss.value.reset(new Statement(parseExpression())); - funAss.location.end = locationOf(*funAss.value).end; - return funAss; + advance(); + assignment.variableName = identifier; + assignment.value.reset(new Statement(parseExpression())); + assignment.location.end = locationOf(*assignment.value).end; + return assignment; } else { // label + if (m_julia) + fatalParserError("Labels are not supported."); Label label = createWithLocation<Label>(identifier.location); label.name = identifier.name; return label; } } default: + if (m_julia) + fatalParserError("Call or assignment expected."); break; } return statement; } +assembly::Case Parser::parseCase() +{ + assembly::Case _case = createWithLocation<assembly::Case>(); + if (m_scanner->currentToken() == Token::Default) + m_scanner->next(); + else if (m_scanner->currentToken() == Token::Case) + { + m_scanner->next(); + assembly::Statement statement = parseElementaryOperation(); + if (statement.type() != typeid(assembly::Literal)) + fatalParserError("Literal expected."); + _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement))); + } + else + fatalParserError("Case or default case expected."); + _case.body = parseBlock(); + _case.location.end = _case.body.location.end; + return _case; +} + +assembly::ForLoop Parser::parseForLoop() +{ + ForLoop forLoop = createWithLocation<ForLoop>(); + expectToken(Token::For); + forLoop.pre = parseBlock(); + forLoop.condition = make_shared<Statement>(parseExpression()); + if (forLoop.condition->type() == typeid(assembly::Instruction)) + fatalParserError("Instructions are not supported as conditions for the for statement."); + forLoop.post = parseBlock(); + forLoop.body = parseBlock(); + forLoop.location.end = forLoop.body.location.end; + return forLoop; +} + assembly::Statement Parser::parseExpression() { Statement operation = parseElementaryOperation(true); - if (m_scanner->currentToken() == Token::LParen) - return parseFunctionalInstruction(std::move(operation)); + if (operation.type() == typeid(Instruction)) + { + Instruction const& instr = boost::get<Instruction>(operation); + int args = instructionInfo(instr.instruction).args; + if (args > 0 && currentToken() != Token::LParen) + fatalParserError(string( + "Expected token \"(\" (\"" + + instructionNames().at(instr.instruction) + + "\" expects " + + boost::lexical_cast<string>(args) + + " arguments)" + )); + } + if (currentToken() == Token::LParen) + return parseCall(std::move(operation)); else return operation; } @@ -159,14 +229,30 @@ std::map<string, dev::solidity::Instruction> const& Parser::instructions() // add alias for suicide s_instructions["suicide"] = solidity::Instruction::SELFDESTRUCT; + // add alis for sha3 + s_instructions["sha3"] = solidity::Instruction::KECCAK256; } return s_instructions; } +std::map<dev::solidity::Instruction, string> const& Parser::instructionNames() +{ + static map<dev::solidity::Instruction, string> s_instructionNames; + if (s_instructionNames.empty()) + { + for (auto const& instr: instructions()) + s_instructionNames[instr.second] = instr.first; + // set the ambiguous instructions to a clear default + s_instructionNames[solidity::Instruction::SELFDESTRUCT] = "selfdestruct"; + s_instructionNames[solidity::Instruction::KECCAK256] = "keccak256"; + } + return s_instructionNames; +} + assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { Statement ret; - switch (m_scanner->currentToken()) + switch (currentToken()) { case Token::Identifier: case Token::Return: @@ -174,14 +260,14 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) case Token::Address: { string literal; - if (m_scanner->currentToken() == Token::Return) + if (currentToken() == Token::Return) literal = "return"; - else if (m_scanner->currentToken() == Token::Byte) + else if (currentToken() == Token::Byte) literal = "byte"; - else if (m_scanner->currentToken() == Token::Address) + else if (currentToken() == Token::Address) literal = "address"; else - literal = m_scanner->currentLiteral(); + literal = currentLiteral(); // first search the set of instructions. if (!m_julia && instructions().count(literal)) { @@ -190,28 +276,62 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { InstructionInfo info = dev::solidity::instructionInfo(instr); if (info.ret != 1) - fatalParserError("Instruction " + info.name + " not allowed in this context."); + fatalParserError("Instruction \"" + literal + "\" not allowed in this context."); } ret = Instruction{location(), instr}; } else ret = Identifier{location(), literal}; + advance(); break; } case Token::StringLiteral: case Token::Number: + case Token::TrueLiteral: + case Token::FalseLiteral: { - ret = Literal{ + LiteralKind kind = LiteralKind::Number; + switch (currentToken()) + { + case Token::StringLiteral: + kind = LiteralKind::String; + break; + case Token::Number: + kind = LiteralKind::Number; + break; + case Token::TrueLiteral: + case Token::FalseLiteral: + kind = LiteralKind::Boolean; + break; + default: + break; + } + + Literal literal{ location(), - m_scanner->currentToken() == Token::Number, - m_scanner->currentLiteral() + kind, + currentLiteral(), + "" }; + advance(); + if (m_julia) + { + expectToken(Token::Colon); + literal.location.end = endPosition(); + literal.type = expectAsmIdentifier(); + } + else if (kind == LiteralKind::Boolean) + fatalParserError("True and false are not valid literals."); + ret = std::move(literal); break; } default: - fatalParserError("Expected elementary inline assembly operation."); + fatalParserError( + m_julia ? + "Literal or identifier expected." : + "Literal, identifier or instruction expected." + ); } - m_scanner->next(); return ret; } @@ -219,7 +339,14 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { VariableDeclaration varDecl = createWithLocation<VariableDeclaration>(); expectToken(Token::Let); - varDecl.name = expectAsmIdentifier(); + while (true) + { + varDecl.variables.emplace_back(parseTypedName()); + if (currentToken() == Token::Comma) + expectToken(Token::Comma); + else + break; + } expectToken(Token::Colon); expectToken(Token::Assign); varDecl.value.reset(new Statement(parseExpression())); @@ -233,22 +360,22 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() expectToken(Token::Function); funDef.name = expectAsmIdentifier(); expectToken(Token::LParen); - while (m_scanner->currentToken() != Token::RParen) + while (currentToken() != Token::RParen) { - funDef.arguments.push_back(expectAsmIdentifier()); - if (m_scanner->currentToken() == Token::RParen) + funDef.arguments.emplace_back(parseTypedName()); + if (currentToken() == Token::RParen) break; expectToken(Token::Comma); } expectToken(Token::RParen); - if (m_scanner->currentToken() == Token::Sub) + if (currentToken() == Token::Sub) { expectToken(Token::Sub); expectToken(Token::GreaterThan); while (true) { - funDef.returns.push_back(expectAsmIdentifier()); - if (m_scanner->currentToken() == Token::LBrace) + funDef.returns.emplace_back(parseTypedName()); + if (currentToken() == Token::LBrace) break; expectToken(Token::Comma); } @@ -258,7 +385,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() return funDef; } -assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) +assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) { if (_instruction.type() == typeid(Instruction)) { @@ -276,26 +403,40 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in unsigned args = unsigned(instrInfo.args); for (unsigned i = 0; i < args; ++i) { + /// check for premature closing parentheses + if (currentToken() == Token::RParen) + fatalParserError(string( + "Expected expression (\"" + + instructionNames().at(instr) + + "\" expects " + + boost::lexical_cast<string>(args) + + " arguments)" + )); + ret.arguments.emplace_back(parseExpression()); if (i != args - 1) { - if (m_scanner->currentToken() != Token::Comma) + if (currentToken() != Token::Comma) fatalParserError(string( - "Expected comma (" + - instrInfo.name + - " expects " + + "Expected comma (\"" + + instructionNames().at(instr) + + "\" expects " + boost::lexical_cast<string>(args) + " arguments)" )); else - m_scanner->next(); + advance(); } } ret.location.end = endPosition(); - if (m_scanner->currentToken() == Token::Comma) - fatalParserError( - string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)") - ); + if (currentToken() == Token::Comma) + fatalParserError(string( + "Expected ')' (\"" + + instructionNames().at(instr) + + "\" expects " + + boost::lexical_cast<string>(args) + + " arguments)" + )); expectToken(Token::RParen); return ret; } @@ -305,10 +446,10 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in ret.functionName = std::move(boost::get<Identifier>(_instruction)); ret.location = ret.functionName.location; expectToken(Token::LParen); - while (m_scanner->currentToken() != Token::RParen) + while (currentToken() != Token::RParen) { ret.arguments.emplace_back(parseExpression()); - if (m_scanner->currentToken() == Token::RParen) + if (currentToken() == Token::RParen) break; expectToken(Token::Comma); } @@ -317,15 +458,40 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in return ret; } else - fatalParserError("Assembly instruction or function name required in front of \"(\")"); + fatalParserError( + m_julia ? + "Function name expected." : + "Assembly instruction or function name required in front of \"(\")" + ); return {}; } +TypedName Parser::parseTypedName() +{ + TypedName typedName = createWithLocation<TypedName>(); + typedName.name = expectAsmIdentifier(); + if (m_julia) + { + expectToken(Token::Colon); + typedName.location.end = endPosition(); + typedName.type = expectAsmIdentifier(); + } + return typedName; +} + string Parser::expectAsmIdentifier() { - string name = m_scanner->currentLiteral(); - if (!m_julia && instructions().count(name)) + string name = currentLiteral(); + if (m_julia) + { + if (currentToken() == Token::Bool) + { + advance(); + return name; + } + } + else if (instructions().count(name)) fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); return name; diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index c55fd2ac..45708afd 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,7 +37,7 @@ namespace assembly class Parser: public ParserBase { public: - explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {} + explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. @@ -62,13 +62,17 @@ protected: Block parseBlock(); Statement parseStatement(); + Case parseCase(); + ForLoop parseForLoop(); /// Parses a functional expression that has to push exactly one stack element Statement parseExpression(); - std::map<std::string, dev::solidity::Instruction> const& instructions(); + static std::map<std::string, dev::solidity::Instruction> const& instructions(); + static std::map<dev::solidity::Instruction, std::string> const& instructionNames(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); - Statement parseFunctionalInstruction(Statement&& _instruction); + Statement parseCall(Statement&& _instruction); + TypedName parseTypedName(); std::string expectAsmIdentifier(); private: diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 252e91f9..062ff453 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -21,8 +21,8 @@ */ #include <libsolidity/inlineasm/AsmPrinter.h> - #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/interface/Exceptions.h> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/replace.hpp> @@ -40,13 +40,22 @@ using namespace dev::solidity::assembly; string AsmPrinter::operator()(assembly::Instruction const& _instruction) { + solAssert(!m_julia, ""); return boost::to_lower_copy(instructionInfo(_instruction.instruction).name); } string AsmPrinter::operator()(assembly::Literal const& _literal) { - if (_literal.isNumber) - return _literal.value; + switch (_literal.kind) + { + case LiteralKind::Number: + return _literal.value + appendTypeName(_literal.type); + case LiteralKind::Boolean: + return ((_literal.value == "true") ? "true" : "false") + appendTypeName(_literal.type); + case LiteralKind::String: + break; + } + string out; for (char c: _literal.value) if (c == '\\') @@ -73,7 +82,7 @@ string AsmPrinter::operator()(assembly::Literal const& _literal) } else out += c; - return "\"" + out + "\""; + return "\"" + out + "\"" + appendTypeName(_literal.type); } string AsmPrinter::operator()(assembly::Identifier const& _identifier) @@ -83,6 +92,7 @@ string AsmPrinter::operator()(assembly::Identifier const& _identifier) string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction) { + solAssert(!m_julia, ""); return (*this)(_functionalInstruction.instruction) + "(" + @@ -94,29 +104,56 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional string AsmPrinter::operator()(assembly::Label const& _label) { + solAssert(!m_julia, ""); return _label.name + ":"; } -string AsmPrinter::operator()(assembly::Assignment const& _assignment) +string AsmPrinter::operator()(assembly::StackAssignment const& _assignment) { + solAssert(!m_julia, ""); return "=: " + (*this)(_assignment.variableName); } -string AsmPrinter::operator()(assembly::FunctionalAssignment const& _functionalAssignment) +string AsmPrinter::operator()(assembly::Assignment const& _assignment) { - return (*this)(_functionalAssignment.variableName) + " := " + boost::apply_visitor(*this, *_functionalAssignment.value); + return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value); } string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration) { - return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value); + string out = "let "; + out += boost::algorithm::join( + _variableDeclaration.variables | boost::adaptors::transformed( + [this](TypedName variable) { return variable.name + appendTypeName(variable.type); } + ), + ", " + ); + out += " := "; + out += boost::apply_visitor(*this, *_variableDeclaration.value); + return out; } string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition) { - string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; + string out = "function " + _functionDefinition.name + "("; + out += boost::algorithm::join( + _functionDefinition.arguments | boost::adaptors::transformed( + [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } + ), + ", " + ); + out += ")"; if (!_functionDefinition.returns.empty()) - out += " -> " + boost::algorithm::join(_functionDefinition.returns, ", "); + { + out += " -> "; + out += boost::algorithm::join( + _functionDefinition.returns | boost::adaptors::transformed( + [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } + ), + ", " + ); + } + return out + "\n" + (*this)(_functionDefinition.body); } @@ -130,6 +167,33 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall) ")"; } +string AsmPrinter::operator()(Switch const& _switch) +{ + string out = "switch " + boost::apply_visitor(*this, *_switch.expression); + for (auto const& _case: _switch.cases) + { + if (!_case.value) + out += "\ndefault "; + else + out += "\ncase " + (*this)(*_case.value) + " "; + out += (*this)(_case.body); + } + return out; +} + +string AsmPrinter::operator()(assembly::ForLoop const& _forLoop) +{ + string out = "for "; + out += (*this)(_forLoop.pre); + out += "\n"; + out += boost::apply_visitor(*this, *_forLoop.condition); + out += "\n"; + out += (*this)(_forLoop.post); + out += "\n"; + out += (*this)(_forLoop.body); + return out; +} + string AsmPrinter::operator()(Block const& _block) { if (_block.statements.empty()) @@ -141,3 +205,10 @@ string AsmPrinter::operator()(Block const& _block) boost::replace_all(body, "\n", "\n "); return "{\n " + body + "\n}"; } + +string AsmPrinter::appendTypeName(std::string const& _type) +{ + if (m_julia) + return ":" + _type; + return ""; +} diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index a7a1de0a..f57dddc8 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> namespace dev @@ -30,32 +32,30 @@ namespace solidity { namespace assembly { -struct Instruction; -struct Literal; -struct Identifier; -struct FunctionalInstruction; -struct Label; -struct Assignment; -struct FunctionalAssignment; -struct VariableDeclaration; -struct FunctionDefinition; -struct FunctionCall; -struct Block; class AsmPrinter: public boost::static_visitor<std::string> { public: + explicit AsmPrinter(bool _julia = false): m_julia(_julia) {} + std::string operator()(assembly::Instruction const& _instruction); std::string operator()(assembly::Literal const& _literal); std::string operator()(assembly::Identifier const& _identifier); std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction); std::string operator()(assembly::Label const& _label); + std::string operator()(assembly::StackAssignment const& _assignment); std::string operator()(assembly::Assignment const& _assignment); - std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment); std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); std::string operator()(assembly::FunctionDefinition const& _functionDefinition); std::string operator()(assembly::FunctionCall const& _functionCall); + std::string operator()(assembly::Switch const& _switch); + std::string operator()(assembly::ForLoop const& _forLoop); std::string operator()(assembly::Block const& _block); + +private: + std::string appendTypeName(std::string const& _type); + + bool m_julia = false; }; } diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp index 609dca16..315d5953 100644 --- a/libsolidity/inlineasm/AsmScope.cpp +++ b/libsolidity/inlineasm/AsmScope.cpp @@ -32,19 +32,21 @@ bool Scope::registerLabel(string const& _name) return true; } -bool Scope::registerVariable(string const& _name) +bool Scope::registerVariable(string const& _name, JuliaType const& _type) { if (exists(_name)) return false; - identifiers[_name] = Variable(); + Variable variable; + variable.type = _type; + identifiers[_name] = variable; return true; } -bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns) +bool Scope::registerFunction(string const& _name, std::vector<JuliaType> const& _arguments, std::vector<JuliaType> const& _returns) { if (exists(_name)) return false; - identifiers[_name] = Function(_arguments, _returns); + identifiers[_name] = Function{_arguments, _returns}; return true; } @@ -77,3 +79,20 @@ bool Scope::exists(string const& _name) else return false; } + +size_t Scope::numberOfVariables() const +{ + size_t count = 0; + for (auto const& identifier: identifiers) + if (identifier.second.type() == typeid(Scope::Variable)) + count++; + return count; +} + +bool Scope::insideFunction() const +{ + for (Scope const* s = this; s; s = s->superScope) + if (s->functionScope) + return true; + return false; +} diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h index 37e0f0b8..cc240565 100644 --- a/libsolidity/inlineasm/AsmScope.h +++ b/libsolidity/inlineasm/AsmScope.h @@ -23,6 +23,7 @@ #include <libsolidity/interface/Exceptions.h> #include <boost/variant.hpp> +#include <boost/optional.hpp> #include <functional> #include <memory> @@ -61,36 +62,28 @@ struct GenericVisitor<>: public boost::static_visitor<> { struct Scope { - struct Variable - { - /// Used during code generation to store the stack height. @todo move there. - int stackHeight = 0; - /// Used during analysis to check whether we already passed the declaration inside the block. - /// @todo move there. - bool active = false; - }; - - struct Label - { - size_t id = unassignedLabelId; - static const size_t errorLabelId = -1; - static const size_t unassignedLabelId = 0; - }; + using JuliaType = std::string; + using LabelID = size_t; + struct Variable { JuliaType type; }; + struct Label { }; struct Function { - Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {} - size_t arguments = 0; - size_t returns = 0; + std::vector<JuliaType> arguments; + std::vector<JuliaType> returns; }; using Identifier = boost::variant<Variable, Label, Function>; using Visitor = GenericVisitor<Variable const, Label const, Function const>; using NonconstVisitor = GenericVisitor<Variable, Label, Function>; - bool registerVariable(std::string const& _name); + bool registerVariable(std::string const& _name, JuliaType const& _type); bool registerLabel(std::string const& _name); - bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns); + bool registerFunction( + std::string const& _name, + std::vector<JuliaType> const& _arguments, + std::vector<JuliaType> const& _returns + ); /// Looks up the identifier in this or super scopes and returns a valid pointer if found /// or a nullptr if not found. Variable lookups up across function boundaries will fail, as @@ -116,6 +109,11 @@ struct Scope /// across function and assembly boundaries). bool exists(std::string const& _name); + /// @returns the number of variables directly registered inside the scope. + size_t numberOfVariables() const; + /// @returns true if this scope is inside a function. + bool insideFunction() const; + Scope* superScope = nullptr; /// If true, variables from the super scope are not visible here (other identifiers are), /// but they are still taken into account to prevent shadowing. diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index de6fbdaa..5b3174b8 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -22,9 +22,10 @@ #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmScope.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/Utils.h> #include <boost/range/adaptor/reversed.hpp> @@ -36,13 +37,9 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; -ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors): - m_scopes(_scopes), m_errors(_errors) +ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter): + m_info(_info), m_errorReporter(_errorReporter) { - // Make the Solidity ErrorTag available to inline assembly - Scope::Label errorLabel; - errorLabel.id = Scope::Label::errorLabelId; - scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel; m_currentScope = &scope(nullptr); } @@ -51,11 +48,10 @@ bool ScopeFiller::operator()(Label const& _item) if (!m_currentScope->registerLabel(_item.name)) { //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Label name " + _item.name + " already taken in this scope.", - _item.location - )); + m_errorReporter.declarationError( + _item.location, + "Label name " + _item.name + " already taken in this scope." + ); return false; } return true; @@ -63,32 +59,75 @@ bool ScopeFiller::operator()(Label const& _item) bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl) { - return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope); + for (auto const& variable: _varDecl.variables) + if (!registerVariable(variable, _varDecl.location, *m_currentScope)) + return false; + return true; } bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) { bool success = true; - if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size())) + vector<Scope::JuliaType> arguments; + for (auto const& _argument: _funDef.arguments) + arguments.push_back(_argument.type); + vector<Scope::JuliaType> returns; + for (auto const& _return: _funDef.returns) + returns.push_back(_return.type); + if (!m_currentScope->registerFunction(_funDef.name, arguments, returns)) { //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Function name " + _funDef.name + " already taken in this scope.", - _funDef.location - )); + m_errorReporter.declarationError( + _funDef.location, + "Function name " + _funDef.name + " already taken in this scope." + ); success = false; } - Scope& body = scope(&_funDef.body); - body.superScope = m_currentScope; - body.functionScope = true; + + auto virtualBlock = m_info.virtualBlocks[&_funDef] = make_shared<Block>(); + Scope& varScope = scope(virtualBlock.get()); + varScope.superScope = m_currentScope; + m_currentScope = &varScope; + varScope.functionScope = true; for (auto const& var: _funDef.arguments + _funDef.returns) - if (!registerVariable(var, _funDef.location, body)) + if (!registerVariable(var, _funDef.location, varScope)) success = false; if (!(*this)(_funDef.body)) success = false; + solAssert(m_currentScope == &varScope, ""); + m_currentScope = m_currentScope->superScope; + + return success; +} + +bool ScopeFiller::operator()(Switch const& _switch) +{ + bool success = true; + for (auto const& _case: _switch.cases) + if (!(*this)(_case.body)) + success = false; + return success; +} + +bool ScopeFiller::operator()(ForLoop const& _forLoop) +{ + Scope* originalScope = m_currentScope; + + bool success = true; + if (!(*this)(_forLoop.pre)) + success = false; + m_currentScope = &scope(&_forLoop.pre); + if (!boost::apply_visitor(*this, *_forLoop.condition)) + success = false; + if (!(*this)(_forLoop.body)) + success = false; + if (!(*this)(_forLoop.post)) + success = false; + + m_currentScope = originalScope; + return success; } @@ -106,16 +145,15 @@ bool ScopeFiller::operator()(Block const& _block) return success; } -bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope) +bool ScopeFiller::registerVariable(TypedName const& _name, SourceLocation const& _location, Scope& _scope) { - if (!_scope.registerVariable(_name)) + if (!_scope.registerVariable(_name.name, _name.type)) { //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable name " + _name + " already taken in this scope.", - _location - )); + m_errorReporter.declarationError( + _location, + "Variable name " + _name.name + " already taken in this scope." + ); return false; } return true; @@ -123,7 +161,7 @@ bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _l Scope& ScopeFiller::scope(Block const* _block) { - auto& scope = m_scopes[_block]; + auto& scope = m_info.scopes[_block]; if (!scope) scope = make_shared<Scope>(); return *scope; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index bb62948b..80c03d2c 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -20,7 +20,7 @@ #pragma once -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/inlineasm/AsmDataForward.h> #include <boost/variant.hpp> @@ -29,24 +29,16 @@ namespace dev { +struct SourceLocation; namespace solidity { +class ErrorReporter; namespace assembly { -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct FunctionalAssignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct Assignment; -struct FunctionDefinition; -struct FunctionCall; - +struct TypedName; struct Scope; +struct AsmAnalysisInfo; /** * Fills scopes with identifiers and checks for name clashes. @@ -55,24 +47,25 @@ struct Scope; class ScopeFiller: public boost::static_visitor<bool> { public: - using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; - ScopeFiller(Scopes& _scopes, ErrorList& _errors); + ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter); bool operator()(assembly::Instruction const&) { return true; } bool operator()(assembly::Literal const&) { return true; } bool operator()(assembly::Identifier const&) { return true; } bool operator()(assembly::FunctionalInstruction const&) { return true; } bool operator()(assembly::Label const& _label); + bool operator()(assembly::StackAssignment const&) { return true; } bool operator()(assembly::Assignment const&) { return true; } - bool operator()(assembly::FunctionalAssignment const&) { return true; } bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const&) { return true; } + bool operator()(assembly::Switch const& _switch); + bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); private: bool registerVariable( - std::string const& _name, + TypedName const& _name, SourceLocation const& _location, Scope& _scope ); @@ -80,8 +73,8 @@ private: Scope& scope(assembly::Block const* _block); Scope* m_currentScope = nullptr; - Scopes& m_scopes; - ErrorList& m_errors; + AsmAnalysisInfo& m_info; + ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp deleted file mode 100644 index c2a7d8ea..00000000 --- a/libsolidity/inlineasm/AsmStack.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - 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 Christian <c@ethdev.com> - * @date 2016 - * Full-stack Solidity inline assember. - */ - -#include <libsolidity/inlineasm/AsmStack.h> - -#include <libsolidity/inlineasm/AsmParser.h> -#include <libsolidity/inlineasm/AsmCodeGen.h> -#include <libsolidity/inlineasm/AsmPrinter.h> -#include <libsolidity/inlineasm/AsmAnalysis.h> -#include <libsolidity/inlineasm/AsmAnalysisInfo.h> - -#include <libsolidity/parsing/Scanner.h> - -#include <libevmasm/Assembly.h> -#include <libevmasm/SourceLocation.h> - -#include <memory> - -using namespace std; -using namespace dev; -using namespace dev::solidity; -using namespace dev::solidity::assembly; - -bool InlineAssemblyStack::parse( - shared_ptr<Scanner> const& _scanner, - ExternalIdentifierAccess::Resolver const& _resolver -) -{ - m_parserResult = make_shared<Block>(); - Parser parser(m_errors); - auto result = parser.parse(_scanner); - if (!result) - return false; - - *m_parserResult = std::move(*result); - AsmAnalysisInfo analysisInfo; - return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult); -} - -string InlineAssemblyStack::toString() -{ - return AsmPrinter()(*m_parserResult); -} - -eth::Assembly InlineAssemblyStack::assemble() -{ - AsmAnalysisInfo analysisInfo; - AsmAnalyzer analyzer(analysisInfo, m_errors); - solAssert(analyzer.analyze(*m_parserResult), ""); - CodeGenerator codeGen(m_errors); - return codeGen.assemble(*m_parserResult, analysisInfo); -} - -bool InlineAssemblyStack::parseAndAssemble( - string const& _input, - eth::Assembly& _assembly, - ExternalIdentifierAccess const& _identifierAccess -) -{ - ErrorList errors; - auto scanner = make_shared<Scanner>(CharStream(_input), "--CODEGEN--"); - auto parserResult = Parser(errors).parse(scanner); - if (!errors.empty()) - return false; - solAssert(parserResult, ""); - - AsmAnalysisInfo analysisInfo; - AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve); - solAssert(analyzer.analyze(*parserResult), ""); - CodeGenerator(errors).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess); - - // At this point, the assembly might be messed up, but we should throw an - // internal compiler error anyway. - return errors.empty(); -} - diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h deleted file mode 100644 index 77a7e02a..00000000 --- a/libsolidity/inlineasm/AsmStack.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - 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 Christian <c@ethdev.com> - * @date 2016 - * Full-stack Solidity inline assember. - */ - -#pragma once - -#include <libsolidity/interface/Exceptions.h> - -#include <string> -#include <functional> - -namespace dev -{ -namespace eth -{ -class Assembly; -} -namespace solidity -{ -class Scanner; -namespace assembly -{ -struct Block; -struct Identifier; - -enum class IdentifierContext { LValue, RValue }; - -/// Object that is used to resolve references and generate code for access to identifiers external -/// to inline assembly (not used in standalone assembly mode). -struct ExternalIdentifierAccess -{ - using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>; - /// Resolve a an external reference given by the identifier in the given context. - /// @returns the size of the value (number of stack slots) or size_t(-1) if not found. - Resolver resolve; - using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>; - /// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context) - /// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed - /// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack. - CodeGenerator generateCode; -}; - -class InlineAssemblyStack -{ -public: - /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. - /// @return false or error. - bool parse( - std::shared_ptr<Scanner> const& _scanner, - ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver() - ); - /// Converts the parser result back into a string form (not necessarily the same form - /// as the source form, but it should parse into the same parsed form again). - std::string toString(); - - eth::Assembly assemble(); - - /// Parse and assemble a string in one run - for use in Solidity code generation itself. - bool parseAndAssemble( - std::string const& _input, - eth::Assembly& _assembly, - ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() - ); - - ErrorList const& errors() const { return m_errors; } - -private: - std::shared_ptr<Block> m_parserResult; - ErrorList m_errors; -}; - -} -} -} diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp new file mode 100644 index 00000000..12f958fc --- /dev/null +++ b/libsolidity/interface/ABI.cpp @@ -0,0 +1,116 @@ +/* + 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/>. +*/ +/** + * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + */ + +#include <libsolidity/interface/ABI.h> +#include <boost/range/irange.hpp> +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +Json::Value ABI::generate(ContractDefinition const& _contractDef) +{ + Json::Value abi(Json::arrayValue); + + for (auto it: _contractDef.interfaceFunctions()) + { + auto externalFunctionType = it.second->interfaceFunctionType(); + Json::Value method; + method["type"] = "function"; + method["name"] = it.second->declaration().name(); + method["constant"] = it.second->isConstant(); + method["payable"] = it.second->isPayable(); + method["inputs"] = formatTypeList( + externalFunctionType->parameterNames(), + externalFunctionType->parameterTypes(), + _contractDef.isLibrary() + ); + method["outputs"] = formatTypeList( + externalFunctionType->returnParameterNames(), + externalFunctionType->returnParameterTypes(), + _contractDef.isLibrary() + ); + abi.append(method); + } + if (_contractDef.constructor()) + { + Json::Value method; + method["type"] = "constructor"; + auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); + solAssert(!!externalFunction, ""); + method["payable"] = externalFunction->isPayable(); + method["inputs"] = formatTypeList( + externalFunction->parameterNames(), + externalFunction->parameterTypes(), + _contractDef.isLibrary() + ); + abi.append(method); + } + if (_contractDef.fallbackFunction()) + { + auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); + solAssert(!!externalFunctionType, ""); + Json::Value method; + method["type"] = "fallback"; + method["payable"] = externalFunctionType->isPayable(); + abi.append(method); + } + for (auto const& it: _contractDef.interfaceEvents()) + { + Json::Value event; + event["type"] = "event"; + event["name"] = it->name(); + event["anonymous"] = it->isAnonymous(); + Json::Value params(Json::arrayValue); + for (auto const& p: it->parameters()) + { + solAssert(!!p->annotation().type->interfaceType(false), ""); + Json::Value input; + input["name"] = p->name(); + input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false); + input["indexed"] = p->isIndexed(); + params.append(input); + } + event["inputs"] = params; + abi.append(event); + } + + return abi; +} + +Json::Value ABI::formatTypeList( + vector<string> const& _names, + vector<TypePointer> const& _types, + bool _forLibrary +) +{ + Json::Value params(Json::arrayValue); + solAssert(_names.size() == _types.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _names.size(); ++i) + { + solAssert(_types[i], ""); + Json::Value param; + param["name"] = _names[i]; + param["type"] = _types[i]->canonicalName(_forLibrary); + params.append(param); + } + return params; +} diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h new file mode 100644 index 00000000..95b162a9 --- /dev/null +++ b/libsolidity/interface/ABI.h @@ -0,0 +1,56 @@ +/* + 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/>. +*/ +/** + * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + */ + +#pragma once + +#include <string> +#include <memory> +#include <json/json.h> + +namespace dev +{ +namespace solidity +{ + +// Forward declarations +class ContractDefinition; +class Type; +using TypePointer = std::shared_ptr<Type const>; + +class ABI +{ +public: + /// Get the ABI Interface of the contract + /// @param _contractDef The contract definition + /// @return A JSONrepresentation of the contract's ABI Interface + static Json::Value generate(ContractDefinition const& _contractDef); +private: + /// @returns a json value suitable for a list of types in function input or output + /// parameters or other places. If @a _forLibrary is true, complex types are referenced + /// by name, otherwise they are anonymously expanded. + static Json::Value formatTypeList( + std::vector<std::string> const& _names, + std::vector<TypePointer> const& _types, + bool _forLibrary + ); +}; + +} +} diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp new file mode 100644 index 00000000..23524bb3 --- /dev/null +++ b/libsolidity/interface/AssemblyStack.cpp @@ -0,0 +1,119 @@ +/* + 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/>. +*/ +/** + * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * eWasm as output. + */ + + +#include <libsolidity/interface/AssemblyStack.h> + +#include <libsolidity/parsing/Scanner.h> +#include <libsolidity/inlineasm/AsmPrinter.h> +#include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> +#include <libsolidity/inlineasm/AsmCodeGen.h> + +#include <libevmasm/Assembly.h> + +#include <libjulia/backends/evm/EVMCodeTransform.h> +#include <libjulia/backends/evm/EVMAssembly.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +Scanner const& AssemblyStack::scanner() const +{ + solAssert(m_scanner, ""); + return *m_scanner; +} + +bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) +{ + m_errors.clear(); + m_analysisSuccessful = false; + m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName); + m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner); + if (!m_errorReporter.errors().empty()) + return false; + solAssert(m_parserResult, ""); + + return analyzeParsed(); +} + +bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scanner) +{ + m_errors.clear(); + m_analysisSuccessful = false; + if (_scanner) + m_scanner = make_shared<Scanner>(*_scanner); + m_parserResult = make_shared<assembly::Block>(_block); + + return analyzeParsed(); +} + +bool AssemblyStack::analyzeParsed() +{ + m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA); + m_analysisSuccessful = analyzer.analyze(*m_parserResult); + return m_analysisSuccessful; +} + +MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const +{ + solAssert(m_analysisSuccessful, ""); + solAssert(m_parserResult, ""); + solAssert(m_analysisInfo, ""); + + switch (_machine) + { + case Machine::EVM: + { + MachineAssemblyObject object; + eth::Assembly assembly; + assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly); + object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble()); + ostringstream tmp; + assembly.stream(tmp); + object.assembly = tmp.str(); + return object; + } + case Machine::EVM15: + { + MachineAssemblyObject object; + julia::EVMAssembly assembly(true); + julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::JULIA, true)(*m_parserResult); + object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); + /// TOOD: fill out text representation + return object; + } + case Machine::eWasm: + solUnimplemented("eWasm backend is not yet implemented."); + } + // unreachable + return MachineAssemblyObject(); +} + +string AssemblyStack::print() const +{ + solAssert(m_parserResult, ""); + return assembly::AsmPrinter(m_language == Language::JULIA)(*m_parserResult); +} diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h new file mode 100644 index 00000000..2ae596ed --- /dev/null +++ b/libsolidity/interface/AssemblyStack.h @@ -0,0 +1,96 @@ +/* + 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/>. +*/ +/** + * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * eWasm as output. + */ + +#pragma once + +#include <libsolidity/interface/ErrorReporter.h> +#include <libevmasm/LinkerObject.h> + +#include <string> +#include <memory> + +namespace dev +{ +namespace solidity +{ +class Scanner; +namespace assembly +{ +struct AsmAnalysisInfo; +struct Block; +} + +struct MachineAssemblyObject +{ + std::shared_ptr<eth::LinkerObject> bytecode; + std::string assembly; +}; + +/* + * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and + * eWasm as output. + */ +class AssemblyStack +{ +public: + enum class Language { JULIA, Assembly }; + enum class Machine { EVM, EVM15, eWasm }; + + explicit AssemblyStack(Language _language = Language::Assembly): + m_language(_language), m_errorReporter(m_errors) + {} + + /// @returns the scanner used during parsing + Scanner const& scanner() const; + + /// Runs parsing and analysis steps, returns false if input cannot be assembled. + /// Multiple calls overwrite the previous state. + bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source); + + /// Runs analysis step on the supplied block, returns false if input cannot be assembled. + /// Multiple calls overwrite the previous state. + bool analyze(assembly::Block const& _block, Scanner const* _scanner = nullptr); + + /// Run the assembly step (should only be called after parseAndAnalyze). + MachineAssemblyObject assemble(Machine _machine) const; + + /// @returns the errors generated during parsing, analysis (and potentially assembly). + ErrorList const& errors() const { return m_errors; } + + /// Pretty-print the input after having parsed it. + std::string print() const; + +private: + bool analyzeParsed(); + + Language m_language = Language::Assembly; + + std::shared_ptr<Scanner> m_scanner; + + bool m_analysisSuccessful = false; + std::shared_ptr<assembly::Block> m_parserResult; + std::shared_ptr<assembly::AsmAnalysisInfo> m_analysisInfo; + ErrorList m_errors; + ErrorReporter m_errorReporter; +}; + +} +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 9c9c9614..e2507821 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -37,9 +37,9 @@ #include <libsolidity/analysis/PostTypeChecker.h> #include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/codegen/Compiler.h> -#include <libsolidity/interface/InterfaceHandler.h> +#include <libsolidity/interface/ABI.h> +#include <libsolidity/interface/Natspec.h> #include <libsolidity/interface/GasEstimator.h> -#include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -56,9 +56,6 @@ using namespace std; using namespace dev; using namespace dev::solidity; -CompilerStack::CompilerStack(ReadFile::Callback const& _readFile): - m_readFile(_readFile) {} - void CompilerStack::setRemappings(vector<string> const& _remappings) { vector<Remapping> remappings; @@ -87,6 +84,7 @@ void CompilerStack::reset(bool _keepSources) } else { + m_stackState = Empty; m_sources.clear(); } m_optimize = false; @@ -95,8 +93,7 @@ void CompilerStack::reset(bool _keepSources) m_scopes.clear(); m_sourceOrder.clear(); m_contracts.clear(); - m_errors.clear(); - m_stackState = Empty; + m_errorReporter.clear(); } bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) @@ -120,15 +117,11 @@ bool CompilerStack::parse() //reset if(m_stackState != SourcesSet) return false; - m_errors.clear(); + m_errorReporter.clear(); ASTNode::resetID(); if (SemVerVersion{string(VersionString)}.isPrerelease()) - { - auto err = make_shared<Error>(Error::Type::Warning); - *err << errinfo_comment("This is a pre-release compiler version, please do not use it in production."); - m_errors.push_back(err); - } + m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production."); vector<string> sourcesToParse; for (auto const& s: m_sources) @@ -138,9 +131,9 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errors).parse(source.scanner); + source.ast = Parser(m_errorReporter).parse(source.scanner); if (!source.ast) - solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); + solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); else { source.ast->annotation().path = path; @@ -153,7 +146,7 @@ bool CompilerStack::parse() } } } - if (Error::containsOnlyWarnings(m_errors)) + if (Error::containsOnlyWarnings(m_errorReporter.errors())) { m_stackState = ParsingSuccessful; return true; @@ -169,18 +162,18 @@ bool CompilerStack::analyze() resolveImports(); bool noErrors = true; - SyntaxChecker syntaxChecker(m_errors); + SyntaxChecker syntaxChecker(m_errorReporter); for (Source const* source: m_sourceOrder) if (!syntaxChecker.checkSyntax(*source->ast)) noErrors = false; - DocStringAnalyser docStringAnalyser(m_errors); + DocStringAnalyser docStringAnalyser(m_errorReporter); for (Source const* source: m_sourceOrder) if (!docStringAnalyser.analyseDocStrings(*source->ast)) noErrors = false; m_globalContext = make_shared<GlobalContext>(); - NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errors); + NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter); for (Source const* source: m_sourceOrder) if (!resolver.registerDeclarations(*source->ast)) return false; @@ -216,11 +209,11 @@ bool CompilerStack::analyze() { m_globalContext->setCurrentContract(*contract); resolver.updateDeclaration(*m_globalContext->currentThis()); - TypeChecker typeChecker(m_errors); + TypeChecker typeChecker(m_errorReporter); if (typeChecker.checkTypeRequirements(*contract)) { - contract->setDevDocumentation(InterfaceHandler::devDocumentation(*contract)); - contract->setUserDocumentation(InterfaceHandler::userDocumentation(*contract)); + contract->setDevDocumentation(Natspec::devDocumentation(*contract)); + contract->setUserDocumentation(Natspec::userDocumentation(*contract)); } else noErrors = false; @@ -236,7 +229,7 @@ bool CompilerStack::analyze() if (noErrors) { - PostTypeChecker postTypeChecker(m_errors); + PostTypeChecker postTypeChecker(m_errorReporter); for (Source const* source: m_sourceOrder) if (!postTypeChecker.check(*source->ast)) noErrors = false; @@ -244,7 +237,7 @@ bool CompilerStack::analyze() if (noErrors) { - StaticAnalyzer staticAnalyzer(m_errors); + StaticAnalyzer staticAnalyzer(m_errorReporter); for (Source const* source: m_sourceOrder) if (!staticAnalyzer.analyze(*source->ast)) noErrors = false; @@ -322,20 +315,6 @@ void CompilerStack::link() } } -bool CompilerStack::prepareFormalAnalysis(ErrorList* _errors) -{ - if (!_errors) - _errors = &m_errors; - Why3Translator translator(*_errors); - for (Source const* source: m_sourceOrder) - if (!translator.process(*source->ast)) - return false; - - m_formalTranslation = translator.translation(); - - return true; -} - eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { Contract const& currentContract = contract(_contractName); @@ -406,15 +385,6 @@ eth::LinkerObject const& CompilerStack::cloneObject(string const& _contractName) return contract(_contractName).cloneObject; } -dev::h256 CompilerStack::contractCodeHash(string const& _contractName) const -{ - auto const& obj = runtimeObject(_contractName); - if (obj.bytecode.empty() || !obj.linkReferences.empty()) - return dev::h256(); - else - return dev::keccak256(obj.bytecode); -} - Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _contractName, StringMap _sourceCodes, bool _inJsonFormat) const { Contract const& currentContract = contract(_contractName); @@ -444,20 +414,31 @@ map<string, unsigned> CompilerStack::sourceIndices() const return indices; } -Json::Value const& CompilerStack::interface(string const& _contractName) const +Json::Value const& CompilerStack::contractABI(string const& _contractName) const { - return metadata(_contractName, DocumentationType::ABIInterface); + return contractABI(contract(_contractName)); } -Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const +Json::Value const& CompilerStack::contractABI(Contract const& _contract) const { if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); - return metadata(contract(_contractName), _type); + solAssert(_contract.contract, ""); + + // caches the result + if (!_contract.abi) + _contract.abi.reset(new Json::Value(ABI::generate(*_contract.contract))); + + return *_contract.abi; } -Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const +Json::Value const& CompilerStack::natspec(string const& _contractName, DocumentationType _type) const +{ + return natspec(contract(_contractName), _type); +} + +Json::Value const& CompilerStack::natspec(Contract const& _contract, DocumentationType _type) const { if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); @@ -474,40 +455,54 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat case DocumentationType::NatspecDev: doc = &_contract.devDocumentation; break; - case DocumentationType::ABIInterface: - doc = &_contract.interface; - break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); } // caches the result if (!*doc) - doc->reset(new Json::Value(InterfaceHandler::documentation(*_contract.contract, _type))); + doc->reset(new Json::Value(Natspec::documentation(*_contract.contract, _type))); return *(*doc); } +Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const +{ + Json::Value methodIdentifiers(Json::objectValue); + for (auto const& it: contractDefinition(_contractName).interfaceFunctions()) + methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref()); + return methodIdentifiers; +} + string const& CompilerStack::onChainMetadata(string const& _contractName) const { if (m_stackState != CompilationSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); return contract(_contractName).onChainMetadata; } Scanner const& CompilerStack::scanner(string const& _sourceName) const { + if (m_stackState < SourcesSet) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No sources set.")); + return *source(_sourceName).scanner; } SourceUnit const& CompilerStack::ast(string const& _sourceName) const { + if (m_stackState < ParsingSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + return *source(_sourceName).ast; } ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + return *contract(_contractName).contract; } @@ -564,11 +559,10 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string 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.contentsOrErrorMessage); - m_errors.push_back(std::move(err)); + m_errorReporter.parserError( + import->location(), + string("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage) + ); continue; } } @@ -734,11 +728,6 @@ void CompilerStack::compileContract( } } -std::string CompilerStack::defaultContractName() const -{ - return contract("").contract->name(); -} - CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { if (m_contracts.empty()) @@ -821,9 +810,9 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const for (auto const& library: m_libraries) meta["settings"]["libraries"][library.first] = "0x" + toHex(library.second.asBytes()); - meta["output"]["abi"] = metadata(_contract, DocumentationType::ABIInterface); - meta["output"]["userdoc"] = metadata(_contract, DocumentationType::NatspecUser); - meta["output"]["devdoc"] = metadata(_contract, DocumentationType::NatspecDev); + meta["output"]["abi"] = contractABI(_contract); + meta["output"]["userdoc"] = natspec(_contract, DocumentationType::NatspecUser); + meta["output"]["devdoc"] = natspec(_contract, DocumentationType::NatspecDev); return jsonCompactPrint(meta); } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index c1d344ca..03a1b806 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -35,7 +35,7 @@ #include <libdevcore/FixedHash.h> #include <libevmasm/SourceLocation.h> #include <libevmasm/LinkerObject.h> -#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/ReadFile.h> namespace dev @@ -59,15 +59,14 @@ class FunctionDefinition; class SourceUnit; class Compiler; class GlobalContext; -class InterfaceHandler; +class Natspec; class Error; class DeclarationContainer; enum class DocumentationType: uint8_t { NatspecUser = 1, - NatspecDev, - ABIInterface + NatspecDev }; /** @@ -78,10 +77,21 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: + enum State { + Empty, + SourcesSet, + ParsingSuccessful, + AnalysisSuccessful, + CompilationSuccessful + }; + /// Creates a new compiler stack. /// @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()); + explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()): + m_readFile(_readFile), + m_errorList(), + m_errorReporter(m_errorList) {} /// Sets path remappings in the format "context:prefix=target" void setRemappings(std::vector<std::string> const& _remappings); @@ -115,7 +125,6 @@ public: 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; /// Compiles the source units that were previously added and parsed. /// @returns false on error. @@ -128,12 +137,6 @@ public: /// @returns false on error. bool compile(std::string const& _sourceCode, bool _optimize = false, unsigned _runs = 200); - /// Tries to translate all source files into a language suitable for formal analysis. - /// @param _errors list to store errors - defaults to the internal error list. - /// @returns false on error. - bool prepareFormalAnalysis(ErrorList* _errors = nullptr); - std::string const& formalTranslation() const { return m_formalTranslation; } - /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName = "") const; /// @returns the runtime object for the contract. @@ -157,11 +160,6 @@ public: /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; - /// @returns hash of the runtime bytecode for the contract, i.e. the code that is - /// returned by the constructor or the zero-h256 if the contract still needs to be linked or - /// does not have runtime code. - dev::h256 contractCodeHash(std::string const& _contractName = "") const; - /// Streams a verbose version of the assembly to @a _outStream. /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFromat shows whether the out should be in Json format @@ -173,14 +171,18 @@ public: /// @returns a mapping assigning each source name its index inside the vector returned /// by sourceNames(). std::map<std::string, unsigned> sourceIndices() const; - /// @returns a JSON representing the contract interface. + /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. - Json::Value const& interface(std::string const& _contractName = "") const; + Json::Value const& contractABI(std::string const& _contractName = "") const; /// @returns a JSON representing the contract's documentation. /// Prerequisite: Successful call to parse or compile. /// @param type The type of the documentation to get. /// Can be one of 4 types defined at @c DocumentationType - Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const; + Json::Value const& natspec(std::string const& _contractName, DocumentationType _type) const; + + /// @returns a JSON representing a map of method identifiers (hashes) to function names. + Json::Value methodIdentifiers(std::string const& _contractName) const; + std::string const& onChainMetadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } @@ -191,16 +193,6 @@ public: Scanner const& scanner(std::string const& _sourceName = "") const; /// @returns the parsed source unit with the supplied name. SourceUnit const& ast(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 offset of the entry point of the given function into the list of assembly items - /// or zero if it is not found or does not exist. - size_t functionEntryPoint( - std::string const& _contractName, - FunctionDefinition const& _function - ) const; /// Helper function for logs printing. Do only use in error cases, it's quite expensive. /// line and columns are numbered starting from 1 with following order: @@ -208,7 +200,9 @@ public: std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const; /// @returns the list of errors that occured during parsing and type checking. - ErrorList const& errors() const { return m_errors; } + ErrorList const& errors() { return m_errorReporter.errors(); } + + State state() const { return m_stackState; } private: /** @@ -230,20 +224,12 @@ private: eth::LinkerObject runtimeObject; eth::LinkerObject cloneObject; std::string onChainMetadata; ///< The metadata json that will be hashed into the chain. - mutable std::unique_ptr<Json::Value const> interface; + mutable std::unique_ptr<Json::Value const> abi; mutable std::unique_ptr<Json::Value const> userDocumentation; mutable std::unique_ptr<Json::Value const> devDocumentation; 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. /// @returns the newly loaded sources. @@ -265,9 +251,21 @@ private: Contract const& contract(std::string const& _contractName = "") const; 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; + std::string createOnChainMetadata(Contract const& _contract) const; std::string computeSourceMapping(eth::AssemblyItems const& _items) const; - Json::Value const& metadata(Contract const&, DocumentationType _type) const; + Json::Value const& contractABI(Contract const&) const; + Json::Value const& natspec(Contract const&, DocumentationType _type) const; + + /// @returns the offset of the entry point of the given function into the list of assembly items + /// or zero if it is not found or does not exist. + size_t functionEntryPoint( + std::string const& _contractName, + FunctionDefinition const& _function + ) const; struct Remapping { @@ -288,8 +286,8 @@ private: std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::vector<Source const*> m_sourceOrder; std::map<std::string const, Contract> m_contracts; - std::string m_formalTranslation; - ErrorList m_errors; + ErrorList m_errorList; + ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; State m_stackState = Empty; }; diff --git a/libsolidity/interface/ErrorReporter.cpp b/libsolidity/interface/ErrorReporter.cpp new file mode 100644 index 00000000..6e2667a5 --- /dev/null +++ b/libsolidity/interface/ErrorReporter.cpp @@ -0,0 +1,167 @@ +/* + 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 Rhett <roadriverrail@gmail.com> + * @date 2017 + * Error helper class. + */ + +#include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/ast/AST.h> +#include <memory> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +ErrorReporter& ErrorReporter::operator=(ErrorReporter const& _errorReporter) +{ + if (&_errorReporter == this) + return *this; + m_errorList = _errorReporter.m_errorList; + return *this; +} + + +void ErrorReporter::warning(string const& _description) +{ + error(Error::Type::Warning, SourceLocation(), _description); +} + +void ErrorReporter::warning(SourceLocation const& _location, string const& _description) +{ + error(Error::Type::Warning, _location, _description); +} + +void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description) +{ + auto err = make_shared<Error>(_type); + *err << + errinfo_sourceLocation(_location) << + errinfo_comment(_description); + + m_errorList.push_back(err); +} + +void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + auto err = make_shared<Error>(_type); + *err << + errinfo_sourceLocation(_location) << + errinfo_secondarySourceLocation(_secondaryLocation) << + errinfo_comment(_description); + + m_errorList.push_back(err); +} + + +void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description) +{ + error(_type, _location, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} + +ErrorList const& ErrorReporter::errors() const +{ + return m_errorList; +} + +void ErrorReporter::clear() +{ + m_errorList.clear(); +} + +void ErrorReporter::declarationError(SourceLocation const& _location, SecondarySourceLocation const&_secondaryLocation, string const& _description) +{ + error( + Error::Type::DeclarationError, + _location, + _secondaryLocation, + _description + ); +} + +void ErrorReporter::declarationError(SourceLocation const& _location, string const& _description) +{ + error( + Error::Type::DeclarationError, + _location, + _description + ); +} + +void ErrorReporter::fatalDeclarationError(SourceLocation const& _location, std::string const& _description) +{ + fatalError( + Error::Type::DeclarationError, + _location, + _description); +} + +void ErrorReporter::parserError(SourceLocation const& _location, string const& _description) +{ + error( + Error::Type::ParserError, + _location, + _description + ); +} + +void ErrorReporter::fatalParserError(SourceLocation const& _location, string const& _description) +{ + fatalError( + Error::Type::ParserError, + _location, + _description + ); +} + +void ErrorReporter::syntaxError(SourceLocation const& _location, string const& _description) +{ + error( + Error::Type::SyntaxError, + _location, + _description + ); +} + +void ErrorReporter::typeError(SourceLocation const& _location, string const& _description) +{ + error( + Error::Type::TypeError, + _location, + _description + ); +} + + +void ErrorReporter::fatalTypeError(SourceLocation const& _location, string const& _description) +{ + fatalError(Error::Type::TypeError, + _location, + _description + ); +} + +void ErrorReporter::docstringParsingError(string const& _description) +{ + error( + Error::Type::DocstringParsingError, + SourceLocation(), + _description + ); +} diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h new file mode 100644 index 00000000..e5605d24 --- /dev/null +++ b/libsolidity/interface/ErrorReporter.h @@ -0,0 +1,102 @@ +/* + 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 Rhett <roadriverrail@gmail.com> + * @date 2017 + * Error reporting helper class. + */ + +#pragma once + +#include <libsolidity/interface/Exceptions.h> +#include <libevmasm/SourceLocation.h> + +namespace dev +{ +namespace solidity +{ + +class ASTNode; + +class ErrorReporter +{ +public: + + ErrorReporter(ErrorList& _errors): + m_errorList(_errors) { } + + ErrorReporter& operator=(ErrorReporter const& _errorReporter); + + void warning(std::string const& _description = std::string()); + + void warning( + SourceLocation const& _location = SourceLocation(), + std::string const& _description = std::string() + ); + + void error( + Error::Type _type, + SourceLocation const& _location = SourceLocation(), + std::string const& _description = std::string() + ); + + void declarationError( + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(), + std::string const& _description = std::string() + ); + + void declarationError( + SourceLocation const& _location, + std::string const& _description = std::string() + ); + + void fatalDeclarationError(SourceLocation const& _location, std::string const& _description); + + void parserError(SourceLocation const& _location, std::string const& _description); + + void fatalParserError(SourceLocation const& _location, std::string const& _description); + + void syntaxError(SourceLocation const& _location, std::string const& _description); + + void typeError(SourceLocation const& _location, std::string const& _description); + + void fatalTypeError(SourceLocation const& _location, std::string const& _description); + + void docstringParsingError(std::string const& _location); + + ErrorList const& errors() const; + + void clear(); + +private: + void error(Error::Type _type, + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description = std::string()); + + void fatalError(Error::Type _type, + SourceLocation const& _location = SourceLocation(), + std::string const& _description = std::string()); + + ErrorList& m_errorList; +}; + + +} +} + diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index c09180de..9f2a2d06 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -21,7 +21,6 @@ */ #include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/Utils.h> using namespace std; using namespace dev; @@ -47,9 +46,6 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip case Type::TypeError: m_typeName = "TypeError"; break; - case Type::Why3TranslatorError: - m_typeName = "Why3TranslatorError"; - break; case Type::Warning: m_typeName = "Warning"; break; diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h index 0803d8cc..09301b10 100644 --- a/libsolidity/interface/Exceptions.h +++ b/libsolidity/interface/Exceptions.h @@ -25,6 +25,7 @@ #include <string> #include <utility> #include <libdevcore/Exceptions.h> +#include <libdevcore/Assertions.h> #include <libevmasm/SourceLocation.h> namespace dev @@ -39,6 +40,16 @@ struct InternalCompilerError: virtual Exception {}; struct FatalError: virtual Exception {}; struct UnimplementedFeatureError: virtual Exception{}; +/// Assertion that throws an InternalCompilerError containing the given description if it is not met. +#define solAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) + +#define solUnimplementedAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION) + +#define solUnimplemented(DESCRIPTION) \ + solUnimplementedAssert(false, DESCRIPTION) + class Error: virtual public Exception { public: @@ -49,7 +60,6 @@ public: ParserError, TypeError, SyntaxError, - Why3TranslatorError, Warning }; diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp deleted file mode 100644 index 6c1bb0c4..00000000 --- a/libsolidity/interface/InterfaceHandler.cpp +++ /dev/null @@ -1,197 +0,0 @@ - -#include <libsolidity/interface/InterfaceHandler.h> -#include <boost/range/irange.hpp> -#include <libsolidity/ast/AST.h> -#include <libsolidity/interface/CompilerStack.h> - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -Json::Value InterfaceHandler::documentation( - ContractDefinition const& _contractDef, - DocumentationType _type -) -{ - switch(_type) - { - case DocumentationType::NatspecUser: - return userDocumentation(_contractDef); - case DocumentationType::NatspecDev: - return devDocumentation(_contractDef); - case DocumentationType::ABIInterface: - return abiInterface(_contractDef); - } - - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); -} - -Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDef) -{ - Json::Value abi(Json::arrayValue); - - for (auto it: _contractDef.interfaceFunctions()) - { - auto externalFunctionType = it.second->interfaceFunctionType(); - Json::Value method; - method["type"] = "function"; - method["name"] = it.second->declaration().name(); - method["constant"] = it.second->isConstant(); - method["payable"] = it.second->isPayable(); - method["inputs"] = formatTypeList( - externalFunctionType->parameterNames(), - externalFunctionType->parameterTypes(), - _contractDef.isLibrary() - ); - method["outputs"] = formatTypeList( - externalFunctionType->returnParameterNames(), - externalFunctionType->returnParameterTypes(), - _contractDef.isLibrary() - ); - abi.append(method); - } - if (_contractDef.constructor()) - { - Json::Value method; - method["type"] = "constructor"; - auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); - solAssert(!!externalFunction, ""); - method["payable"] = externalFunction->isPayable(); - method["inputs"] = formatTypeList( - externalFunction->parameterNames(), - externalFunction->parameterTypes(), - _contractDef.isLibrary() - ); - abi.append(method); - } - if (_contractDef.fallbackFunction()) - { - auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); - solAssert(!!externalFunctionType, ""); - Json::Value method; - method["type"] = "fallback"; - method["payable"] = externalFunctionType->isPayable(); - abi.append(method); - } - for (auto const& it: _contractDef.interfaceEvents()) - { - Json::Value event; - event["type"] = "event"; - event["name"] = it->name(); - event["anonymous"] = it->isAnonymous(); - Json::Value params(Json::arrayValue); - for (auto const& p: it->parameters()) - { - solAssert(!!p->annotation().type->interfaceType(false), ""); - Json::Value input; - input["name"] = p->name(); - input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false); - input["indexed"] = p->isIndexed(); - params.append(input); - } - event["inputs"] = params; - abi.append(event); - } - - return abi; -} - -Json::Value InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef) -{ - Json::Value doc; - Json::Value methods(Json::objectValue); - - for (auto const& it: _contractDef.interfaceFunctions()) - if (it.second->hasDeclaration()) - if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) - { - string value = extractDoc(f->annotation().docTags, "notice"); - if (!value.empty()) - { - Json::Value user; - // since @notice is the only user tag if missing function should not appear - user["notice"] = Json::Value(value); - methods[it.second->externalSignature()] = user; - } - } - doc["methods"] = methods; - - return doc; -} - -Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) -{ - Json::Value doc; - Json::Value methods(Json::objectValue); - - auto author = extractDoc(_contractDef.annotation().docTags, "author"); - if (!author.empty()) - doc["author"] = author; - auto title = extractDoc(_contractDef.annotation().docTags, "title"); - if (!title.empty()) - doc["title"] = title; - - 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; - - if (!method.empty()) - // add the function, only if we have any documentation to add - methods[it.second->externalSignature()] = method; - } - } - doc["methods"] = methods; - - return doc; -} - -Json::Value InterfaceHandler::formatTypeList( - vector<string> const& _names, - vector<TypePointer> const& _types, - bool _forLibrary -) -{ - Json::Value params(Json::arrayValue); - solAssert(_names.size() == _types.size(), "Names and types vector size does not match"); - for (unsigned i = 0; i < _names.size(); ++i) - { - solAssert(_types[i], ""); - Json::Value param; - param["name"] = _names[i]; - param["type"] = _types[i]->canonicalName(_forLibrary); - params.append(param); - } - return params; -} - -string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name) -{ - string value; - auto range = _tags.equal_range(_name); - for (auto i = range.first; i != range.second; i++) - value += i->second.content; - return value; -} diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp new file mode 100644 index 00000000..70486e23 --- /dev/null +++ b/libsolidity/interface/Natspec.cpp @@ -0,0 +1,130 @@ +/* + 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 Lefteris <lefteris@ethdev.com> + * @date 2014 + * Takes the parsed AST and produces the Natspec documentation: + * https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format + * + * Can generally deal with JSON files + */ + +#include <libsolidity/interface/Natspec.h> +#include <boost/range/irange.hpp> +#include <libsolidity/ast/AST.h> +#include <libsolidity/interface/CompilerStack.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +Json::Value Natspec::documentation( + ContractDefinition const& _contractDef, + DocumentationType _type +) +{ + switch(_type) + { + case DocumentationType::NatspecUser: + return userDocumentation(_contractDef); + case DocumentationType::NatspecDev: + return devDocumentation(_contractDef); + } + + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); +} + +Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) +{ + Json::Value doc; + Json::Value methods(Json::objectValue); + + for (auto const& it: _contractDef.interfaceFunctions()) + if (it.second->hasDeclaration()) + if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) + { + string value = extractDoc(f->annotation().docTags, "notice"); + if (!value.empty()) + { + Json::Value user; + // since @notice is the only user tag if missing function should not appear + user["notice"] = Json::Value(value); + methods[it.second->externalSignature()] = user; + } + } + doc["methods"] = methods; + + return doc; +} + +Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) +{ + Json::Value doc; + Json::Value methods(Json::objectValue); + + auto author = extractDoc(_contractDef.annotation().docTags, "author"); + if (!author.empty()) + doc["author"] = author; + auto title = extractDoc(_contractDef.annotation().docTags, "title"); + if (!title.empty()) + doc["title"] = title; + + 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; + + if (!method.empty()) + // add the function, only if we have any documentation to add + methods[it.second->externalSignature()] = method; + } + } + doc["methods"] = methods; + + return doc; +} + +string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const& _name) +{ + string value; + auto range = _tags.equal_range(_name); + for (auto i = range.first; i != range.second; i++) + value += i->second.content; + return value; +} diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/Natspec.h index 56927d44..bec9acd2 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/Natspec.h @@ -17,8 +17,7 @@ /** * @author Lefteris <lefteris@ethdev.com> * @date 2014 - * Takes the parsed AST and produces the Natspec - * documentation and the ABI interface + * Takes the parsed AST and produces the Natspec documentation: * https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format * * Can generally deal with JSON files @@ -59,7 +58,7 @@ enum class CommentOwner Function }; -class InterfaceHandler +class Natspec { public: /// Get the given type of documentation @@ -71,10 +70,6 @@ public: ContractDefinition const& _contractDef, DocumentationType _type ); - /// Get the ABI Interface of the contract - /// @param _contractDef The contract definition - /// @return A JSONrepresentation of the contract's ABI Interface - static Json::Value abiInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition /// @return A JSON representation of the contract's user documentation diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 223cc15d..e677afc8 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -115,14 +115,6 @@ StringMap createSourceList(Json::Value const& _input) 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); @@ -273,11 +265,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); }; - bool success = false; - try { - success = m_compilerStack.compile(optimize, optimizeRuns, libraries); + m_compilerStack.compile(optimize, optimizeRuns, libraries); for (auto const& error: m_compilerStack.errors()) { @@ -367,22 +357,26 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (errors.size() > 0) output["errors"] = errors; + bool parsingSuccess = m_compilerStack.state() >= CompilerStack::State::ParsingSuccessful; + bool compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; + /// Inconsistent state - stop here to receive error reports from users - if (!success && (errors.size() == 0)) + if (!compilationSuccess && (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()) + for (auto const& source: parsingSuccess ? m_compilerStack.sourceNames() : vector<string>()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - sourceResult["legacyAST"] = ASTJsonConverter(m_compilerStack.ast(source), m_compilerStack.sourceIndices()).json(); + sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(source)); + sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(source)); output["sources"][source] = sourceResult; } Json::Value contractsOutput = Json::objectValue; - for (string const& contractName: success ? m_compilerStack.contractNames() : vector<string>()) + for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>()) { size_t colon = contractName.find(':'); solAssert(colon != string::npos, ""); @@ -391,10 +385,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - contractData["abi"] = m_compilerStack.metadata(contractName, DocumentationType::ABIInterface); + contractData["abi"] = m_compilerStack.contractABI(contractName); contractData["metadata"] = m_compilerStack.onChainMetadata(contractName); - contractData["userdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecUser); - contractData["devdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecDev); + contractData["userdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecUser); + contractData["devdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecDev); // EVM Json::Value evmData(Json::objectValue); @@ -403,7 +397,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) 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["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); evmData["bytecode"] = collectEVMObject( diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index 0d23f9c3..a35bfd29 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -24,7 +24,7 @@ #include <string> #include <libdevcore/CommonData.h> #include <libdevcore/Common.h> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/Exceptions.h> #include <solidity/BuildInfo.h> using namespace dev; diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 8e912126..0409de72 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -1,6 +1,7 @@ #include <libsolidity/parsing/DocStringParser.h> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/interface/Exceptions.h> #include <boost/range/irange.hpp> #include <boost/range/algorithm.hpp> @@ -51,9 +52,9 @@ string::const_iterator skipWhitespace( } -bool DocStringParser::parse(string const& _docString, ErrorList& _errors) +bool DocStringParser::parse(string const& _docString, ErrorReporter& _errorReporter) { - m_errors = &_errors; + m_errorReporter = &_errorReporter; m_errorsOccurred = false; m_lastTag = nullptr; @@ -172,8 +173,6 @@ void DocStringParser::newTag(string const& _tagName) void DocStringParser::appendError(string const& _description) { - auto err = make_shared<Error>(Error::Type::DocstringParsingError); - *err << errinfo_comment(_description); - m_errors->push_back(err); m_errorsOccurred = true; + m_errorReporter->docstringParsingError(_description); } diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h index c7f81c55..5f2819cc 100644 --- a/libsolidity/parsing/DocStringParser.h +++ b/libsolidity/parsing/DocStringParser.h @@ -23,7 +23,6 @@ #pragma once #include <string> -#include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/ASTAnnotations.h> namespace dev @@ -31,12 +30,14 @@ namespace dev namespace solidity { +class ErrorReporter; + class DocStringParser { public: /// Parse the given @a _docString and stores the parsed components internally. /// @returns false on error and appends the error to @a _errors. - bool parse(std::string const& _docString, ErrorList& _errors); + bool parse(std::string const& _docString, ErrorReporter& _errorReporter); std::multimap<std::string, DocTag> const& tags() const { return m_docTags; } @@ -62,7 +63,7 @@ private: /// Mapping tag name -> content. std::multimap<std::string, DocTag> m_docTags; DocTag* m_lastTag = nullptr; - ErrorList* m_errors = nullptr; + ErrorReporter* m_errorReporter = nullptr; bool m_errorsOccurred = false; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index b5130c8a..b0cf364e 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -26,8 +26,7 @@ #include <libsolidity/parsing/Parser.h> #include <libsolidity/parsing/Scanner.h> #include <libsolidity/inlineasm/AsmParser.h> -#include <libsolidity/interface/Exceptions.h> -#include <libsolidity/interface/InterfaceHandler.h> +#include <libsolidity/interface/ErrorReporter.h> using namespace std; @@ -95,7 +94,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) } catch (FatalError const&) { - if (m_errors.empty()) + if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. return nullptr; } @@ -324,11 +323,17 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN Token::Value token = m_scanner->currentToken(); if (token == Token::Const) { + if (result.isDeclaredConst) + parserError(string("Multiple \"constant\" specifiers.")); + result.isDeclaredConst = true; m_scanner->next(); } else if (m_scanner->currentToken() == Token::Payable) { + if (result.isPayable) + parserError(string("Multiple \"payable\" specifiers.")); + result.isPayable = true; m_scanner->next(); } @@ -348,8 +353,12 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN else if (Token::isVisibilitySpecifier(token)) { if (result.visibility != Declaration::Visibility::Default) - fatalParserError(string("Multiple visibility specifiers.")); - result.visibility = parseVisibilitySpecifier(token); + { + parserError(string("Multiple visibility specifiers.")); + m_scanner->next(); + } + else + result.visibility = parseVisibilitySpecifier(token); } else break; @@ -502,8 +511,12 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token)) { if (visibility != Declaration::Visibility::Default) - fatalParserError(string("Visibility already specified.")); - visibility = parseVisibilitySpecifier(token); + { + parserError(string("Visibility already specified.")); + m_scanner->next(); + } + else + visibility = parseVisibilitySpecifier(token); } else { @@ -514,14 +527,15 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token)) { if (location != VariableDeclaration::Location::Default) - fatalParserError(string("Location already specified.")); - if (!type) - fatalParserError(string("Location specifier needs explicit type name.")); - location = ( - token == Token::Memory ? - VariableDeclaration::Location::Memory : - VariableDeclaration::Location::Storage - ); + parserError(string("Location already specified.")); + else if (!type) + parserError(string("Location specifier needs explicit type name.")); + else + location = ( + token == Token::Memory ? + VariableDeclaration::Location::Memory : + VariableDeclaration::Location::Storage + ); } else break; @@ -703,7 +717,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) else if (token == Token::Var) { if (!_allowVar) - fatalParserError(string("Expected explicit type name.")); + parserError(string("Expected explicit type name.")); m_scanner->next(); } else if (token == Token::Function) @@ -866,7 +880,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con m_scanner->next(); } - assembly::Parser asmParser(m_errors); + assembly::Parser asmParser(m_errorReporter); shared_ptr<assembly::Block> block = asmParser.parse(m_scanner); nodeFactory.markEndPosition(); return nodeFactory.createNode<InlineAssembly>(_docString, block); @@ -1329,16 +1343,27 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars { // call({arg1 : 1, arg2 : 2 }) expectToken(Token::LBrace); + + bool first = true; while (m_scanner->currentToken() != Token::RBrace) { + if (!first) + expectToken(Token::Comma); + ret.second.push_back(expectIdentifierToken()); expectToken(Token::Colon); ret.first.push_back(parseExpression()); - if (m_scanner->currentToken() == Token::Comma) - expectToken(Token::Comma); - else - break; + if ( + m_scanner->currentToken() == Token::Comma && + m_scanner->peekNextToken() == Token::RBrace + ) + { + parserError("Unexpected trailing comma."); + m_scanner->next(); + } + + first = false; } expectToken(Token::RBrace); } @@ -1438,5 +1463,49 @@ ASTPointer<ParameterList> Parser::createEmptyParameterList() return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>()); } +string Parser::currentTokenName() +{ + Token::Value token = m_scanner->currentToken(); + if (Token::isElementaryTypeName(token)) //for the sake of accuracy in reporting + { + ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); + return elemTypeName.toString(); + } + else + return Token::name(token); +} + +Token::Value Parser::expectAssignmentOperator() +{ + Token::Value op = m_scanner->currentToken(); + if (!Token::isAssignmentOp(op)) + fatalParserError( + string("Expected assignment operator, got '") + + currentTokenName() + + string("'") + ); + m_scanner->next(); + return op; +} + +ASTPointer<ASTString> Parser::expectIdentifierToken() +{ + Token::Value id = m_scanner->currentToken(); + if (id != Token::Identifier) + fatalParserError( + string("Expected identifier, got '") + + currentTokenName() + + string("'") + ); + return getLiteralAndAdvance(); +} + +ASTPointer<ASTString> Parser::getLiteralAndAdvance() +{ + ASTPointer<ASTString> identifier = make_shared<ASTString>(m_scanner->currentLiteral()); + m_scanner->next(); + return identifier; +} + } } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 282617ab..19631c58 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -35,7 +35,7 @@ class Scanner; class Parser: public ParserBase { public: - Parser(ErrorList& _errors): ParserBase(_errors) {} + Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {} ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner); @@ -154,6 +154,11 @@ private: std::vector<ASTPointer<PrimaryExpression>> const& _path, std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices ); + + std::string currentTokenName(); + Token::Value expectAssignmentOperator(); + ASTPointer<ASTString> expectIdentifierToken(); + ASTPointer<ASTString> getLiteralAndAdvance(); ///@} /// Creates an empty ParameterList at the current location (used if parameters can be omitted). diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 87d47f4b..5657c2c0 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -22,6 +22,7 @@ #include <libsolidity/parsing/ParserBase.h> #include <libsolidity/parsing/Scanner.h> +#include <libsolidity/interface/ErrorReporter.h> using namespace std; using namespace dev; @@ -42,6 +43,26 @@ int ParserBase::endPosition() const return m_scanner->currentLocation().end; } +Token::Value ParserBase::currentToken() const +{ + return m_scanner->currentToken(); +} + +Token::Value ParserBase::peekNextToken() const +{ + return m_scanner->peekNextToken(); +} + +std::string ParserBase::currentLiteral() const +{ + return m_scanner->currentLiteral(); +} + +Token::Value ParserBase::advance() +{ + return m_scanner->next(); +} + void ParserBase::expectToken(Token::Value _value) { Token::Value tok = m_scanner->currentToken(); @@ -80,74 +101,12 @@ void ParserBase::expectToken(Token::Value _value) m_scanner->next(); } -Token::Value ParserBase::expectAssignmentOperator() -{ - Token::Value op = m_scanner->currentToken(); - if (!Token::isAssignmentOp(op)) - { - if (Token::isElementaryTypeName(op)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - fatalParserError( - string("Expected assignment operator, got '") + - elemTypeName.toString() + - string("'") - ); - } - else - fatalParserError( - string("Expected assignment operator, got '") + - string(Token::name(m_scanner->currentToken())) + - string("'") - ); - } - m_scanner->next(); - return op; -} - -ASTPointer<ASTString> ParserBase::expectIdentifierToken() -{ - Token::Value id = m_scanner->currentToken(); - if (id != Token::Identifier) - { - if (Token::isElementaryTypeName(id)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - fatalParserError( - string("Expected identifier, got '") + - elemTypeName.toString() + - string("'") - ); - } - else - fatalParserError( - string("Expected identifier, got '") + - string(Token::name(id)) + - string("'") - ); - } - return getLiteralAndAdvance(); -} - -ASTPointer<ASTString> ParserBase::getLiteralAndAdvance() -{ - ASTPointer<ASTString> identifier = make_shared<ASTString>(m_scanner->currentLiteral()); - m_scanner->next(); - return identifier; -} - void ParserBase::parserError(string const& _description) { - auto err = make_shared<Error>(Error::Type::ParserError); - *err << - errinfo_sourceLocation(SourceLocation(position(), position(), sourceName())) << - errinfo_comment(_description); - - m_errors.push_back(err); + m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description); } void ParserBase::fatalParserError(string const& _description) { - parserError(_description); - BOOST_THROW_EXCEPTION(FatalError()); + m_errorReporter.fatalParserError(SourceLocation(position(), position(), sourceName()), _description); } diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h index dfb7cab7..5b03ab5e 100644 --- a/libsolidity/parsing/ParserBase.h +++ b/libsolidity/parsing/ParserBase.h @@ -23,21 +23,20 @@ #pragma once #include <memory> -#include <libsolidity/interface/Exceptions.h> #include <libsolidity/parsing/Token.h> -#include <libsolidity/ast/ASTForward.h> namespace dev { namespace solidity { +class ErrorReporter; class Scanner; class ParserBase { public: - ParserBase(ErrorList& errors): m_errors(errors) {} + ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} std::shared_ptr<std::string const> const& sourceName() const; @@ -47,14 +46,14 @@ protected: /// End position of the current token int endPosition() const; - ///@{ ///@name Helper functions /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); - Token::Value expectAssignmentOperator(); - ASTPointer<ASTString> expectIdentifierToken(); - ASTPointer<ASTString> getLiteralAndAdvance(); + Token::Value currentToken() const; + Token::Value peekNextToken() const; + std::string currentLiteral() const; + Token::Value advance(); ///@} /// Creates a @ref ParserError and annotates it with the current position and the @@ -67,7 +66,7 @@ protected: std::shared_ptr<Scanner> m_scanner; /// The reference to the list of errors and warning to add errors/warnings during parsing - ErrorList& m_errors; + ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index 0e60fd0b..fdca23ea 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -52,7 +52,7 @@ #include <algorithm> #include <tuple> -#include <libsolidity/interface/Utils.h> +#include <libsolidity/interface/Exceptions.h> #include <libsolidity/parsing/Scanner.h> using namespace std; diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 9a557ebd..39c0eff9 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -43,7 +43,6 @@ #pragma once #include <libdevcore/Common.h> -#include <libsolidity/interface/Utils.h> #include <libsolidity/interface/Exceptions.h> #include <libdevcore/UndefMacros.h> |