diff options
Diffstat (limited to 'libsolidity')
59 files changed, 3453 insertions, 373 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 2342f0f9..f7c1a390 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -1,23 +1,20 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") - -aux_source_directory(analysis SRC_LIST) -aux_source_directory(ast SRC_LIST) -aux_source_directory(codegen SRC_LIST) -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" "../libjulia/backends/evm/*") - -include_directories(BEFORE ..) -add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) +file(GLOB_RECURSE sources "*.cpp" "../libjulia/*.cpp") +file(GLOB_RECURSE headers "*.h" "../libjulia/*.h") -eth_use(${EXECUTABLE} REQUIRED Dev::soldevcore Solidity::solevmasm) +find_package(Z3 QUIET) +if (${Z3_FOUND}) + include_directories(${Z3_INCLUDE_DIR}) + add_definitions(-DHAVE_Z3) + message("Z3 SMT solver FOUND.") +else() + message("Z3 SMT solver NOT found.") + list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/Z3Interface.cpp") +endif() -install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +add_library(solidity ${sources} ${headers}) +target_link_libraries(solidity PUBLIC evmasm devcore) +if (${Z3_FOUND}) + target_link_libraries(solidity PUBLIC ${Z3_LIBRARY}) +endif()
\ No newline at end of file diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 46477e1e..2f130414 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -57,6 +57,8 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) solAssert(m_localVarUseCount.empty(), ""); m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); m_constructor = _function.isConstructor(); + if (_function.stateMutability() == StateMutability::Pure) + m_errorReporter.warning(_function.location(), "Function is marked pure. Be careful, pureness is not enforced yet."); return true; } @@ -92,6 +94,17 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable) // This is not a no-op, the entry might pre-exist. m_localVarUseCount[&_variable] += 0; } + else if (_variable.isStateVariable()) + { + set<StructDefinition const*> structsSeen; + if (structureSizeEstimate(*_variable.type(), structsSeen) >= bigint(1) << 64) + m_errorReporter.warning( + _variable.location(), + "Variable covers a large part of storage and thus makes collisions likely. " + "Either use mappings or dynamic arrays and allow their size to be increased only " + "in small quantities per transaction." + ); + } return true; } @@ -160,3 +173,34 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly) return true; } + +bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen) +{ + switch (_type.category()) + { + case Type::Category::Array: + { + auto const& t = dynamic_cast<ArrayType const&>(_type); + return structureSizeEstimate(*t.baseType(), _structsSeen) * (t.isDynamicallySized() ? 1 : t.length()); + } + case Type::Category::Struct: + { + auto const& t = dynamic_cast<StructType const&>(_type); + bigint size = 1; + if (!_structsSeen.count(&t.structDefinition())) + { + _structsSeen.insert(&t.structDefinition()); + for (auto const& m: t.members(nullptr)) + size += structureSizeEstimate(*m.type, _structsSeen); + } + return size; + } + case Type::Category::Mapping: + { + return structureSizeEstimate(*dynamic_cast<MappingType const&>(_type).valueType(), _structsSeen); + } + default: + break; + } + return bigint(1); +} diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 21a487df..a3080b42 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -65,6 +65,9 @@ private: virtual bool visit(MemberAccess const& _memberAccess) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; + /// @returns the size of this type in storage, including all sub-types. + static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen); + ErrorReporter& m_errorReporter; /// Flag that indicates whether the current contract definition is a library. diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index bde0e616..d2571cd3 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -18,6 +18,7 @@ #include <libsolidity/analysis/SyntaxChecker.h> #include <memory> #include <libsolidity/ast/AST.h> +#include <libsolidity/ast/ExperimentalFeatures.h> #include <libsolidity/analysis/SemVerHandler.h> #include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> @@ -33,9 +34,10 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) return Error::containsOnlyWarnings(m_errorReporter.errors()); } -bool SyntaxChecker::visit(SourceUnit const&) +bool SyntaxChecker::visit(SourceUnit const& _sourceUnit) { m_versionPragmaFound = false; + m_sourceUnit = &_sourceUnit; return true; } @@ -57,15 +59,46 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) m_errorReporter.warning(_sourceUnit.location(), errorString); } + m_sourceUnit = nullptr; } 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") - m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); - else + if (_pragma.tokens()[0] != Token::Identifier) + m_errorReporter.syntaxError(_pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\""); + else if (_pragma.literals()[0] == "experimental") + { + solAssert(m_sourceUnit, ""); + vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + if (literals.size() == 0) + m_errorReporter.syntaxError( + _pragma.location(), + "Experimental feature name is missing." + ); + else if (literals.size() > 1) + m_errorReporter.syntaxError( + _pragma.location(), + "Stray arguments." + ); + else + { + string const literal = literals[0]; + if (literal.empty()) + m_errorReporter.syntaxError(_pragma.location(), "Empty experimental feature name is invalid."); + else if (!ExperimentalFeatureNames.count(literal)) + m_errorReporter.syntaxError(_pragma.location(), "Unsupported experimental feature name."); + else if (m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeatureNames.at(literal))) + m_errorReporter.syntaxError(_pragma.location(), "Duplicate experimental feature name."); + else + { + m_sourceUnit->annotation().experimentalFeatures.insert(ExperimentalFeatureNames.at(literal)); + m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments."); + } + } + } + else if (_pragma.literals()[0] == "solidity") { vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); @@ -81,6 +114,8 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) ); m_versionPragmaFound = true; } + else + m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); return true; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index fb5cc6d7..fa34bab3 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -77,6 +77,8 @@ private: bool m_versionPragmaFound = false; int m_inLoopDepth = 0; + + SourceUnit const* m_sourceUnit = nullptr; }; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 6852c13d..99f3c64c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -84,8 +84,13 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { if (!function->returnParameters().empty()) m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); - if (function->isDeclaredConst()) - m_errorReporter.typeError(function->location(), "Constructor cannot be defined as constant."); + if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + function->location(), + "Constructor must be payable or non-payable, but is \"" + + stateMutabilityToString(function->stateMutability()) + + "\"." + ); if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal) m_errorReporter.typeError(function->location(), "Constructor must be public or internal."); } @@ -104,8 +109,13 @@ bool TypeChecker::visit(ContractDefinition const& _contract) fallbackFunction = function; if (_contract.isLibrary()) m_errorReporter.typeError(fallbackFunction->location(), "Libraries cannot have fallback functions."); - if (fallbackFunction->isDeclaredConst()) - m_errorReporter.typeError(fallbackFunction->location(), "Fallback function cannot be declared constant."); + if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + function->location(), + "Fallback function must be payable or non-payable, but is \"" + + stateMutabilityToString(function->stateMutability()) + + "\"." + ); if (!fallbackFunction->parameters().empty()) m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters."); if (!fallbackFunction->returnParameters().empty()) @@ -277,21 +287,10 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr string const& name = function->name(); if (modifiers.count(name)) 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]) - { - FunctionType overridingType(*overriding); - if (!overridingType.hasEqualArgumentTypes(functionType)) - continue; - if ( - overriding->visibility() != function->visibility() || - overriding->isDeclaredConst() != function->isDeclaredConst() || - overriding->isPayable() != function->isPayable() || - overridingType != functionType - ) - m_errorReporter.typeError(overriding->location(), "Override changes extended function signature."); - } + checkFunctionOverride(*overriding, *function); + functions[name].push_back(function); } for (ModifierDefinition const* modifier: contract->functionModifiers()) @@ -308,6 +307,41 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr } } +void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super) +{ + FunctionType functionType(function); + FunctionType superType(super); + + if (!functionType.hasEqualArgumentTypes(superType)) + return; + + if (function.visibility() != super.visibility()) + overrideError(function, super, "Overriding function visibility differs."); + + else if (function.stateMutability() != super.stateMutability()) + overrideError( + function, + super, + "Overriding function changes state mutability from \"" + + stateMutabilityToString(super.stateMutability()) + + "\" to \"" + + stateMutabilityToString(function.stateMutability()) + + "\"." + ); + + else if (functionType != superType) + overrideError(function, super, "Overriding function return types differ."); +} + +void TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) +{ + m_errorReporter.typeError( + function.location(), + SecondarySourceLocation().append("Overriden function is here:", super.location()), + message + ); +} + void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract) { map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations; @@ -396,7 +430,11 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from."); auto const& arguments = _inheritance.arguments(); - TypePointers parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); + TypePointers parameterTypes; + if (base->contractKind() != ContractDefinition::ContractKind::Interface) + // Interfaces do not have constructors, so there are zero parameters. + parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); + if (!arguments.empty() && parameterTypes.size() != arguments.size()) { m_errorReporter.typeError( @@ -429,7 +467,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) _usingFor.libraryName().annotation().referencedDeclaration ); if (!library || !library->isLibrary()) - m_errorReporter.typeError(_usingFor.libraryName().location(), "Library name expected."); + m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected."); } bool TypeChecker::visit(StructDefinition const& _struct) @@ -475,8 +513,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.typeError(_function.location(), "Library functions cannot be payable."); if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface()) m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); - if (_function.isDeclaredConst()) - m_errorReporter.typeError(_function.location(), "Functions cannot be constant and payable at the same time."); } for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters()) { @@ -514,6 +550,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (_function.isConstructor()) m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces."); } + else if (m_scope->contractKind() == ContractDefinition::ContractKind::Library) + if (_function.isConstructor()) + m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in libraries."); if (_function.isImplemented()) _function.body().accept(*this); else if (_function.isConstructor()) @@ -1282,8 +1321,9 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) _operation.leftExpression().annotation().isPure && _operation.rightExpression().annotation().isPure; - if (_operation.getOperator() == Token::Exp) + if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL) { + string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift"; if ( leftType->category() == Type::Category::RationalNumber && rightType->category() != Type::Category::RationalNumber @@ -1297,7 +1337,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) )) m_errorReporter.warning( _operation.location(), - "Result of exponentiation has type " + commonType->toString() + " and thus " + "Result of " + operation + " has type " + commonType->toString() + " and thus " "might overflow. Silence this warning by converting the literal to the " "expected type." ); @@ -1518,6 +1558,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (!contract) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); + if (contract->contractKind() == ContractDefinition::ContractKind::Interface) + m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface."); if (!contract->annotation().unimplementedFunctions.empty()) m_errorReporter.typeError( _newExpression.location(), @@ -1949,4 +1991,3 @@ void TypeChecker::requireLValue(Expression const& _expression) else if (!_expression.annotation().isLValue) m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue."); } - diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index ee43d13a..f2e13765 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -62,6 +62,9 @@ private: /// arguments and that there is at most one constructor. void checkContractDuplicateFunctions(ContractDefinition const& _contract); void checkContractIllegalOverrides(ContractDefinition const& _contract); + /// Reports a type error with an appropiate message if overriden function signature differs. + void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); + void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); void checkContractAbstractFunctions(ContractDefinition const& _contract); void checkContractAbstractConstructors(ContractDefinition const& _contract); /// Checks that different functions with external visibility end up having different diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 1d68231e..7f4dea0e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -22,6 +22,7 @@ #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> @@ -188,7 +189,6 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::inter { if (!m_interfaceFunctionList) { - set<string> functionsSeen; set<string> signaturesSeen; m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>()); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) @@ -371,6 +371,15 @@ string FunctionDefinition::externalSignature() const return FunctionType(*this).externalSignature(); } +string FunctionDefinition::fullyQualifiedName() const +{ + auto const* contract = dynamic_cast<ContractDefinition const*>(scope()); + solAssert(contract, "Enclosing scope of function definition was not set."); + + auto fname = name().empty() ? "<fallback>" : name(); + return sourceUnitName() + ":" + contract->name() + "." + fname; +} + FunctionDefinitionAnnotation& FunctionDefinition::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 3e97286b..4592a190 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -28,6 +28,7 @@ #include <libsolidity/ast/Types.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/ASTAnnotations.h> +#include <libsolidity/ast/ASTEnums.h> #include <libevmasm/SourceLocation.h> #include <libevmasm/Instruction.h> @@ -152,6 +153,24 @@ public: /// Visibility ordered from restricted to unrestricted. enum class Visibility { Default, Private, Internal, Public, External }; + static std::string visibilityToString(Declaration::Visibility _visibility) + { + switch(_visibility) + { + case Declaration::Visibility::Public: + return "public"; + case Declaration::Visibility::Internal: + return "internal"; + case Declaration::Visibility::Private: + return "private"; + case Declaration::Visibility::External: + return "external"; + default: + solAssert(false, "Invalid visibility specifier."); + } + return std::string(); + } + Declaration( SourceLocation const& _location, ASTPointer<ASTString> const& _name, @@ -566,21 +585,19 @@ public: SourceLocation const& _location, ASTPointer<ASTString> const& _name, Declaration::Visibility _visibility, + StateMutability _stateMutability, bool _isConstructor, ASTPointer<ASTString> const& _documentation, ASTPointer<ParameterList> const& _parameters, - bool _isDeclaredConst, std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, ASTPointer<ParameterList> const& _returnParameters, - bool _isPayable, ASTPointer<Block> const& _body ): CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters), Documented(_documentation), ImplementationOptional(_body != nullptr), + m_stateMutability(_stateMutability), m_isConstructor(_isConstructor), - m_isDeclaredConst(_isDeclaredConst), - m_isPayable(_isPayable), m_functionModifiers(_modifiers), m_body(_body) {} @@ -588,13 +605,14 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; + StateMutability stateMutability() const { return m_stateMutability; } bool isConstructor() const { return m_isConstructor; } bool isFallback() const { return name().empty(); } - bool isDeclaredConst() const { return m_isDeclaredConst; } - bool isPayable() const { return m_isPayable; } + bool isPayable() const { return m_stateMutability == StateMutability::Payable; } 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 { solAssert(m_body, ""); return *m_body; } + std::string fullyQualifiedName() const; virtual bool isVisibleInContract() const override { return Declaration::isVisibleInContract() && !isConstructor() && !isFallback(); @@ -615,9 +633,8 @@ public: virtual FunctionDefinitionAnnotation& annotation() const override; private: + StateMutability m_stateMutability; bool m_isConstructor; - bool m_isDeclaredConst; - bool m_isPayable; std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers; ASTPointer<Block> m_body; }; @@ -819,11 +836,10 @@ private: */ class TypeName: public ASTNode { -public: +protected: explicit TypeName(SourceLocation const& _location): ASTNode(_location) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; +public: virtual TypeNameAnnotation& annotation() const override; }; @@ -877,11 +893,10 @@ public: ASTPointer<ParameterList> const& _parameterTypes, ASTPointer<ParameterList> const& _returnTypes, Declaration::Visibility _visibility, - bool _isDeclaredConst, - bool _isPayable + StateMutability _stateMutability ): TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), - m_visibility(_visibility), m_isDeclaredConst(_isDeclaredConst), m_isPayable(_isPayable) + m_visibility(_visibility), m_stateMutability(_stateMutability) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -895,15 +910,14 @@ public: { return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility; } - bool isDeclaredConst() const { return m_isDeclaredConst; } - bool isPayable() const { return m_isPayable; } + StateMutability stateMutability() const { return m_stateMutability; } + bool isPayable() const { return m_stateMutability == StateMutability::Payable; } private: ASTPointer<ParameterList> m_parameterTypes; ASTPointer<ParameterList> m_returnTypes; Declaration::Visibility m_visibility; - bool m_isDeclaredConst; - bool m_isPayable; + StateMutability m_stateMutability; }; /** diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index f757f03c..fd9efb4d 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -23,6 +23,7 @@ #pragma once #include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ExperimentalFeatures.h> #include <map> #include <memory> @@ -61,6 +62,8 @@ struct SourceUnitAnnotation: ASTAnnotation std::string path; /// The exported symbols (all global symbols). std::map<ASTString, std::vector<Declaration const*>> exportedSymbols; + /// Experimental features. + std::set<ExperimentalFeature> experimentalFeatures; }; struct ImportAnnotation: ASTAnnotation diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h new file mode 100644 index 00000000..5ba21907 --- /dev/null +++ b/libsolidity/ast/ASTEnums.h @@ -0,0 +1,54 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @date 2017 + * Enums for AST classes. + */ + +#pragma once + +#include <libsolidity/interface/Exceptions.h> + +#include <string> + +namespace dev +{ +namespace solidity +{ + +// How a function can mutate the EVM state. +enum class StateMutability { Pure, View, NonPayable, Payable }; + +inline std::string stateMutabilityToString(StateMutability const& _stateMutability) +{ + switch(_stateMutability) + { + case StateMutability::Pure: + return "pure"; + case StateMutability::View: + return "view"; + case StateMutability::NonPayable: + return "nonpayable"; + case StateMutability::Payable: + return "payable"; + default: + solAssert(false, "Unknown state mutability."); + } +} + +} +} diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index cfeeaa58..15735368 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -95,6 +95,5 @@ using ASTPointer = std::shared_ptr<T>; using ASTString = std::string; - } } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index e4a602cb..afc53bfe 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -15,8 +15,7 @@ along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** - * @author Lefteris <lefteris@ethdev.com> - * @date 2015 + * @date 2017 * Converts the AST into json format */ @@ -81,28 +80,30 @@ void ASTJsonConverter::setJsonNode( (_nodeType == "InlineAssembly") || (_nodeType == "Throw") ) - { - Json::Value children(Json::arrayValue); - m_currentValue["children"] = children; - } + m_currentValue["children"] = Json::arrayValue; 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.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 (!m_currentValue["children"].isArray()) + m_currentValue["children"] = Json::arrayValue; + appendMove(m_currentValue["children"], std::move(e.second)); + } if (e.second.isArray()) for (auto& child: e.second) if (!child.isNull()) - m_currentValue["children"].append(std::move(child)); + { + if (!m_currentValue["children"].isArray()) + m_currentValue["children"] = Json::arrayValue; + appendMove(m_currentValue["children"], std::move(child)); + } } else { @@ -147,7 +148,7 @@ Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr<std::vector<Type { Json::Value arguments(Json::arrayValue); for (auto const& tp: *_tps) - arguments.append(typePointerToJson(tp)); + appendMove(arguments, typePointerToJson(tp)); return arguments; } else @@ -186,7 +187,7 @@ void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node) _stream << toJson(_node); } -Json::Value ASTJsonConverter::toJson(ASTNode const& _node) +Json::Value&& ASTJsonConverter::toJson(ASTNode const& _node) { _node.accept(*this); return std::move(m_currentValue); @@ -285,7 +286,7 @@ bool ASTJsonConverter::visit(StructDefinition const& _node) { setJsonNode(_node, "StructDefinition", { make_pair("name", _node.name()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("canonicalName", _node.annotation().canonicalName), make_pair("members", toJson(_node.members())), make_pair("scope", idOrNull(_node.scope())) @@ -323,9 +324,11 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) { std::vector<pair<string, Json::Value>> attributes = { make_pair("name", _node.name()), - make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), + // FIXME: remove with next breaking release + make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View), make_pair("payable", _node.isPayable()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("isConstructor", _node.isConstructor()), make_pair("returnParameters", toJson(*_node.returnParameterList())), @@ -346,7 +349,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) 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", Declaration::visibilityToString(_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)) @@ -361,7 +364,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node) { setJsonNode(_node, "ModifierDefinition", { make_pair("name", _node.name()), - make_pair("visibility", visibility(_node.visibility())), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("body", toJson(_node.body())) }); @@ -377,12 +380,6 @@ bool ASTJsonConverter::visit(ModifierInvocation const& _node) return false; } -bool ASTJsonConverter::visit(TypeName const&) -{ - solAssert(false, "AST node of abstract type used."); - return false; -} - bool ASTJsonConverter::visit(EventDefinition const& _node) { m_inEvent = true; @@ -418,8 +415,10 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node) { setJsonNode(_node, "FunctionTypeName", { make_pair("payable", _node.isPayable()), - make_pair("visibility", visibility(_node.visibility())), - make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()), + make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), + // FIXME: remove with next breaking release + make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View), make_pair("parameterTypes", toJson(*_node.parameterTypeList())), make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) @@ -545,7 +544,7 @@ bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node) { Json::Value varDecs(Json::arrayValue); for (auto const& v: _node.annotation().assignments) - varDecs.append(idOrNull(v)); + appendMove(varDecs, idOrNull(v)); setJsonNode(_node, "VariableDeclarationStatement", { make_pair("assignments", std::move(varDecs)), make_pair("declarations", toJson(_node.declarations())), @@ -730,23 +729,6 @@ void ASTJsonConverter::endVisit(EventDefinition const&) m_inEvent = false; } -string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility) -{ - switch (_visibility) - { - case Declaration::Visibility::Private: - return "private"; - case Declaration::Visibility::Internal: - return "internal"; - case Declaration::Visibility::Public: - return "public"; - case Declaration::Visibility::External: - return "external"; - default: - solAssert(false, "Unknown declaration visibility."); - } -} - string ASTJsonConverter::location(VariableDeclaration::Location _location) { switch (_location) diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 27114c2a..60c660c1 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -49,13 +49,16 @@ public: ); /// Output the json representation of the AST to _stream. void print(std::ostream& _stream, ASTNode const& _node); - Json::Value toJson(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); + if (n) + appendMove(ret, toJson(*n)); + else + ret.append(Json::nullValue); return ret; } bool visit(SourceUnit const& _node) override; @@ -73,7 +76,6 @@ public: bool visit(ModifierDefinition const& _node) override; bool visit(ModifierInvocation const& _node) override; bool visit(EventDefinition const& _node) override; - bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(FunctionTypeName const& _node) override; @@ -119,7 +121,7 @@ private: ); std::string sourceLocationToString(SourceLocation const& _location) const; std::string namePathToString(std::vector<ASTString> const& _namePath) const; - Json::Value idOrNull(ASTNode const* _pt) + static Json::Value idOrNull(ASTNode const* _pt) { return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue; } @@ -128,19 +130,18 @@ private: 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); - int nodeId(ASTNode const& _node) + static int nodeId(ASTNode const& _node) { return _node.id(); } template<class Container> - Json::Value getContainerIds(Container const& container) + static Json::Value getContainerIds(Container const& container) { Json::Value tmp(Json::arrayValue); for (auto const& element: container) @@ -156,6 +157,12 @@ private: std::vector<std::pair<std::string, Json::Value>> &_attributes, ExpressionAnnotation const& _annotation ); + static void appendMove(Json::Value& _array, Json::Value&& _value) + { + solAssert(_array.isArray(), ""); + _array.append(std::move(_value)); + } + bool m_legacy = false; ///< if true, use legacy format bool m_inEvent = false; ///< whether we are currently inside an event or not Json::Value m_currentValue; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 23eb3b64..392179ef 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -105,7 +105,7 @@ bool ASTPrinter::visit(FunctionDefinition const& _node) { writeLine("FunctionDefinition \"" + _node.name() + "\"" + (_node.isPublic() ? " - public" : "") + - (_node.isDeclaredConst() ? " - const" : "")); + (_node.stateMutability() == StateMutability::View ? " - const" : "")); printSourcePart(_node); return goDeeper(); } @@ -143,13 +143,6 @@ bool ASTPrinter::visit(EventDefinition const& _node) return goDeeper(); } -bool ASTPrinter::visit(TypeName const& _node) -{ - writeLine("TypeName"); - printSourcePart(_node); - return goDeeper(); -} - bool ASTPrinter::visit(ElementaryTypeName const& _node) { writeLine(string("ElementaryTypeName ") + _node.typeName().toString()); @@ -434,11 +427,6 @@ void ASTPrinter::endVisit(EventDefinition const&) m_indentation--; } -void ASTPrinter::endVisit(TypeName const&) -{ - m_indentation--; -} - void ASTPrinter::endVisit(ElementaryTypeName const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index 4a37f17f..d6897dfd 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -60,7 +60,6 @@ public: bool visit(ModifierDefinition const& _node) override; bool visit(ModifierInvocation const& _node) override; bool visit(EventDefinition const& _node) override; - bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(FunctionTypeName const& _node) override; @@ -104,7 +103,6 @@ public: 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; @@ -146,7 +144,7 @@ private: std::string m_source; ASTNode const* m_ast; GasEstimator::ASTGasConsumption m_gasCosts; - std::ostream* m_ostream; + std::ostream* m_ostream = nullptr; }; } diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 20be634b..b726d592 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -58,7 +58,6 @@ public: virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); } virtual bool visit(ModifierInvocation& _node) { return visitNode(_node); } virtual bool visit(EventDefinition& _node) { return visitNode(_node); } - virtual bool visit(TypeName& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); } @@ -104,7 +103,6 @@ public: virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); } virtual void endVisit(ModifierInvocation& _node) { endVisitNode(_node); } virtual void endVisit(EventDefinition& _node) { endVisitNode(_node); } - virtual void endVisit(TypeName& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); } @@ -162,7 +160,6 @@ public: virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); } virtual bool visit(ModifierInvocation const& _node) { return visitNode(_node); } virtual bool visit(EventDefinition const& _node) { return visitNode(_node); } - virtual bool visit(TypeName const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); } virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); } @@ -208,7 +205,6 @@ public: virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(ModifierInvocation const& _node) { endVisitNode(_node); } virtual void endVisit(EventDefinition const& _node) { endVisitNode(_node); } - virtual void endVisit(TypeName const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 7c1c64b0..904d9ff6 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -291,18 +291,6 @@ void EventDefinition::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } -void TypeName::accept(ASTVisitor& _visitor) -{ - _visitor.visit(*this); - _visitor.endVisit(*this); -} - -void TypeName::accept(ASTConstVisitor& _visitor) const -{ - _visitor.visit(*this); - _visitor.endVisit(*this); -} - void ElementaryTypeName::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h new file mode 100644 index 00000000..2c089671 --- /dev/null +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -0,0 +1,53 @@ +/* + 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/>. +*/ +/** + * List of experimental features. + */ + +#pragma once + +#include <map> + +namespace dev +{ +namespace solidity +{ + +enum class ExperimentalFeature +{ + SMTChecker, + ABIEncoderV2, // new ABI encoder that makes use of JULIA + Test, + TestOnlyAnalysis +}; + +static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis = +{ + { ExperimentalFeature::SMTChecker, true }, + { ExperimentalFeature::TestOnlyAnalysis, true }, +}; + +static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames = +{ + { "SMTChecker", ExperimentalFeature::SMTChecker }, + { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 }, + { "__test", ExperimentalFeature::Test }, + { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis }, +}; + +} +} diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 56fdd508..5e61cdee 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -477,8 +477,8 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons if (isAddress()) return { {"balance", make_shared<IntegerType >(256)}, - {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, false, true)}, - {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)}, + {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, StateMutability::Payable)}, + {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, StateMutability::Payable)}, {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} @@ -525,19 +525,20 @@ bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const { - // "delete" is ok for all fixed types - if (_operator == Token::Delete) + switch(_operator) + { + case Token::Delete: + // "delete" is ok for all fixed types return make_shared<TupleType>(); - // for fixed, we allow +, -, ++ and -- - else if ( - _operator == Token::Add || - _operator == Token::Sub || - _operator == Token::Inc || - _operator == Token::Dec - ) + case Token::Add: + case Token::Sub: + case Token::Inc: + case Token::Dec: + // for fixed, we allow +, -, ++ and -- return shared_from_this(); - else + default: return TypePointer(); + } } bool FixedPointType::operator==(Type const& _other) const @@ -738,18 +739,18 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() == Category::Integer) { - auto targetType = dynamic_cast<IntegerType const*>(&_convertTo); if (m_value == rational(0)) return true; if (isFractional()) return false; - int forSignBit = (targetType->isSigned() ? 1 : 0); + IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo); + int forSignBit = (targetType.isSigned() ? 1 : 0); if (m_value > rational(0)) { - if (m_value.numerator() <= (u256(-1) >> (256 - targetType->numBits() + forSignBit))) + if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit))) return true; } - else if (targetType->isSigned() && -m_value.numerator() <= (u256(1) << (targetType->numBits() - forSignBit))) + else if (targetType.isSigned() && -m_value.numerator() <= (u256(1) << (targetType.numBits() - forSignBit))) return true; return false; } @@ -1408,6 +1409,11 @@ unsigned ArrayType::calldataEncodedSize(bool _padded) const return unsigned(size); } +bool ArrayType::isDynamicallyEncoded() const +{ + return isDynamicallySized() || baseType()->isDynamicallyEncoded(); +} + u256 ArrayType::storageSize() const { if (isDynamicallySized()) @@ -1523,8 +1529,6 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const TypePointer baseExt = m_baseType->interfaceType(_inLibrary); if (!baseExt) return TypePointer(); - if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) - return TypePointer(); if (isDynamicallySized()) return make_shared<ArrayType>(DataLocation::Memory, baseExt); @@ -1710,6 +1714,11 @@ unsigned StructType::calldataEncodedSize(bool _padded) const return size; } +bool StructType::isDynamicallyEncoded() const +{ + solAssert(false, "Structs are not yet supported in the ABI."); +} + u256 StructType::memorySize() const { u256 size; @@ -1989,8 +1998,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): m_kind(_isInternal ? Kind::Internal : Kind::External), - m_isConstant(_function.isDeclaredConst()), - m_isPayable(_isInternal ? false : _function.isPayable()), + m_stateMutability(_function.stateMutability()), m_declaration(&_function) { TypePointers params; @@ -1998,6 +2006,9 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal TypePointers retParams; vector<string> retParamNames; + if (_isInternal && m_stateMutability == StateMutability::Payable) + m_stateMutability = StateMutability::NonPayable; + params.reserve(_function.parameters().size()); paramNames.reserve(_function.parameters().size()); for (ASTPointer<VariableDeclaration> const& var: _function.parameters()) @@ -2019,7 +2030,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal } FunctionType::FunctionType(VariableDeclaration const& _varDecl): - m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl) + m_kind(Kind::External), m_stateMutability(StateMutability::View), m_declaration(&_varDecl) { TypePointers paramTypes; vector<string> paramNames; @@ -2079,7 +2090,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } FunctionType::FunctionType(EventDefinition const& _event): - m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event) + m_kind(Kind::Event), m_stateMutability(StateMutability::View), m_declaration(&_event) { TypePointers params; vector<string> paramNames; @@ -2096,14 +2107,10 @@ FunctionType::FunctionType(EventDefinition const& _event): FunctionType::FunctionType(FunctionTypeName const& _typeName): m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal), - m_isConstant(_typeName.isDeclaredConst()), - m_isPayable(_typeName.isPayable()) + m_stateMutability(_typeName.stateMutability()) { if (_typeName.isPayable()) - { solAssert(m_kind == Kind::External, "Internal payable function type used."); - solAssert(!m_isConstant, "Payable constant function"); - } for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); @@ -2131,7 +2138,9 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c FunctionDefinition const* constructor = _contract.constructor(); TypePointers parameters; strings parameterNames; - bool payable = false; + StateMutability stateMutability = StateMutability::NonPayable; + + solAssert(_contract.contractKind() != ContractDefinition::ContractKind::Interface, ""); if (constructor) { @@ -2140,8 +2149,10 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c parameterNames.push_back(var->name()); parameters.push_back(var->annotation().type); } - payable = constructor->isPayable(); + if (constructor->isPayable()) + stateMutability = StateMutability::Payable; } + return make_shared<FunctionType>( parameters, TypePointers{make_shared<ContractType>(_contract)}, @@ -2150,8 +2161,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c Kind::Creation, false, nullptr, - false, - payable + stateMutability ); } @@ -2208,8 +2218,7 @@ string FunctionType::identifier() const case Kind::Require: id += "require";break; default: solAssert(false, "Unknown function location."); break; } - if (isConstant()) - id += "_constant"; + id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); if (m_gasSet) id += "gas"; @@ -2224,23 +2233,21 @@ bool FunctionType::operator==(Type const& _other) const { if (_other.category() != category()) return false; - FunctionType const& other = dynamic_cast<FunctionType const&>(_other); - if (m_kind != other.m_kind) - return false; - if (m_isConstant != other.isConstant()) + FunctionType const& other = dynamic_cast<FunctionType const&>(_other); + if ( + m_kind != other.m_kind || + m_stateMutability != other.stateMutability() || + m_parameterTypes.size() != other.m_parameterTypes.size() || + m_returnParameterTypes.size() != other.m_returnParameterTypes.size() + ) return false; - if (m_parameterTypes.size() != other.m_parameterTypes.size() || - m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) - return false; auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; - - if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), - other.m_parameterTypes.cbegin(), typeCompare)) - return false; - if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), - other.m_returnParameterTypes.cbegin(), typeCompare)) + if ( + !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) || + !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare) + ) return false; //@todo this is ugly, but cannot be prevented right now if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet) @@ -2292,10 +2299,8 @@ string FunctionType::toString(bool _short) const for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); name += ")"; - if (m_isConstant) - name += " constant"; - if (m_isPayable) - name += " payable"; + if (m_stateMutability != StateMutability::NonPayable) + name += " " + stateMutabilityToString(m_stateMutability); if (m_kind == Kind::External) name += " external"; if (!m_returnParameterTypes.empty()) @@ -2344,14 +2349,26 @@ unsigned FunctionType::sizeOnStack() const } unsigned size = 0; - if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall) + + switch(kind) + { + case Kind::External: + case Kind::CallCode: + case Kind::DelegateCall: size = 2; - else if (kind == Kind::BareCall || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) - size = 1; - else if (kind == Kind::Internal) - size = 1; - else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush) + break; + case Kind::BareCall: + case Kind::BareCallCode: + case Kind::BareDelegateCall: + case Kind::Internal: + case Kind::ArrayPush: + case Kind::ByteArrayPush: size = 1; + break; + default: + break; + } + if (m_gasSet) size++; if (m_valueSet) @@ -2389,10 +2406,14 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const return FunctionTypePointer(); return make_shared<FunctionType>( - paramTypes, retParamTypes, - m_parameterNames, m_returnParameterNames, - m_kind, m_arbitraryParameters, - m_declaration, m_isConstant, m_isPayable + paramTypes, + retParamTypes, + m_parameterNames, + m_returnParameterNames, + m_kind, + m_arbitraryParameters, + m_declaration, + m_stateMutability ); } @@ -2409,7 +2430,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con MemberList::MemberMap members; if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall) { - if (m_isPayable) + if (isPayable()) members.push_back(MemberList::Member( "value", make_shared<FunctionType>( @@ -2420,8 +2441,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con Kind::SetValue, false, nullptr, - false, - false, + StateMutability::NonPayable, m_gasSet, m_valueSet ) @@ -2438,8 +2458,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con Kind::SetGas, false, nullptr, - false, - false, + StateMutability::NonPayable, m_gasSet, m_valueSet ) @@ -2575,8 +2594,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_kind, m_arbitraryParameters, m_declaration, - m_isConstant, - m_isPayable, + m_stateMutability, m_gasSet || _setGas, m_valueSet || _setValue, m_bound @@ -2625,8 +2643,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) kind, m_arbitraryParameters, m_declaration, - m_isConstant, - m_isPayable, + m_stateMutability, m_gasSet, m_valueSet, _bound diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 5d2bdca0..ce2d3bf8 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -24,6 +24,7 @@ #include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ASTEnums.h> #include <libsolidity/parsing/Token.h> #include <libdevcore/Common.h> @@ -187,6 +188,7 @@ public: /// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type, /// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded /// in calldata. + /// @note: This should actually not be called on types, where isDynamicallyEncoded returns true. /// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes. virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; } /// @returns the size of this data type in bytes when stored in memory. For memory-reference @@ -194,8 +196,10 @@ public: virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); } /// Convenience version of @see calldataEncodedSize(bool) unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } - /// @returns true if the type is dynamically encoded in calldata + /// @returns true if the type is a dynamic array virtual bool isDynamicallySized() const { return false; } + /// @returns true if the type is dynamically encoded in the ABI + virtual bool isDynamicallyEncoded() const { return false; } /// @returns the number of storage slots required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 storageSize() const { return 1; } @@ -609,6 +613,7 @@ public: virtual bool operator==(const Type& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } + virtual bool isDynamicallyEncoded() const override; virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } virtual unsigned sizeOnStack() const override; @@ -723,6 +728,7 @@ public: virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; + virtual bool isDynamicallyEncoded() const override; u256 memorySize() const; virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return true; } @@ -884,8 +890,7 @@ public: strings const& _returnParameterTypes, Kind _kind = Kind::Internal, bool _arbitraryParameters = false, - bool _constant = false, - bool _payable = false + StateMutability _stateMutability = StateMutability::NonPayable ): FunctionType( parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), @@ -894,8 +899,7 @@ public: _kind, _arbitraryParameters, nullptr, - _constant, - _payable + _stateMutability ) { } @@ -912,8 +916,7 @@ public: Kind _kind = Kind::Internal, bool _arbitraryParameters = false, Declaration const* _declaration = nullptr, - bool _isConstant = false, - bool _isPayable = false, + StateMutability _stateMutability = StateMutability::NonPayable, bool _gasSet = false, bool _valueSet = false, bool _bound = false @@ -923,12 +926,11 @@ public: m_parameterNames(_parameterNames), m_returnParameterNames(_returnParameterNames), m_kind(_kind), + m_stateMutability(_stateMutability), m_arbitraryParameters(_arbitraryParameters), m_gasSet(_gasSet), m_valueSet(_valueSet), m_bound(_bound), - m_isConstant(_isConstant), - m_isPayable(_isPayable), m_declaration(_declaration) { solAssert( @@ -980,6 +982,7 @@ public: /// @returns true if the ABI is used for this call (only meaningful for external calls) bool isBareCall() const; Kind const& kind() const { return m_kind; } + StateMutability stateMutability() const { return m_stateMutability; } /// @returns the external signature of this function type given the function name std::string externalSignature() const; /// @returns the external identifier of this function (the hash of the signature). @@ -990,12 +993,11 @@ public: return *m_declaration; } bool hasDeclaration() const { return !!m_declaration; } - bool isConstant() const { return m_isConstant; } /// @returns true if the the result of this function only depends on its arguments /// and it does not modify the state. /// Currently, this will only return true for internal functions like keccak and ecrecover. bool isPure() const; - bool isPayable() const { return m_isPayable; } + bool isPayable() const { return m_stateMutability == StateMutability::Payable; } /// @return A shared pointer of an ASTString. /// Can contain a nullptr in which case indicates absence of documentation ASTPointer<ASTString> documentation() const; @@ -1028,13 +1030,12 @@ private: std::vector<std::string> m_parameterNames; std::vector<std::string> m_returnParameterNames; Kind const m_kind; + StateMutability m_stateMutability = StateMutability::NonPayable; /// true if the function takes an arbitrary number of arguments of arbitrary types bool const m_arbitraryParameters = false; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_valueSet = false; ///< true iff the value to be sent is on the stack bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn) - bool m_isConstant = false; - bool m_isPayable = false; Declaration const* m_declaration = nullptr; }; diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp new file mode 100644 index 00000000..a2938ed7 --- /dev/null +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -0,0 +1,1074 @@ +/* + 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 <chris@ethereum.org> + * @date 2017 + * Routines that generate JULIA code related to ABI encoding, decoding and type conversions. + */ + +#include <libsolidity/codegen/ABIFunctions.h> + +#include <libdevcore/Whiskers.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +ABIFunctions::~ABIFunctions() +{ + // This throws an exception and thus might cause immediate termination, but hey, + // it's a failed assertion anyway :-) + solAssert(m_requestedFunctions.empty(), "Forgot to call ``requestedFunctions()``."); +} + +string ABIFunctions::tupleEncoder( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _encodeAsLibraryTypes +) +{ + // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> + + solAssert(!_givenTypes.empty(), ""); + size_t const headSize_ = headSize(_targetTypes); + + Whiskers encoder(R"( + { + let tail := add($headStart, <headSize>) + <encodeElements> + <deepestStackElement> := tail + } + )"); + encoder("headSize", to_string(headSize_)); + string encodeElements; + size_t headPos = 0; + size_t stackPos = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + solAssert(_givenTypes[i], ""); + solAssert(_targetTypes[i], ""); + size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); + string valueNames = ""; + for (size_t j = 0; j < sizeOnStack; j++) + valueNames += "$value" + to_string(stackPos++) + ", "; + bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl( + dynamic ? + string(R"( + mstore(add($headStart, <pos>), sub(tail, $headStart)) + tail := <abiEncode>(<values> tail) + )") : + string(R"( + <abiEncode>(<values> add($headStart, <pos>)) + )") + ); + elementTempl("values", valueNames); + elementTempl("pos", to_string(headPos)); + elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false)); + encodeElements += elementTempl.render(); + headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); + } + solAssert(headPos == headSize_, ""); + encoder("encodeElements", encodeElements); + encoder("deepestStackElement", stackPos > 0 ? "$value0" : "$headStart"); + + return encoder.render(); +} + +string ABIFunctions::requestedFunctions() +{ + string result; + for (auto const& f: m_requestedFunctions) + result += f.second; + m_requestedFunctions.clear(); + return result; +} + +string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) +{ + string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function <functionName>(value) -> cleaned { + <body> + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast<IntegerType const&>(_type); + if (type.numBits() == 256) + templ("body", "cleaned := value"); + else if (type.isSigned()) + templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); + else + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); + break; + } + case Type::Category::RationalNumber: + templ("body", "cleaned := value"); + break; + case Type::Category::Bool: + templ("body", "cleaned := iszero(iszero(value))"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + solAssert(false, "Array cleanup requested."); + break; + case Type::Category::Struct: + solAssert(false, "Struct cleanup requested."); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type); + if (type.numBytes() == 32) + templ("body", "cleaned := value"); + else if (type.numBytes() == 0) + templ("body", "cleaned := 0"); + else + { + size_t numBits = type.numBytes() * 8; + u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); + } + break; + } + case Type::Category::Contract: + templ("body", "cleaned := " + cleanupFunction(IntegerType(0, IntegerType::Modifier::Address)) + "(value)"); + break; + case Type::Category::Enum: + { + size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); + solAssert(members > 0, "empty enum should have caused a parser error."); + Whiskers w("switch lt(value, <members>) case 0 { <failure> } cleaned := value"); + w("members", to_string(members)); + if (_revertOnFailure) + w("failure", "revert(0, 0)"); + else + w("failure", "invalid()"); + templ("body", w.render()); + break; + } + default: + solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) +{ + string functionName = + "convert_" + + _from.identifier() + + "_to_" + + _to.identifier(); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function <functionName>(value) -> converted { + <body> + } + )"); + templ("functionName", functionName); + string body; + auto toCategory = _to.category(); + auto fromCategory = _from.category(); + switch (fromCategory) + { + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Contract: + { + if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from)) + solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); + if (toCategory == Type::Category::FixedBytes) + { + solAssert( + fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, + "Invalid conversion to FixedBytesType requested." + ); + FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to); + body = + Whiskers("converted := <shiftLeft>(<clean>(value))") + ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("clean", cleanupFunction(_from)) + .render(); + } + else if (toCategory == Type::Category::Enum) + { + solAssert(_from.mobileType(), ""); + body = + Whiskers("converted := <cleanEnum>(<cleanInt>(value))") + ("cleanEnum", cleanupFunction(_to, false)) + // "mobileType()" returns integer type for rational + ("cleanInt", cleanupFunction(*_from.mobileType())) + .render(); + } + else if (toCategory == Type::Category::FixedPoint) + { + solUnimplemented("Not yet implemented - FixedPointType."); + } + else + { + solAssert( + toCategory == Type::Category::Integer || + toCategory == Type::Category::Contract, + ""); + IntegerType const addressType(0, IntegerType::Modifier::Address); + IntegerType const& to = + toCategory == Type::Category::Integer ? + dynamic_cast<IntegerType const&>(_to) : + addressType; + + // Clean according to the "to" type, except if this is + // a widening conversion. + IntegerType const* cleanupType = &to; + if (fromCategory != Type::Category::RationalNumber) + { + IntegerType const& from = + fromCategory == Type::Category::Integer ? + dynamic_cast<IntegerType const&>(_from) : + addressType; + if (to.numBits() > from.numBits()) + cleanupType = &from; + } + body = + Whiskers("converted := <cleanInt>(value)") + ("cleanInt", cleanupFunction(*cleanupType)) + .render(); + } + break; + } + case Type::Category::Bool: + { + solAssert(_from == _to, "Invalid conversion for bool."); + body = + Whiskers("converted := <clean>(value)") + ("clean", cleanupFunction(_from)) + .render(); + break; + } + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + solUnimplementedAssert(false, "Array conversion not implemented."); + break; + case Type::Category::Struct: + solUnimplementedAssert(false, "Struct conversion not implemented."); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from); + if (toCategory == Type::Category::Integer) + body = + Whiskers("converted := <convert>(<shift>(value))") + ("shift", shiftRightFunction(256 - from.numBytes() * 8, false)) + ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) + .render(); + else + { + // clear for conversion to longer bytes + solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + body = + Whiskers("converted := <clean>(value)") + ("clean", cleanupFunction(from)) + .render(); + } + break; + } + case Type::Category::Function: + { + solAssert(false, "Conversion should not be called for function types."); + break; + } + case Type::Category::Enum: + { + solAssert(toCategory == Type::Category::Integer || _from == _to, ""); + EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from); + body = + Whiskers("converted := <clean>(value)") + ("clean", cleanupFunction(enumType)) + .render(); + break; + } + case Type::Category::Tuple: + { + solUnimplementedAssert(false, "Tuple conversion not implemented."); + break; + } + default: + solAssert(false, ""); + } + + solAssert(!body.empty(), ""); + templ("body", body); + return templ.render(); + }); +} + +string ABIFunctions::cleanupCombinedExternalFunctionIdFunction() +{ + string functionName = "cleanup_combined_external_function_id"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(addr_and_selector) -> cleaned { + cleaned := <clean>(addr_and_selector) + } + )") + ("functionName", functionName) + ("clean", cleanupFunction(FixedBytesType(24))) + .render(); + }); +} + +string ABIFunctions::combineExternalFunctionIdFunction() +{ + string functionName = "combine_external_function_id"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(addr, selector) -> combined { + combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff))) + } + )") + ("functionName", functionName) + ("shl32", shiftLeftFunction(32)) + ("shl64", shiftLeftFunction(64)) + .render(); + }); +} + +string ABIFunctions::abiEncodingFunction( + Type const& _from, + Type const& _to, + bool _encodeAsLibraryTypes, + bool _compacted +) +{ + solUnimplementedAssert( + _to.mobileType() && + _to.mobileType()->interfaceType(_encodeAsLibraryTypes) && + _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(), + "Encoding type \"" + _to.toString() + "\" not yet implemented." + ); + TypePointer toInterface = _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(); + Type const& to = *toInterface; + + if (_from.category() == Type::Category::StringLiteral) + return abiEncodingFunctionStringLiteral(_from, to, _encodeAsLibraryTypes); + else if (auto toArray = dynamic_cast<ArrayType const*>(&to)) + { + solAssert(_from.category() == Type::Category::Array, ""); + solAssert(to.dataStoredIn(DataLocation::Memory), ""); + ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from); + if (fromArray.location() == DataLocation::CallData) + return abiEncodingFunctionCalldataArray(fromArray, *toArray, _encodeAsLibraryTypes); + else if (!fromArray.isByteArray() && ( + fromArray.location() == DataLocation::Memory || + fromArray.baseType()->storageBytes() > 16 + )) + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _encodeAsLibraryTypes); + else if (fromArray.location() == DataLocation::Memory) + return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _encodeAsLibraryTypes); + else if (fromArray.location() == DataLocation::Storage) + return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _encodeAsLibraryTypes); + else + solAssert(false, ""); + } + else if (dynamic_cast<StructType const*>(&to)) + { + solUnimplementedAssert(false, "Structs not yet implemented."); + } + else if (_from.category() == Type::Category::Function) + return abiEncodingFunctionFunctionType( + dynamic_cast<FunctionType const&>(_from), + to, + _encodeAsLibraryTypes, + _compacted + ); + + solAssert(_from.sizeOnStack() == 1, ""); + solAssert(to.isValueType(), ""); + solAssert(to.calldataEncodedSize() == 32, ""); + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + return createFunction(functionName, [&]() { + solAssert(!to.isDynamicallyEncoded(), ""); + + Whiskers templ(R"( + function <functionName>(value, pos) { + mstore(pos, <cleanupConvert>) + } + )"); + templ("functionName", functionName); + + if (_from.dataStoredIn(DataLocation::Storage) && to.isValueType()) + { + // special case: convert storage reference type to value type - this is only + // possible for library calls where we just forward the storage reference + solAssert(_encodeAsLibraryTypes, ""); + solAssert(to == IntegerType(256), ""); + templ("cleanupConvert", "value"); + } + else + { + if (_from == to) + templ("cleanupConvert", cleanupFunction(_from) + "(value)"); + else + templ("cleanupConvert", conversionFunction(_from, to) + "(value)"); + } + return templ.render(); + }); +} + +string ABIFunctions::abiEncodingFunctionCalldataArray( + Type const& _from, + Type const& _to, + bool _encodeAsLibraryTypes +) +{ + solAssert(_to.isDynamicallySized(), ""); + solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); + solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); + auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from); + auto const& toArrayType = dynamic_cast<ArrayType const&>(_to); + + solAssert(fromArrayType.location() == DataLocation::CallData, ""); + + solAssert( + *fromArrayType.copyForLocation(DataLocation::Memory, true) == + *toArrayType.copyForLocation(DataLocation::Memory, true), + "" + ); + + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + return createFunction(functionName, [&]() { + solUnimplementedAssert(fromArrayType.isByteArray(), ""); + // TODO if this is not a byte array, we might just copy byte-by-byte anyway, + // because the encoding is position-independent, but we have to check that. + Whiskers templ(R"( + function <functionName>(start, length, pos) -> end { + <storeLength> // might update pos + <copyFun>(start, pos, length) + end := add(pos, <roundUpFun>(length)) + } + )"); + templ("storeLength", _to.isDynamicallySized() ? "mstore(pos, length) pos := add(pos, 0x20)" : ""); + templ("functionName", functionName); + templ("copyFun", copyToMemoryFunction(true)); + templ("roundUpFun", roundUpFunction()); + return templ.render(); + }); +} + +string ABIFunctions::abiEncodingFunctionSimpleArray( + ArrayType const& _from, + ArrayType const& _to, + bool _encodeAsLibraryTypes +) +{ + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + + solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); + solAssert(_from.length() == _to.length(), ""); + solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), ""); + solAssert(!_from.isByteArray(), ""); + solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, ""); + + return createFunction(functionName, [&]() { + bool dynamic = _to.isDynamicallyEncoded(); + bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); + bool inMemory = _from.dataStoredIn(DataLocation::Memory); + Whiskers templ( + dynamicBase ? + R"( + function <functionName>(value, pos) <return> { + let length := <lengthFun>(value) + <storeLength> // might update pos + let headStart := pos + let tail := add(pos, mul(length, 0x20)) + let srcPtr := <dataAreaFun>(value) + for { let i := 0 } lt(i, length) { i := add(i, 1) } + { + mstore(pos, sub(tail, headStart)) + tail := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail) + srcPtr := <nextArrayElement>(srcPtr) + pos := add(pos, <elementEncodedSize>) + } + pos := tail + <assignEnd> + } + )" : + R"( + function <functionName>(value, pos) <return> { + let length := <lengthFun>(value) + <storeLength> // might update pos + let srcPtr := <dataAreaFun>(value) + for { let i := 0 } lt(i, length) { i := add(i, 1) } + { + <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos) + srcPtr := <nextArrayElement>(srcPtr) + pos := add(pos, <elementEncodedSize>) + } + <assignEnd> + } + )" + ); + templ("functionName", functionName); + templ("return", dynamic ? " -> end " : ""); + templ("assignEnd", dynamic ? "end := pos" : ""); + templ("lengthFun", arrayLengthFunction(_from)); + if (_to.isDynamicallySized()) + templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)"); + else + templ("storeLength", ""); + templ("dataAreaFun", arrayDataAreaFunction(_from)); + templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize())); + templ("encodeToMemoryFun", abiEncodingFunction( + *_from.baseType(), + *_to.baseType(), + _encodeAsLibraryTypes, + true + )); + templ("arrayElementAccess", inMemory ? "mload" : "sload"); + templ("nextArrayElement", nextArrayElementFunction(_from)); + return templ.render(); + }); +} + +string ABIFunctions::abiEncodingFunctionMemoryByteArray( + ArrayType const& _from, + ArrayType const& _to, + bool _encodeAsLibraryTypes +) +{ + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + + solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); + solAssert(_from.length() == _to.length(), ""); + solAssert(_from.dataStoredIn(DataLocation::Memory), ""); + solAssert(_from.isByteArray(), ""); + + return createFunction(functionName, [&]() { + solAssert(_to.isByteArray(), ""); + Whiskers templ(R"( + function <functionName>(value, pos) -> end { + let length := <lengthFun>(value) + mstore(pos, length) + <copyFun>(add(value, 0x20), add(pos, 0x20), length) + end := add(add(pos, 0x20), <roundUpFun>(length)) + } + )"); + templ("functionName", functionName); + templ("lengthFun", arrayLengthFunction(_from)); + templ("copyFun", copyToMemoryFunction(false)); + templ("roundUpFun", roundUpFunction()); + return templ.render(); + }); +} + +string ABIFunctions::abiEncodingFunctionCompactStorageArray( + ArrayType const& _from, + ArrayType const& _to, + bool _encodeAsLibraryTypes +) +{ + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + + solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); + solAssert(_from.length() == _to.length(), ""); + solAssert(_from.dataStoredIn(DataLocation::Storage), ""); + + return createFunction(functionName, [&]() { + if (_from.isByteArray()) + { + solAssert(_to.isByteArray(), ""); + Whiskers templ(R"( + function <functionName>(value, pos) -> ret { + let slotValue := sload(value) + switch and(slotValue, 1) + case 0 { + // short byte array + let length := and(div(slotValue, 2), 0x7f) + mstore(pos, length) + mstore(add(pos, 0x20), and(slotValue, not(0xff))) + ret := add(pos, 0x40) + } + case 1 { + // long byte array + let length := div(slotValue, 2) + mstore(pos, length) + pos := add(pos, 0x20) + let dataPos := <arrayDataSlot>(value) + let i := 0 + for { } lt(i, length) { i := add(i, 0x20) } { + mstore(add(pos, i), sload(dataPos)) + dataPos := add(dataPos, 1) + } + ret := add(pos, i) + } + } + )"); + templ("functionName", functionName); + templ("arrayDataSlot", arrayDataAreaFunction(_from)); + return templ.render(); + } + else + { + // Multiple items per slot + solAssert(_from.baseType()->storageBytes() <= 16, ""); + solAssert(!_from.baseType()->isDynamicallyEncoded(), ""); + solAssert(_from.baseType()->isValueType(), ""); + bool dynamic = _to.isDynamicallyEncoded(); + size_t storageBytes = _from.baseType()->storageBytes(); + size_t itemsPerSlot = 32 / storageBytes; + // This always writes full slot contents to memory, which might be + // more than desired, i.e. it writes beyond the end of memory. + Whiskers templ( + R"( + function <functionName>(value, pos) <return> { + let length := <lengthFun>(value) + <storeLength> // might update pos + let originalPos := pos + let srcPtr := <dataArea>(value) + for { let i := 0 } lt(i, length) { i := add(i, <itemsPerSlot>) } + { + let data := sload(srcPtr) + <#items> + <encodeToMemoryFun>(<shiftRightFun>(data), pos) + pos := add(pos, <elementEncodedSize>) + </items> + srcPtr := add(srcPtr, 1) + } + pos := add(originalPos, mul(length, <elementEncodedSize>)) + <assignEnd> + } + )" + ); + templ("functionName", functionName); + templ("return", dynamic ? " -> end " : ""); + templ("assignEnd", dynamic ? "end := pos" : ""); + templ("lengthFun", arrayLengthFunction(_from)); + if (_to.isDynamicallySized()) + templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)"); + else + templ("storeLength", ""); + templ("dataArea", arrayDataAreaFunction(_from)); + templ("itemsPerSlot", to_string(itemsPerSlot)); + string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); + templ("elementEncodedSize", elementEncodedSize); + string encodeToMemoryFun = abiEncodingFunction( + *_from.baseType(), + *_to.baseType(), + _encodeAsLibraryTypes, + true + ); + templ("encodeToMemoryFun", encodeToMemoryFun); + std::vector<std::map<std::string, std::string>> items(itemsPerSlot); + for (size_t i = 0; i < itemsPerSlot; ++i) + items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8, false); + templ("items", items); + return templ.render(); + } + }); +} + +string ABIFunctions::abiEncodingFunctionStringLiteral( + Type const& _from, + Type const& _to, + bool _encodeAsLibraryTypes +) +{ + solAssert(_from.category() == Type::Category::StringLiteral, ""); + + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + return createFunction(functionName, [&]() { + auto const& strType = dynamic_cast<StringLiteralType const&>(_from); + string const& value = strType.value(); + solAssert(_from.sizeOnStack() == 0, ""); + + if (_to.isDynamicallySized()) + { + Whiskers templ(R"( + function <functionName>(pos) -> end { + mstore(pos, <length>) + <#word> + mstore(add(pos, <offset>), <wordValue>) + </word> + end := add(pos, <overallSize>) + } + )"); + templ("functionName", functionName); + + // TODO this can make use of CODECOPY for large strings once we have that in JULIA + size_t words = (value.size() + 31) / 32; + templ("overallSize", to_string(32 + words * 32)); + templ("length", to_string(value.size())); + vector<map<string, string>> wordParams(words); + for (size_t i = 0; i < words; ++i) + { + wordParams[i]["offset"] = to_string(32 + i * 32); + wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); + } + templ("word", wordParams); + return templ.render(); + } + else + { + solAssert(_to.category() == Type::Category::FixedBytes, ""); + solAssert(value.size() <= 32, ""); + Whiskers templ(R"( + function <functionName>(pos) { + mstore(pos, <wordValue>) + } + )"); + templ("functionName", functionName); + templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex()); + return templ.render(); + } + }); +} + +string ABIFunctions::abiEncodingFunctionFunctionType( + FunctionType const& _from, + Type const& _to, + bool _encodeAsLibraryTypes, + bool _compacted +) +{ + solAssert(_from.kind() == FunctionType::Kind::External, ""); + solAssert(_from == _to, ""); + + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_compacted ? "_compacted" : "") + + (_encodeAsLibraryTypes ? "_library" : ""); + + if (_compacted) + { + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(addr_and_function_id, pos) { + mstore(pos, <cleanExtFun>(addr_and_function_id)) + } + )") + ("functionName", functionName) + ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + .render(); + }); + } + else + { + return createFunction(functionName, [&]() { + return Whiskers(R"( + function <functionName>(addr, function_id, pos) { + mstore(pos, <combineExtFun>(addr, function_id)) + } + )") + ("functionName", functionName) + ("combineExtFun", combineExternalFunctionIdFunction()) + .render(); + }); + } +} + +string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) +{ + string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; + return createFunction(functionName, [&]() { + if (_fromCalldata) + { + return Whiskers(R"( + function <functionName>(src, dst, length) { + calldatacopy(dst, src, length) + // clear end + mstore(add(dst, length), 0) + } + )") + ("functionName", functionName) + .render(); + } + else + { + return Whiskers(R"( + function <functionName>(src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + switch eq(i, length) + case 0 { + // clear end + mstore(add(dst, length), 0) + } + } + )") + ("functionName", functionName) + .render(); + } + }); +} + +string ABIFunctions::shiftLeftFunction(size_t _numBits) +{ + string functionName = "shift_left_" + to_string(_numBits); + return createFunction(functionName, [&]() { + solAssert(_numBits < 256, ""); + return + Whiskers(R"(function <functionName>(value) -> newValue { + newValue := mul(value, <multiplier>) + })") + ("functionName", functionName) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); +} + +string ABIFunctions::shiftRightFunction(size_t _numBits, bool _signed) +{ + string functionName = "shift_right_" + to_string(_numBits) + (_signed ? "_signed" : "_unsigned"); + return createFunction(functionName, [&]() { + solAssert(_numBits < 256, ""); + return + Whiskers(R"(function <functionName>(value) -> newValue { + newValue := <div>(value, <multiplier>) + })") + ("functionName", functionName) + ("div", _signed ? "sdiv" : "div") + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); +} + +string ABIFunctions::roundUpFunction() +{ + string functionName = "round_up_to_mul_of_32"; + return createFunction(functionName, [&]() { + return + Whiskers(R"(function <functionName>(value) -> result { + result := and(add(value, 31), not(31)) + })") + ("functionName", functionName) + .render(); + }); +} + +string ABIFunctions::arrayLengthFunction(ArrayType const& _type) +{ + string functionName = "array_length_" + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers w(R"( + function <functionName>(value) -> length { + <body> + } + )"); + w("functionName", functionName); + string body; + if (!_type.isDynamicallySized()) + body = "length := " + toCompactHexWithPrefix(_type.length()); + else + { + switch (_type.location()) + { + case DataLocation::CallData: + solAssert(false, "called regular array length function on calldata array"); + break; + case DataLocation::Memory: + body = "length := mload(value)"; + break; + case DataLocation::Storage: + if (_type.isByteArray()) + { + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + body = R"( + length := sload(value) + let mask := sub(mul(0x100, iszero(and(length, 1))), 1) + length := div(and(length, mask), 2) + )"; + } + else + body = "length := sload(value)"; + break; + } + } + solAssert(!body.empty(), ""); + w("body", body); + return w.render(); + }); +} + +string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) +{ + string functionName = "array_dataslot_" + _type.identifier(); + return createFunction(functionName, [&]() { + if (_type.dataStoredIn(DataLocation::Memory)) + { + if (_type.isDynamicallySized()) + return Whiskers(R"( + function <functionName>(memPtr) -> dataPtr { + dataPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function <functionName>(memPtr) -> dataPtr { + dataPtr := memPtr + } + )") + ("functionName", functionName) + .render(); + } + else if (_type.dataStoredIn(DataLocation::Storage)) + { + if (_type.isDynamicallySized()) + { + Whiskers w(R"( + function <functionName>(slot) -> dataSlot { + mstore(0, slot) + dataSlot := keccak256(0, 0x20) + } + )"); + w("functionName", functionName); + return w.render(); + } + else + { + Whiskers w(R"( + function <functionName>(slot) -> dataSlot { + dataSlot := slot + } + )"); + w("functionName", functionName); + return w.render(); + } + } + else + { + // Not used for calldata + solAssert(false, ""); + } + }); +} + +string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) +{ + solAssert(!_type.isByteArray(), ""); + solAssert( + _type.location() == DataLocation::Memory || + _type.location() == DataLocation::Storage, + "" + ); + solAssert( + _type.location() == DataLocation::Memory || + _type.baseType()->storageBytes() > 16, + "" + ); + string functionName = "array_nextElement_" + _type.identifier(); + return createFunction(functionName, [&]() { + if (_type.location() == DataLocation::Memory) + return Whiskers(R"( + function <functionName>(memPtr) -> nextPtr { + nextPtr := add(memPtr, 0x20) + } + )") + ("functionName", functionName) + .render(); + else if (_type.location() == DataLocation::Storage) + return Whiskers(R"( + function <functionName>(slot) -> nextSlot { + nextSlot := add(slot, 1) + } + )") + ("functionName", functionName) + .render(); + else + solAssert(false, ""); + }); +} + +string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator) +{ + if (!m_requestedFunctions.count(_name)) + { + auto fun = _creator(); + solAssert(!fun.empty(), ""); + m_requestedFunctions[_name] = fun; + } + return _name; +} + +size_t ABIFunctions::headSize(TypePointers const& _targetTypes) +{ + size_t headSize = 0; + for (auto const& t: _targetTypes) + { + if (t->isDynamicallyEncoded()) + headSize += 0x20; + else + { + solAssert(t->calldataEncodedSize() > 0, ""); + headSize += t->calldataEncodedSize(); + } + } + + return headSize; +} diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h new file mode 100644 index 00000000..e43e2323 --- /dev/null +++ b/libsolidity/codegen/ABIFunctions.h @@ -0,0 +1,172 @@ +/* + 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 <chris@ethereum.org> + * @date 2017 + * Routines that generate JULIA code related to ABI encoding, decoding and type conversions. + */ + +#pragma once + +#include <libsolidity/ast/ASTForward.h> + +#include <vector> +#include <functional> +#include <map> + +namespace dev { +namespace solidity { + +class Type; +class ArrayType; +class StructType; +class FunctionType; +using TypePointer = std::shared_ptr<Type const>; +using TypePointers = std::vector<TypePointer>; + +/// +/// Class to generate encoding and decoding functions. Also maintains a collection +/// of "functions to be generated" in order to avoid generating the same function +/// multiple times. +/// +/// Make sure to include the result of ``requestedFunctions()`` to a block that +/// is visible from the code that was generated here. +class ABIFunctions +{ +public: + ~ABIFunctions(); + + /// @returns assembly code block to ABI-encode values of @a _givenTypes residing on the stack + /// into memory, converting the types to @a _targetTypes on the fly. + /// Assumed variables to be present: <$value0> <$value1> ... <$value(n-1)> <$headStart> + /// Does not allocate memory (does not change the memory head pointer), but writes + /// to memory starting at $headStart and an unrestricted amount after that. + /// Assigns the end of encoded memory either to $value0 or (if that is not present) + /// to $headStart. + std::string tupleEncoder( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _encodeAsLibraryTypes = false + ); + + /// @returns auxiliary functions referenced from the block generated in @a tupleEncoder + std::string requestedFunctions(); + +private: + /// @returns the name of the cleanup function for the given type and + /// adds its implementation to the requested functions. + /// @param _revertOnFailure if true, causes revert on invalid data, + /// otherwise an assertion failure. + std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false); + + /// @returns the name of the function that converts a value of type @a _from + /// to a value of type @a _to. The resulting vale is guaranteed to be in range + /// (i.e. "clean"). Asserts on failure. + std::string conversionFunction(Type const& _from, Type const& _to); + + std::string cleanupCombinedExternalFunctionIdFunction(); + + /// @returns a function that combines the address and selector to a single value + /// for use in the ABI. + std::string combineExternalFunctionIdFunction(); + + /// @returns the name of the ABI encoding function with the given type + /// and queues the generation of the function to the requested functions. + /// @param _compacted if true, the input value was just loaded from storage + /// or memory and thus might be compacted into a single slot (depending on the type). + std::string abiEncodingFunction( + Type const& _givenType, + Type const& _targetType, + bool _encodeAsLibraryTypes, + bool _compacted + ); + /// Part of @a abiEncodingFunction for array target type and given calldata array. + std::string abiEncodingFunctionCalldataArray( + Type const& _givenType, + Type const& _targetType, + bool _encodeAsLibraryTypes + ); + /// Part of @a abiEncodingFunction for array target type and given memory array or + /// a given storage array with one item per slot. + std::string abiEncodingFunctionSimpleArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + bool _encodeAsLibraryTypes + ); + std::string abiEncodingFunctionMemoryByteArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + bool _encodeAsLibraryTypes + ); + /// Part of @a abiEncodingFunction for array target type and given storage array + /// where multiple items are packed into the same storage slot. + std::string abiEncodingFunctionCompactStorageArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + bool _encodeAsLibraryTypes + ); + + // @returns the name of the ABI encoding function with the given type + // and queues the generation of the function to the requested functions. + // Case for _givenType being a string literal + std::string abiEncodingFunctionStringLiteral( + Type const& _givenType, + Type const& _targetType, + bool _encodeAsLibraryTypes + ); + + std::string abiEncodingFunctionFunctionType( + FunctionType const& _from, + Type const& _to, + bool _encodeAsLibraryTypes, + bool _compacted + ); + + /// @returns a function that copies raw bytes of dynamic length from calldata + /// or memory to memory. + /// Pads with zeros and might write more than exactly length. + std::string copyToMemoryFunction(bool _fromCalldata); + + std::string shiftLeftFunction(size_t _numBits); + std::string shiftRightFunction(size_t _numBits, bool _signed); + /// @returns the name of a function that rounds its input to the next multiple + /// of 32 or the input if it is a multiple of 32. + std::string roundUpFunction(); + + std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that converts a storage slot number + /// or a memory pointer to the slot number / memory pointer for the data position of an array + /// which is stored in that slot / memory area. + std::string arrayDataAreaFunction(ArrayType const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. + /// Only works for memory arrays and storage arrays that store one item per slot. + std::string nextArrayElementFunction(ArrayType const& _type); + + /// Helper function that uses @a _creator to create a function and add it to + /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both + /// cases. + std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator); + + /// @returns the size of the static part of the encoding of the given types. + static size_t headSize(TypePointers const& _targetTypes); + + /// Map from function name to code for a multi-use function. + std::map<std::string, std::string> m_requestedFunctions; +}; + +} +} diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index 806fbea7..f3ddc4ee 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -40,7 +40,7 @@ using TypePointer = std::shared_ptr<Type const>; class ArrayUtils { public: - ArrayUtils(CompilerContext& _context): m_context(_context) {} + explicit ArrayUtils(CompilerContext& _context): m_context(_context) {} /// Copies an array to an array in storage. The arrays can be of different types only if /// their storage representation is the same. diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index eef078c1..8c63ea9c 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -51,9 +51,9 @@ public: ContractDefinition const& _contract, std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts ); - eth::Assembly const& assembly() { return m_context.assembly(); } - eth::LinkerObject assembledObject() { return m_context.assembledObject(); } - eth::LinkerObject runtimeObject() { return m_context.assembledRuntimeObject(m_runtimeSub); } + eth::Assembly const& assembly() const { return m_context.assembly(); } + eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } + eth::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); } /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFromat shows whether the out should be in Json format Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index bc4de3ee..ed780d0b 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -44,11 +44,6 @@ namespace dev namespace solidity { -void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration) -{ - m_magicGlobals.insert(&_declaration); -} - void CompilerContext::addStateVariable( VariableDeclaration const& _declaration, u256 const& _storageOffset, diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 13821f67..96cbf6c1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -48,7 +48,7 @@ namespace solidity { class CompilerContext { public: - CompilerContext(CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext(CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared<eth::Assembly>()), m_runtimeContext(_runtimeContext) { @@ -56,7 +56,9 @@ public: m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } - void addMagicGlobal(MagicVariableDeclaration const& _declaration); + void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; } + bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); } + void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void removeVariable(VariableDeclaration const& _declaration); @@ -68,7 +70,6 @@ public: void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } - bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } @@ -207,8 +208,8 @@ public: return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); } - eth::LinkerObject const& assembledObject() { return m_asm->assemble(); } - eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); } + eth::LinkerObject const& assembledObject() const { return m_asm->assemble(); } + eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); } /** * Helper class to pop the visited nodes stack when a scope closes @@ -265,8 +266,8 @@ private: } m_functionCompilationQueue; eth::AssemblyPointer m_asm; - /// Magic global variables like msg, tx or this, distinguished by type. - std::set<Declaration const*> m_magicGlobals; + /// Activated experimental features. + std::set<ExperimentalFeature> m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts; /// Storage offsets of state variables diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 782aad9d..a0fc5d55 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -25,6 +25,7 @@ #include <libevmasm/Instruction.h> #include <libsolidity/codegen/ArrayUtils.h> #include <libsolidity/codegen/LValue.h> +#include <libsolidity/codegen/ABIFunctions.h> using namespace std; @@ -182,6 +183,18 @@ void CompilerUtils::encodeToMemory( if (_givenTypes.empty()) return; + else if ( + _padToWordBoundaries && + !_copyDynamicDataInPlace && + m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) + ) + { + // Use the new JULIA-based encoding function + auto stackHeightBefore = m_context.stackHeight(); + abiEncode(_givenTypes, targetTypes, _encodeAsLibraryTypes); + solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), ""); + return; + } // Stack during operation: // <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem> @@ -289,6 +302,28 @@ void CompilerUtils::encodeToMemory( popStackSlots(argSize + dynPointers + 1); } +void CompilerUtils::abiEncode( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _encodeAsLibraryTypes +) +{ + // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> + + vector<string> variables; + size_t numValues = sizeOnStack(_givenTypes); + for (size_t i = 0; i < numValues; ++i) + variables.push_back("$value" + to_string(i)); + variables.push_back("$headStart"); + + ABIFunctions funs; + string routine = funs.tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes); + routine += funs.requestedFunctions(); + m_context.appendInlineAssembly("{" + routine + "}", variables); + // Remove everyhing except for "value0" / the final memory pointer. + popStackSlots(numValues); +} + void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { auto repeat = m_context.newTag(); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index fb169463..18b70250 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -33,7 +33,7 @@ class Type; // forward class CompilerUtils { public: - CompilerUtils(CompilerContext& _context): m_context(_context) {} + explicit CompilerUtils(CompilerContext& _context): m_context(_context) {} /// Stores the initial value of the free-memory-pointer at its position; void initialiseFreeMemoryPointer(); @@ -103,6 +103,14 @@ public: bool _encodeAsLibraryTypes = false ); + /// Special case of @a encodeToMemory which assumes that everything is padded to words + /// and dynamic data is not copied in place (i.e. a proper ABI encoding). + void abiEncode( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _encodeAsLibraryTypes = false + ); + /// Zero-initialises (the data part of) an already allocated memory array. /// Length has to be nonzero! /// Stack pre: <length> <memptr> diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index fd0998d4..e53f1b94 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -45,7 +45,7 @@ using namespace dev::solidity; class StackHeightChecker { public: - StackHeightChecker(CompilerContext const& _context): + explicit StackHeightChecker(CompilerContext const& _context): m_context(_context), stackHeight(m_context.stackHeight()) {} void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); } private: @@ -100,6 +100,7 @@ void ContractCompiler::initializeContext( map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts ) { + m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); m_context.setCompiledContracts(_compiledContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); CompilerUtils(m_context).initialiseFreeMemoryPointer(); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 521d485f..639bfc32 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -645,8 +645,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) FunctionType::Kind::BareCall, false, nullptr, - false, - false, + StateMutability::NonPayable, true, true ), @@ -1812,7 +1811,7 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue<StorageItem>(_expression, *_expression.annotation().type); } -bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) +bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) const { if (Token::isCompareOp(_op) || Token::isShiftOp(_op)) return true; diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 3b8cf1c6..5f6c3d64 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -119,7 +119,7 @@ private: /// @returns true if the operator applied to the given type requires a cleanup prior to the /// operation. - bool cleanupNeededForOp(Type::Category _type, Token::Value _op); + bool cleanupNeededForOp(Type::Category _type, Token::Value _op) const; /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp new file mode 100644 index 00000000..fd78e578 --- /dev/null +++ b/libsolidity/formal/SMTChecker.cpp @@ -0,0 +1,588 @@ +/* + 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/>. +*/ + +#include <libsolidity/formal/SMTChecker.h> + +#ifdef HAVE_Z3 +#include <libsolidity/formal/Z3Interface.h> +#else +#include <libsolidity/formal/SMTLib2Interface.h> +#endif + +#include <libsolidity/interface/ErrorReporter.h> + +#include <boost/range/adaptor/map.hpp> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readFileCallback): +#ifdef HAVE_Z3 + m_interface(make_shared<smt::Z3Interface>()), +#else + m_interface(make_shared<smt::SMTLib2Interface>(_readFileCallback)), +#endif + m_errorReporter(_errorReporter) +{ + (void)_readFileCallback; +} + +void SMTChecker::analyze(SourceUnit const& _source) +{ + if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) + { + m_interface->reset(); + m_currentSequenceCounter.clear(); + m_nextFreeSequenceCounter.clear(); + _source.accept(*this); + } +} + +void SMTChecker::endVisit(VariableDeclaration const& _varDecl) +{ + if (_varDecl.value()) + { + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet support this." + ); + } + else if (_varDecl.isLocalOrReturn()) + createVariable(_varDecl, true); + else if (_varDecl.isCallableParameter()) + createVariable(_varDecl, false); +} + +bool SMTChecker::visit(FunctionDefinition const& _function) +{ + if (!_function.modifiers().empty() || _function.isConstructor()) + m_errorReporter.warning( + _function.location(), + "Assertion checker does not yet support constructors and functions with modifiers." + ); + // TODO actually we probably also have to reset all local variables and similar things. + m_currentFunction = &_function; + m_interface->push(); + return true; +} + +void SMTChecker::endVisit(FunctionDefinition const&) +{ + // TOOD we could check for "reachability", i.e. satisfiability here. + // We only handle local variables, so we clear everything. + // If we add storage variables, those should be cleared differently. + m_currentSequenceCounter.clear(); + m_nextFreeSequenceCounter.clear(); + m_interface->pop(); + m_currentFunction = nullptr; +} + +bool SMTChecker::visit(IfStatement const& _node) +{ + _node.condition().accept(*this); + + // TODO Check if condition is always true + + auto countersAtStart = m_currentSequenceCounter; + m_interface->push(); + m_interface->addAssertion(expr(_node.condition())); + _node.trueStatement().accept(*this); + auto countersAtEndOfTrue = m_currentSequenceCounter; + m_interface->pop(); + + decltype(m_currentSequenceCounter) countersAtEndOfFalse; + if (_node.falseStatement()) + { + m_currentSequenceCounter = countersAtStart; + m_interface->push(); + m_interface->addAssertion(!expr(_node.condition())); + _node.falseStatement()->accept(*this); + countersAtEndOfFalse = m_currentSequenceCounter; + m_interface->pop(); + } + else + countersAtEndOfFalse = countersAtStart; + + // Reset all values that have been touched. + + // TODO this should use a previously generated side-effect structure + + solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), ""); + for (auto const& declCounter: countersAtEndOfTrue) + { + solAssert(countersAtEndOfFalse.count(declCounter.first), ""); + auto decl = declCounter.first; + int trueCounter = countersAtEndOfTrue.at(decl); + int falseCounter = countersAtEndOfFalse.at(decl); + if (trueCounter == falseCounter) + continue; // Was not modified + newValue(*decl); + setValue(*decl, 0); + } + return false; +} + +bool SMTChecker::visit(WhileStatement const& _node) +{ + _node.condition().accept(*this); + + //m_interface->push(); + //m_interface->addAssertion(expr(_node.condition())); + // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions + + // TODO combine similar to if + return true; +} + +void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) +{ + if (_varDecl.declarations().size() != 1) + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet support such variable declarations." + ); + else if (knownVariable(*_varDecl.declarations()[0])) + { + if (_varDecl.initialValue()) + // TODO more checks? + // TODO add restrictions about type (might be assignment from smaller type) + m_interface->addAssertion(newValue(*_varDecl.declarations()[0]) == expr(*_varDecl.initialValue())); + } + else + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet implement such variable declarations." + ); +} + +void SMTChecker::endVisit(ExpressionStatement const&) +{ +} + +void SMTChecker::endVisit(Assignment const& _assignment) +{ + if (_assignment.assignmentOperator() != Token::Value::Assign) + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement compound assignment." + ); + else if (_assignment.annotation().type->category() != Type::Category::Integer) + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement type " + _assignment.annotation().type->toString() + ); + else if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_assignment.leftHandSide())) + { + Declaration const* decl = identifier->annotation().referencedDeclaration; + if (knownVariable(*decl)) + // TODO more checks? + // TODO add restrictions about type (might be assignment from smaller type) + m_interface->addAssertion(newValue(*decl) == expr(_assignment.rightHandSide())); + else + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement such assignments." + ); + } + else + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement such assignments." + ); +} + +void SMTChecker::endVisit(TupleExpression const& _tuple) +{ + if (_tuple.isInlineArray() || _tuple.components().size() != 1) + m_errorReporter.warning( + _tuple.location(), + "Assertion checker does not yet implement tules and inline arrays." + ); + else + m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0])); +} + +void SMTChecker::endVisit(BinaryOperation const& _op) +{ + if (Token::isArithmeticOp(_op.getOperator())) + arithmeticOperation(_op); + else if (Token::isCompareOp(_op.getOperator())) + compareOperation(_op); + else if (Token::isBooleanOp(_op.getOperator())) + booleanOperation(_op); + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); +} + +void SMTChecker::endVisit(FunctionCall const& _funCall) +{ + FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); + + std::vector<ASTPointer<Expression const>> const args = _funCall.arguments(); + if (funType.kind() == FunctionType::Kind::Assert) + { + solAssert(args.size() == 1, ""); + solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); + checkCondition(!(expr(*args[0])), _funCall.location(), "Assertion violation"); + m_interface->addAssertion(expr(*args[0])); + } + else if (funType.kind() == FunctionType::Kind::Require) + { + solAssert(args.size() == 1, ""); + solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); + m_interface->addAssertion(expr(*args[0])); + checkCondition(!(expr(*args[0])), _funCall.location(), "Unreachable code"); + // TODO is there something meaningful we can check here? + // We can check whether the condition is always fulfilled or never fulfilled. + } +} + +void SMTChecker::endVisit(Identifier const& _identifier) +{ + Declaration const* decl = _identifier.annotation().referencedDeclaration; + solAssert(decl, ""); + if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get())) + { + m_interface->addAssertion(expr(_identifier) == currentValue(*decl)); + return; + } + else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) + { + if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) + return; + // TODO for others, clear our knowledge about storage and memory + } + m_errorReporter.warning( + _identifier.location(), + "Assertion checker does not yet support the type of this expression (" + + _identifier.annotation().type->toString() + + ")." + ); +} + +void SMTChecker::endVisit(Literal const& _literal) +{ + Type const& type = *_literal.annotation().type; + if (type.category() == Type::Category::Integer || type.category() == Type::Category::RationalNumber) + { + if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type)) + solAssert(!rational->isFractional(), ""); + + m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal))); + } + else + m_errorReporter.warning( + _literal.location(), + "Assertion checker does not yet support the type of this expression (" + + _literal.annotation().type->toString() + + ")." + ); +} + +void SMTChecker::arithmeticOperation(BinaryOperation const& _op) +{ + switch (_op.getOperator()) + { + case Token::Add: + case Token::Sub: + case Token::Mul: + { + solAssert(_op.annotation().commonType, ""); + solAssert(_op.annotation().commonType->category() == Type::Category::Integer, ""); + smt::Expression left(expr(_op.leftExpression())); + smt::Expression right(expr(_op.rightExpression())); + Token::Value op = _op.getOperator(); + smt::Expression value( + op == Token::Add ? left + right : + op == Token::Sub ? left - right : + /*op == Token::Mul*/ left * right + ); + + // Overflow check + auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType); + checkCondition( + value < minValue(intType), + _op.location(), + "Underflow (resulting value less than " + formatNumber(intType.minValue()) + ")", + "value", + &value + ); + checkCondition( + value > maxValue(intType), + _op.location(), + "Overflow (resulting value larger than " + formatNumber(intType.maxValue()) + ")", + "value", + &value + ); + + m_interface->addAssertion(expr(_op) == value); + break; + } + default: + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); + } +} + +void SMTChecker::compareOperation(BinaryOperation const& _op) +{ + solAssert(_op.annotation().commonType, ""); + if (_op.annotation().commonType->category() == Type::Category::Integer) + { + smt::Expression left(expr(_op.leftExpression())); + smt::Expression right(expr(_op.rightExpression())); + Token::Value op = _op.getOperator(); + smt::Expression value = ( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (left < right) : + op == Token::LessThanOrEqual ? (left <= right) : + op == Token::GreaterThan ? (left > right) : + /*op == Token::GreaterThanOrEqual*/ (left >= right) + ); + // TODO: check that other values for op are not possible. + m_interface->addAssertion(expr(_op) == value); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons" + ); +} + +void SMTChecker::booleanOperation(BinaryOperation const& _op) +{ + solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, ""); + solAssert(_op.annotation().commonType, ""); + if (_op.annotation().commonType->category() == Type::Category::Bool) + { + if (_op.getOperator() == Token::And) + m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression())); + else + m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression())); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" + ); +} + +void SMTChecker::checkCondition( + smt::Expression _condition, + SourceLocation const& _location, + string const& _description, + string const& _additionalValueName, + smt::Expression* _additionalValue +) +{ + m_interface->push(); + m_interface->addAssertion(_condition); + + vector<smt::Expression> expressionsToEvaluate; + vector<string> expressionNames; + if (m_currentFunction) + { + if (_additionalValue) + { + expressionsToEvaluate.emplace_back(*_additionalValue); + expressionNames.push_back(_additionalValueName); + } + for (auto const& param: m_currentFunction->parameters()) + if (knownVariable(*param)) + { + expressionsToEvaluate.emplace_back(currentValue(*param)); + expressionNames.push_back(param->name()); + } + for (auto const& var: m_currentFunction->localVariables()) + if (knownVariable(*var)) + { + expressionsToEvaluate.emplace_back(currentValue(*var)); + expressionNames.push_back(var->name()); + } + } + smt::CheckResult result; + vector<string> values; + try + { + tie(result, values) = m_interface->check(expressionsToEvaluate); + } + catch (smt::SolverError const& _e) + { + string description("Error querying SMT solver"); + if (_e.comment()) + description += ": " + *_e.comment(); + m_errorReporter.warning(_location, description); + return; + } + + switch (result) + { + case smt::CheckResult::SATISFIABLE: + { + std::ostringstream message; + message << _description << " happens here"; + if (m_currentFunction) + { + message << " for:\n"; + solAssert(values.size() == expressionNames.size(), ""); + for (size_t i = 0; i < values.size(); ++i) + { + string formattedValue = values.at(i); + try + { + // Parse and re-format nicely + formattedValue = formatNumber(bigint(formattedValue)); + } + catch (...) { } + + message << " " << expressionNames.at(i) << " = " << formattedValue << "\n"; + } + } + else + message << "."; + m_errorReporter.warning(_location, message.str()); + break; + } + case smt::CheckResult::UNSATISFIABLE: + break; + case smt::CheckResult::UNKNOWN: + m_errorReporter.warning(_location, _description + " might happen here."); + break; + case smt::CheckResult::ERROR: + m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); + break; + default: + solAssert(false, ""); + } + m_interface->pop(); +} + +void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero) +{ + if (dynamic_cast<IntegerType const*>(_varDecl.type().get())) + { + solAssert(m_currentSequenceCounter.count(&_varDecl) == 0, ""); + solAssert(m_nextFreeSequenceCounter.count(&_varDecl) == 0, ""); + solAssert(m_Variables.count(&_varDecl) == 0, ""); + m_currentSequenceCounter[&_varDecl] = 0; + m_nextFreeSequenceCounter[&_varDecl] = 1; + m_Variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int)); + setValue(_varDecl, _setToZero); + } + else + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet support the type of this variable." + ); +} + +string SMTChecker::uniqueSymbol(Declaration const& _decl) +{ + return _decl.name() + "_" + to_string(_decl.id()); +} + +string SMTChecker::uniqueSymbol(Expression const& _expr) +{ + return "expr_" + to_string(_expr.id()); +} + +bool SMTChecker::knownVariable(Declaration const& _decl) +{ + return m_currentSequenceCounter.count(&_decl); +} + +smt::Expression SMTChecker::currentValue(Declaration const& _decl) +{ + solAssert(m_currentSequenceCounter.count(&_decl), ""); + return valueAtSequence(_decl, m_currentSequenceCounter.at(&_decl)); +} + +smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _sequence) +{ + return var(_decl)(_sequence); +} + +smt::Expression SMTChecker::newValue(Declaration const& _decl) +{ + solAssert(m_currentSequenceCounter.count(&_decl), ""); + solAssert(m_nextFreeSequenceCounter.count(&_decl), ""); + m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++; + return currentValue(_decl); +} + +void SMTChecker::setValue(Declaration const& _decl, bool _setToZero) +{ + auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type()); + + if (_setToZero) + m_interface->addAssertion(currentValue(_decl) == 0); + else + { + m_interface->addAssertion(currentValue(_decl) >= minValue(intType)); + m_interface->addAssertion(currentValue(_decl) <= maxValue(intType)); + } +} + +smt::Expression SMTChecker::minValue(IntegerType const& _t) +{ + return smt::Expression(_t.minValue()); +} + +smt::Expression SMTChecker::maxValue(IntegerType const& _t) +{ + return smt::Expression(_t.maxValue()); +} + +smt::Expression SMTChecker::expr(Expression const& _e) +{ + if (!m_Expressions.count(&_e)) + { + solAssert(_e.annotation().type, ""); + switch (_e.annotation().type->category()) + { + case Type::Category::RationalNumber: + { + if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(_e.annotation().type.get())) + solAssert(!rational->isFractional(), ""); + m_Expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e))); + break; + } + case Type::Category::Integer: + m_Expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e))); + break; + case Type::Category::Bool: + m_Expressions.emplace(&_e, m_interface->newBool(uniqueSymbol(_e))); + break; + default: + solAssert(false, "Type not implemented."); + } + } + return m_Expressions.at(&_e); +} + +smt::Expression SMTChecker::var(Declaration const& _decl) +{ + solAssert(m_Variables.count(&_decl), ""); + return m_Variables.at(&_decl); +} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h new file mode 100644 index 00000000..d23fd201 --- /dev/null +++ b/libsolidity/formal/SMTChecker.h @@ -0,0 +1,114 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/formal/SolverInterface.h> +#include <libsolidity/interface/ReadFile.h> + +#include <map> +#include <string> + +namespace dev +{ +namespace solidity +{ + +class ErrorReporter; + +class SMTChecker: private ASTConstVisitor +{ +public: + SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readCallback); + + void analyze(SourceUnit const& _sources); + +private: + // TODO: Check that we do not have concurrent reads and writes to a variable, + // because the order of expression evaluation is undefined + // TODO: or just force a certain order, but people might have a different idea about that. + + virtual void endVisit(VariableDeclaration const& _node) override; + virtual bool visit(FunctionDefinition const& _node) override; + virtual void endVisit(FunctionDefinition const& _node) override; + virtual bool visit(IfStatement const& _node) override; + virtual bool visit(WhileStatement const& _node) override; + virtual void endVisit(VariableDeclarationStatement const& _node) override; + virtual void endVisit(ExpressionStatement const& _node) override; + virtual void endVisit(Assignment const& _node) override; + virtual void endVisit(TupleExpression const& _node) override; + virtual void endVisit(BinaryOperation const& _node) override; + virtual void endVisit(FunctionCall const& _node) override; + virtual void endVisit(Identifier const& _node) override; + virtual void endVisit(Literal const& _node) override; + + void arithmeticOperation(BinaryOperation const& _op); + void compareOperation(BinaryOperation const& _op); + void booleanOperation(BinaryOperation const& _op); + + void checkCondition( + smt::Expression _condition, + SourceLocation const& _location, + std::string const& _description, + std::string const& _additionalValueName = "", + smt::Expression* _additionalValue = nullptr + ); + + void createVariable(VariableDeclaration const& _varDecl, bool _setToZero); + + static std::string uniqueSymbol(Declaration const& _decl); + static std::string uniqueSymbol(Expression const& _expr); + + /// @returns true if _delc is a variable that is known at the current point, i.e. + /// has a valid sequence number + bool knownVariable(Declaration const& _decl); + /// @returns an expression denoting the value of the variable declared in @a _decl + /// at the current point. + smt::Expression currentValue(Declaration const& _decl); + /// @returns an expression denoting the value of the variable declared in @a _decl + /// at the given sequence point. Does not ensure that this sequence point exists. + smt::Expression valueAtSequence(Declaration const& _decl, int _sequence); + /// Allocates a new sequence number for the declaration, updates the current + /// sequence number to this value and returns the expression. + smt::Expression newValue(Declaration const& _decl); + + /// Sets the value of the declaration either to zero or to its intrinsic range. + void setValue(Declaration const& _decl, bool _setToZero); + + static smt::Expression minValue(IntegerType const& _t); + static smt::Expression maxValue(IntegerType const& _t); + + /// Returns the expression corresponding to the AST node. Creates a new expression + /// if it does not exist yet. + smt::Expression expr(Expression const& _e); + /// Returns the function declaration corresponding to the given variable. + /// The function takes one argument which is the "sequence number". + smt::Expression var(Declaration const& _decl); + + std::shared_ptr<smt::SolverInterface> m_interface; + std::map<Declaration const*, int> m_currentSequenceCounter; + std::map<Declaration const*, int> m_nextFreeSequenceCounter; + std::map<Expression const*, smt::Expression> m_Expressions; + std::map<Declaration const*, smt::Expression> m_Variables; + ErrorReporter& m_errorReporter; + + FunctionDefinition const* m_currentFunction = nullptr; +}; + +} +} diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp new file mode 100644 index 00000000..cbd766fb --- /dev/null +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -0,0 +1,187 @@ +/* + 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/>. +*/ + +#include <libsolidity/formal/SMTLib2Interface.h> + +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/join.hpp> +#include <boost/filesystem/operations.hpp> + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <memory> +#include <stdexcept> +#include <string> +#include <array> + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::smt; + +SMTLib2Interface::SMTLib2Interface(ReadCallback::Callback const& _queryCallback): + m_queryCallback(_queryCallback) +{ + reset(); +} + +void SMTLib2Interface::reset() +{ + m_accumulatedOutput.clear(); + m_accumulatedOutput.emplace_back(); + write("(set-option :produce-models true)"); + write("(set-logic QF_UFLIA)"); +} + +void SMTLib2Interface::push() +{ + m_accumulatedOutput.emplace_back(); +} + +void SMTLib2Interface::pop() +{ + solAssert(!m_accumulatedOutput.empty(), ""); + m_accumulatedOutput.pop_back(); +} + +Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain) +{ + write( + "(declare-fun |" + + _name + + "| (" + + (_domain == Sort::Int ? "Int" : "Bool") + + ") " + + (_codomain == Sort::Int ? "Int" : "Bool") + + ")" + ); + return SolverInterface::newFunction(move(_name), _domain, _codomain); +} + +Expression SMTLib2Interface::newInteger(string _name) +{ + write("(declare-const |" + _name + "| Int)"); + return SolverInterface::newInteger(move(_name)); +} + +Expression SMTLib2Interface::newBool(string _name) +{ + write("(declare-const |" + _name + "| Bool)"); + return SolverInterface::newBool(std::move(_name)); +} + +void SMTLib2Interface::addAssertion(Expression const& _expr) +{ + write("(assert " + toSExpr(_expr) + ")"); +} + +pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> const& _expressionsToEvaluate) +{ + string response = querySolver( + boost::algorithm::join(m_accumulatedOutput, "\n") + + checkSatAndGetValuesCommand(_expressionsToEvaluate) + ); + + CheckResult result; + // TODO proper parsing + if (boost::starts_with(response, "sat\n")) + result = CheckResult::SATISFIABLE; + else if (boost::starts_with(response, "unsat\n")) + result = CheckResult::UNSATISFIABLE; + else if (boost::starts_with(response, "unknown\n")) + result = CheckResult::UNKNOWN; + else + result = CheckResult::ERROR; + + vector<string> values; + if (result != CheckResult::UNSATISFIABLE && result != CheckResult::ERROR) + values = parseValues(find(response.cbegin(), response.cend(), '\n'), response.cend()); + return make_pair(result, values); +} + +string SMTLib2Interface::toSExpr(Expression const& _expr) +{ + if (_expr.arguments.empty()) + return _expr.name; + std::string sexpr = "(" + _expr.name; + for (auto const& arg: _expr.arguments) + sexpr += " " + toSExpr(arg); + sexpr += ")"; + return sexpr; +} + +void SMTLib2Interface::write(string _data) +{ + solAssert(!m_accumulatedOutput.empty(), ""); + m_accumulatedOutput.back() += move(_data) + "\n"; +} + +string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _expressionsToEvaluate) +{ + string command; + if (_expressionsToEvaluate.empty()) + command = "(check-sat)\n"; + else + { + // TODO make sure these are unique + for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) + { + auto const& e = _expressionsToEvaluate.at(i); + // TODO they don't have to be ints... + command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n"; + command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n"; + } + command += "(check-sat)\n"; + command += "(get-value ("; + for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) + command += "|EVALEXPR_" + to_string(i) + "| "; + command += "))\n"; + } + + return command; +} + +vector<string> SMTLib2Interface::parseValues(string::const_iterator _start, string::const_iterator _end) +{ + vector<string> values; + while (_start < _end) + { + auto valStart = find(_start, _end, ' '); + if (valStart < _end) + ++valStart; + auto valEnd = find(valStart, _end, ')'); + values.emplace_back(valStart, valEnd); + _start = find(valEnd, _end, '('); + } + + return values; +} + +string SMTLib2Interface::querySolver(string const& _input) +{ + if (!m_queryCallback) + BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment("No SMT solver available.")); + + ReadCallback::Result queryResult = m_queryCallback(_input); + if (!queryResult.success) + BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment(queryResult.responseOrErrorMessage)); + return queryResult.responseOrErrorMessage; +} diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h new file mode 100644 index 00000000..b8dac366 --- /dev/null +++ b/libsolidity/formal/SMTLib2Interface.h @@ -0,0 +1,75 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SolverInterface.h> + +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> + +#include <libdevcore/Common.h> + +#include <boost/noncopyable.hpp> + +#include <map> +#include <string> +#include <vector> +#include <cstdio> + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +class SMTLib2Interface: public SolverInterface, public boost::noncopyable +{ +public: + SMTLib2Interface(ReadCallback::Callback const& _queryCallback); + + void reset() override; + + void push() override; + void pop() override; + + Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override; + Expression newInteger(std::string _name) override; + Expression newBool(std::string _name) override; + + void addAssertion(Expression const& _expr) override; + std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override; + +private: + std::string toSExpr(Expression const& _expr); + + void write(std::string _data); + + std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate); + std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end); + + /// Communicates with the solver via the callback. Throws SMTSolverError on error. + std::string querySolver(std::string const& _input); + + ReadCallback::Callback m_queryCallback; + std::vector<std::string> m_accumulatedOutput; +}; + +} +} +} diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h new file mode 100644 index 00000000..32d92a2a --- /dev/null +++ b/libsolidity/formal/SolverInterface.h @@ -0,0 +1,178 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> + +#include <libdevcore/Common.h> +#include <libdevcore/Exceptions.h> + +#include <boost/noncopyable.hpp> + +#include <map> +#include <string> +#include <vector> +#include <cstdio> + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +enum class CheckResult +{ + SATISFIABLE, UNSATISFIABLE, UNKNOWN, ERROR +}; + +enum class Sort +{ + Int, Bool +}; + +/// C++ representation of an SMTLIB2 expression. +class Expression +{ + friend class SolverInterface; +public: + Expression(size_t _number): name(std::to_string(_number)) {} + Expression(u256 const& _number): name(_number.str()) {} + Expression(bigint const& _number): name(_number.str()) {} + + Expression(Expression const& _other) = default; + Expression(Expression&& _other) = default; + Expression& operator=(Expression const& _other) = default; + Expression& operator=(Expression&& _other) = default; + + static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue) + { + return Expression("ite", std::vector<Expression>{ + std::move(_condition), std::move(_trueValue), std::move(_falseValue) + }); + } + + friend Expression operator!(Expression _a) + { + return Expression("not", std::move(_a)); + } + friend Expression operator&&(Expression _a, Expression _b) + { + return Expression("and", std::move(_a), std::move(_b)); + } + friend Expression operator||(Expression _a, Expression _b) + { + return Expression("or", std::move(_a), std::move(_b)); + } + friend Expression operator==(Expression _a, Expression _b) + { + return Expression("=", std::move(_a), std::move(_b)); + } + friend Expression operator!=(Expression _a, Expression _b) + { + return !(std::move(_a) == std::move(_b)); + } + friend Expression operator<(Expression _a, Expression _b) + { + return Expression("<", std::move(_a), std::move(_b)); + } + friend Expression operator<=(Expression _a, Expression _b) + { + return Expression("<=", std::move(_a), std::move(_b)); + } + friend Expression operator>(Expression _a, Expression _b) + { + return Expression(">", std::move(_a), std::move(_b)); + } + friend Expression operator>=(Expression _a, Expression _b) + { + return Expression(">=", std::move(_a), std::move(_b)); + } + friend Expression operator+(Expression _a, Expression _b) + { + return Expression("+", std::move(_a), std::move(_b)); + } + friend Expression operator-(Expression _a, Expression _b) + { + return Expression("-", std::move(_a), std::move(_b)); + } + friend Expression operator*(Expression _a, Expression _b) + { + return Expression("*", std::move(_a), std::move(_b)); + } + Expression operator()(Expression _a) const + { + solAssert(arguments.empty(), "Attempted function application to non-function."); + return Expression(name, _a); + } + + std::string const name; + std::vector<Expression> const arguments; + +private: + /// Manual constructor, should only be used by SolverInterface and this class itself. + Expression(std::string _name, std::vector<Expression> _arguments): + name(std::move(_name)), arguments(std::move(_arguments)) {} + + explicit Expression(std::string _name): + Expression(std::move(_name), std::vector<Expression>{}) {} + Expression(std::string _name, Expression _arg): + Expression(std::move(_name), std::vector<Expression>{std::move(_arg)}) {} + Expression(std::string _name, Expression _arg1, Expression _arg2): + Expression(std::move(_name), std::vector<Expression>{std::move(_arg1), std::move(_arg2)}) {} +}; + +DEV_SIMPLE_EXCEPTION(SolverError); + +class SolverInterface +{ +public: + virtual void reset() = 0; + + virtual void push() = 0; + virtual void pop() = 0; + + virtual Expression newFunction(std::string _name, Sort /*_domain*/, Sort /*_codomain*/) + { + // Subclasses should do something here + return Expression(std::move(_name), {}); + } + virtual Expression newInteger(std::string _name) + { + // Subclasses should do something here + return Expression(std::move(_name), {}); + } + virtual Expression newBool(std::string _name) + { + // Subclasses should do something here + return Expression(std::move(_name), {}); + } + + virtual void addAssertion(Expression const& _expr) = 0; + + /// Checks for satisfiability, evaluates the expressions if a model + /// is available. Throws SMTSolverError on error. + virtual std::pair<CheckResult, std::vector<std::string>> + check(std::vector<Expression> const& _expressionsToEvaluate) = 0; +}; + + +} +} +} diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp new file mode 100644 index 00000000..522928f0 --- /dev/null +++ b/libsolidity/formal/Z3Interface.cpp @@ -0,0 +1,189 @@ +/* + 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/>. +*/ + +#include <libsolidity/formal/Z3Interface.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <libdevcore/CommonIO.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity::smt; + +Z3Interface::Z3Interface(): + m_solver(m_context) +{ +} + +void Z3Interface::reset() +{ + m_constants.clear(); + m_functions.clear(); + m_solver.reset(); +} + +void Z3Interface::push() +{ + m_solver.push(); +} + +void Z3Interface::pop() +{ + m_solver.pop(); +} + +Expression Z3Interface::newFunction(string _name, Sort _domain, Sort _codomain) +{ + m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(_domain), z3Sort(_codomain))}); + return SolverInterface::newFunction(move(_name), _domain, _codomain); +} + +Expression Z3Interface::newInteger(string _name) +{ + m_constants.insert({_name, m_context.int_const(_name.c_str())}); + return SolverInterface::newInteger(move(_name)); +} + +Expression Z3Interface::newBool(string _name) +{ + m_constants.insert({_name, m_context.bool_const(_name.c_str())}); + return SolverInterface::newBool(std::move(_name)); +} + +void Z3Interface::addAssertion(Expression const& _expr) +{ + m_solver.add(toZ3Expr(_expr)); +} + +pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _expressionsToEvaluate) +{ +// cout << "---------------------------------" << endl; +// cout << m_solver << endl; + CheckResult result; + switch (m_solver.check()) + { + case z3::check_result::sat: + result = CheckResult::SATISFIABLE; + cout << "sat" << endl; + break; + case z3::check_result::unsat: + result = CheckResult::UNSATISFIABLE; + cout << "unsat" << endl; + break; + case z3::check_result::unknown: + result = CheckResult::UNKNOWN; + cout << "unknown" << endl; + break; + default: + solAssert(false, ""); + } +// cout << "---------------------------------" << endl; + + + vector<string> values; + if (result != CheckResult::UNSATISFIABLE) + { + z3::model m = m_solver.get_model(); + for (Expression const& e: _expressionsToEvaluate) + values.push_back(toString(m.eval(toZ3Expr(e)))); + } + return make_pair(result, values); +} + +z3::expr Z3Interface::toZ3Expr(Expression const& _expr) +{ + if (_expr.arguments.empty() && m_constants.count(_expr.name)) + return m_constants.at(_expr.name); + z3::expr_vector arguments(m_context); + for (auto const& arg: _expr.arguments) + arguments.push_back(toZ3Expr(arg)); + + static map<string, unsigned> arity{ + {"ite", 3}, + {"not", 1}, + {"and", 2}, + {"or", 2}, + {"=", 2}, + {"<", 2}, + {"<=", 2}, + {">", 2}, + {">=", 2}, + {"+", 2}, + {"-", 2}, + {"*", 2}, + {">=", 2} + }; + string const& n = _expr.name; + if (m_functions.count(n)) + return m_functions.at(n)(arguments); + else if (m_constants.count(n)) + { + solAssert(arguments.empty(), ""); + return m_constants.at(n); + } + else if (arguments.empty()) + { + // We assume it is an integer... + return m_context.int_val(n.c_str()); + } + + assert(arity.count(n) && arity.at(n) == arguments.size()); + if (n == "ite") + return z3::ite(arguments[0], arguments[1], arguments[2]); + else if (n == "not") + return !arguments[0]; + else if (n == "and") + return arguments[0] && arguments[1]; + else if (n == "or") + return arguments[0] || arguments[1]; + else if (n == "=") + return arguments[0] == arguments[1]; + else if (n == "<") + return arguments[0] < arguments[1]; + else if (n == "<=") + return arguments[0] <= arguments[1]; + else if (n == ">") + return arguments[0] > arguments[1]; + else if (n == ">=") + return arguments[0] >= arguments[1]; + else if (n == "+") + return arguments[0] + arguments[1]; + else if (n == "-") + return arguments[0] - arguments[1]; + else if (n == "*") + return arguments[0] * arguments[1]; + // Cannot reach here. + solAssert(false, ""); + return arguments[0]; +} + +z3::sort Z3Interface::z3Sort(Sort _sort) +{ + switch (_sort) + { + case Sort::Bool: + return m_context.bool_sort(); + case Sort::Int: + return m_context.int_sort(); + default: + break; + } + solAssert(false, ""); + // Cannot be reached. + return m_context.int_sort(); +} diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h new file mode 100644 index 00000000..44d4bb2f --- /dev/null +++ b/libsolidity/formal/Z3Interface.h @@ -0,0 +1,65 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SolverInterface.h> + +#include <boost/noncopyable.hpp> + +#include <z3++.h> + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +class Z3Interface: public SolverInterface, public boost::noncopyable +{ +public: + Z3Interface(); + + void reset() override; + + void push() override; + void pop() override; + + Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override; + Expression newInteger(std::string _name) override; + Expression newBool(std::string _name) override; + + void addAssertion(Expression const& _expr) override; + std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override; + +private: + z3::expr toZ3Expr(Expression const& _expr); + z3::sort z3Sort(smt::Sort _sort); + + std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate); + std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end); + + z3::context m_context; + z3::solver m_solver; + std::map<std::string, z3::expr> m_constants; + std::map<std::string, z3::func_decl> m_functions; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 74743737..6d0c0255 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -52,7 +52,7 @@ using namespace dev::solidity::assembly; class EthAssemblyAdapter: public julia::AbstractAssembly { public: - EthAssemblyAdapter(eth::Assembly& _assembly): + explicit EthAssemblyAdapter(eth::Assembly& _assembly): m_assembly(_assembly) { } @@ -127,7 +127,7 @@ public: } private: - LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const + static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) { u256 id = _tag.data(); solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large."); diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 133f70b1..d84fe999 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -23,6 +23,9 @@ #include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/parsing/Scanner.h> #include <libsolidity/interface/ErrorReporter.h> + +#include <boost/algorithm/string.hpp> + #include <ctype.h> #include <algorithm> @@ -33,6 +36,7 @@ using namespace dev::solidity::assembly; shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scanner) { + m_recursionDepth = 0; try { m_scanner = _scanner; @@ -48,6 +52,7 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann assembly::Block Parser::parseBlock() { + RecursionGuard recursionGuard(*this); assembly::Block block = createWithLocation<Block>(); expectToken(Token::LBrace); while (currentToken() != Token::RBrace) @@ -59,6 +64,7 @@ assembly::Block Parser::parseBlock() assembly::Statement Parser::parseStatement() { + RecursionGuard recursionGuard(*this); switch (currentToken()) { case Token::Let: @@ -155,6 +161,7 @@ assembly::Statement Parser::parseStatement() assembly::Case Parser::parseCase() { + RecursionGuard recursionGuard(*this); assembly::Case _case = createWithLocation<assembly::Case>(); if (m_scanner->currentToken() == Token::Default) m_scanner->next(); @@ -175,6 +182,7 @@ assembly::Case Parser::parseCase() assembly::ForLoop Parser::parseForLoop() { + RecursionGuard recursionGuard(*this); ForLoop forLoop = createWithLocation<ForLoop>(); expectToken(Token::For); forLoop.pre = parseBlock(); @@ -189,6 +197,7 @@ assembly::ForLoop Parser::parseForLoop() assembly::Statement Parser::parseExpression() { + RecursionGuard recursionGuard(*this); Statement operation = parseElementaryOperation(true); if (operation.type() == typeid(Instruction)) { @@ -251,6 +260,7 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames() assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { + RecursionGuard recursionGuard(*this); Statement ret; switch (currentToken()) { @@ -297,6 +307,8 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) kind = LiteralKind::String; break; case Token::Number: + if (!isValidNumberLiteral(currentLiteral())) + fatalParserError("Invalid number literal."); kind = LiteralKind::Number; break; case Token::TrueLiteral: @@ -337,6 +349,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) assembly::VariableDeclaration Parser::parseVariableDeclaration() { + RecursionGuard recursionGuard(*this); VariableDeclaration varDecl = createWithLocation<VariableDeclaration>(); expectToken(Token::Let); while (true) @@ -361,6 +374,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() assembly::FunctionDefinition Parser::parseFunctionDefinition() { + RecursionGuard recursionGuard(*this); FunctionDefinition funDef = createWithLocation<FunctionDefinition>(); expectToken(Token::Function); funDef.name = expectAsmIdentifier(); @@ -392,6 +406,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) { + RecursionGuard recursionGuard(*this); if (_instruction.type() == typeid(Instruction)) { solAssert(!m_julia, "Instructions are invalid in JULIA"); @@ -474,6 +489,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) TypedName Parser::parseTypedName() { + RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation<TypedName>(); typedName.name = expectAsmIdentifier(); if (m_julia) @@ -501,3 +517,19 @@ string Parser::expectAsmIdentifier() expectToken(Token::Identifier); return name; } + +bool Parser::isValidNumberLiteral(string const& _literal) +{ + try + { + u256(_literal); + } + catch (...) + { + return false; + } + if (boost::starts_with(_literal, "0x")) + return true; + else + return _literal.find_first_not_of("0123456789") == string::npos; +} diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 45708afd..e46d1732 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -45,7 +45,7 @@ public: protected: /// Creates an inline assembly node with the given source location. - template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) + template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const { T r; r.location = _loc; @@ -75,6 +75,8 @@ protected: TypedName parseTypedName(); std::string expectAsmIdentifier(); + static bool isValidNumberLiteral(std::string const& _literal); + private: bool m_julia = false; }; diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 4f96a3e9..47ede91d 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -209,7 +209,7 @@ string AsmPrinter::operator()(Block const& _block) return "{\n " + body + "\n}"; } -string AsmPrinter::appendTypeName(std::string const& _type) +string AsmPrinter::appendTypeName(std::string const& _type) const { if (m_julia) return ":" + _type; diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index f57dddc8..66520632 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -53,7 +53,7 @@ public: std::string operator()(assembly::Block const& _block); private: - std::string appendTypeName(std::string const& _type); + std::string appendTypeName(std::string const& _type) const; bool m_julia = false; }; diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp index 315d5953..64d5bd9a 100644 --- a/libsolidity/inlineasm/AsmScope.cpp +++ b/libsolidity/inlineasm/AsmScope.cpp @@ -70,7 +70,7 @@ Scope::Identifier* Scope::lookup(string const& _name) return nullptr; } -bool Scope::exists(string const& _name) +bool Scope::exists(string const& _name) const { if (identifiers.count(_name)) return true; diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h index cc240565..447d6490 100644 --- a/libsolidity/inlineasm/AsmScope.h +++ b/libsolidity/inlineasm/AsmScope.h @@ -107,7 +107,7 @@ struct Scope } /// @returns true if the name exists in this scope or in super scopes (also searches /// across function and assembly boundaries). - bool exists(std::string const& _name); + bool exists(std::string const& _name) const; /// @returns the number of variables directly registered inside the scope. size_t numberOfVariables() const; diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 12f958fc..3df9d1f8 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -19,7 +19,6 @@ */ #include <libsolidity/interface/ABI.h> -#include <boost/range/irange.hpp> #include <libsolidity/ast/AST.h> using namespace std; @@ -36,8 +35,10 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value method; method["type"] = "function"; method["name"] = it.second->declaration().name(); - method["constant"] = it.second->isConstant(); + // TODO: deprecate constant in a future release + method["constant"] = it.second->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View; method["payable"] = it.second->isPayable(); + method["stateMutability"] = stateMutabilityToString(it.second->stateMutability()); method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), externalFunctionType->parameterTypes(), @@ -57,6 +58,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["payable"] = externalFunction->isPayable(); + method["stateMutability"] = stateMutabilityToString(externalFunction->stateMutability()); method["inputs"] = formatTypeList( externalFunction->parameterNames(), externalFunction->parameterTypes(), @@ -71,6 +73,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value method; method["type"] = "fallback"; method["payable"] = externalFunctionType->isPayable(); + method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); abi.append(method); } for (auto const& it: _contractDef.interfaceEvents()) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 7c66a843..363f45dd 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -37,6 +37,7 @@ #include <libsolidity/analysis/PostTypeChecker.h> #include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/codegen/Compiler.h> +#include <libsolidity/formal/SMTChecker.h> #include <libsolidity/interface/ABI.h> #include <libsolidity/interface/Natspec.h> #include <libsolidity/interface/GasEstimator.h> @@ -49,8 +50,6 @@ #include <json/json.h> #include <boost/algorithm/string.hpp> -#include <boost/filesystem.hpp> - using namespace std; using namespace dev; @@ -240,6 +239,13 @@ bool CompilerStack::analyze() if (noErrors) { + SMTChecker smtChecker(m_errorReporter, m_smtQuery); + for (Source const* source: m_sourceOrder) + smtChecker.analyze(*source->ast); + } + + if (noErrors) + { m_stackState = AnalysisSuccessful; return true; } @@ -406,39 +412,42 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const return *_contract.abi; } -Json::Value const& CompilerStack::natspec(string const& _contractName, DocumentationType _type) const +Json::Value const& CompilerStack::natspecUser(string const& _contractName) const { - return natspec(contract(_contractName), _type); + return natspecUser(contract(_contractName)); } -Json::Value const& CompilerStack::natspec(Contract const& _contract, DocumentationType _type) const +Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const { if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); solAssert(_contract.contract, ""); - std::unique_ptr<Json::Value const>* doc; - // checks wheather we already have the documentation - switch (_type) - { - case DocumentationType::NatspecUser: - doc = &_contract.userDocumentation; - // caches the result - if (!*doc) - doc->reset(new Json::Value(Natspec::userDocumentation(*_contract.contract))); - break; - case DocumentationType::NatspecDev: - doc = &_contract.devDocumentation; - // caches the result - if (!*doc) - doc->reset(new Json::Value(Natspec::devDocumentation(*_contract.contract))); - break; - default: - solAssert(false, "Illegal documentation type."); - } + // caches the result + if (!_contract.userDocumentation) + _contract.userDocumentation.reset(new Json::Value(Natspec::userDocumentation(*_contract.contract))); + + return *_contract.userDocumentation; +} - return *(*doc); +Json::Value const& CompilerStack::natspecDev(string const& _contractName) const +{ + return natspecDev(contract(_contractName)); +} + +Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const +{ + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + + solAssert(_contract.contract, ""); + + // caches the result + if (!_contract.devDocumentation) + _contract.devDocumentation.reset(new Json::Value(Natspec::devDocumentation(*_contract.contract))); + + return *_contract.devDocumentation; } Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const @@ -526,17 +535,17 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string if (m_sources.count(importPath) || newSources.count(importPath)) continue; - ReadFile::Result result{false, string("File not supplied initially.")}; + ReadCallback::Result result{false, string("File not supplied initially.")}; if (m_readFile) result = m_readFile(importPath); if (result.success) - newSources[importPath] = result.contentsOrErrorMessage; + newSources[importPath] = result.responseOrErrorMessage; else { m_errorReporter.parserError( import->location(), - string("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage) + string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage) ); continue; } @@ -632,6 +641,17 @@ string CompilerStack::absolutePath(string const& _path, string const& _reference return result.generic_string(); } +namespace +{ +bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& features) +{ + for (auto const feature: features) + if (!ExperimentalFeatureOnlyAnalysis.count(feature)) + return false; + return true; +} +} + void CompilerStack::compileContract( ContractDefinition const& _contract, map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts @@ -649,10 +669,23 @@ void CompilerStack::compileContract( shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); string metadata = createMetadata(compiledContract); - bytes cborEncodedMetadata = - // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} - bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + - dev::swarmHash(metadata).asBytes(); + bytes cborEncodedHash = + // CBOR-encoding of the key "bzzr0" + bytes{0x65, 'b', 'z', 'z', 'r', '0'}+ + // CBOR-encoding of the hash + bytes{0x58, 0x20} + dev::swarmHash(metadata).asBytes(); + bytes cborEncodedMetadata; + if (onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures)) + cborEncodedMetadata = + // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} + bytes{0xa1} + + cborEncodedHash; + else + cborEncodedMetadata = + // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true} + bytes{0xa2} + + cborEncodedHash + + bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large"); // 16-bit big endian length cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); @@ -795,8 +828,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["settings"]["libraries"][library.first] = "0x" + toHex(library.second.asBytes()); meta["output"]["abi"] = contractABI(_contract); - meta["output"]["userdoc"] = natspec(_contract, DocumentationType::NatspecUser); - meta["output"]["devdoc"] = natspec(_contract, DocumentationType::NatspecDev); + meta["output"]["userdoc"] = natspecUser(_contract); + meta["output"]["devdoc"] = natspecDev(_contract); return jsonCompactPrint(meta); } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index d287f224..361b8a45 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -63,12 +63,6 @@ class Natspec; class Error; class DeclarationContainer; -enum class DocumentationType: uint8_t -{ - NatspecUser = 1, - NatspecDev -}; - /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. * It holds state and can be used to either step through the compilation stages (and abort e.g. @@ -88,13 +82,13 @@ public: /// 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(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): m_readFile(_readFile), m_errorList(), m_errorReporter(m_errorList) {} /// @returns the list of errors that occured during parsing and type checking. - ErrorList const& errors() { return m_errorReporter.errors(); } + ErrorList const& errors() const { return m_errorReporter.errors(); } /// @returns the current state. State state() const { return m_stackState; } @@ -203,11 +197,13 @@ public: /// Prerequisite: Successful call to parse or compile. Json::Value const& contractABI(std::string const& _contractName = "") const; - /// @returns a JSON representing the contract's documentation. + /// @returns a JSON representing the contract's user documentation. + /// Prerequisite: Successful call to parse or compile. + Json::Value const& natspecUser(std::string const& _contractName) const; + + /// @returns a JSON representing the contract's developer 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& natspec(std::string const& _contractName, DocumentationType _type) const; + Json::Value const& natspecDev(std::string const& _contractName) const; /// @returns a JSON representing a map of method identifiers (hashes) to function names. Json::Value methodIdentifiers(std::string const& _contractName) const; @@ -274,7 +270,8 @@ private: std::string createMetadata(Contract const& _contract) const; std::string computeSourceMapping(eth::AssemblyItems const& _items) const; Json::Value const& contractABI(Contract const&) const; - Json::Value const& natspec(Contract const&, DocumentationType _type) const; + Json::Value const& natspecUser(Contract const&) const; + Json::Value const& natspecDev(Contract const&) 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. @@ -290,7 +287,8 @@ private: std::string target; }; - ReadFile::Callback m_readFile; + ReadCallback::Callback m_readFile; + ReadCallback::Callback m_smtQuery; bool m_optimize = false; unsigned m_optimizeRuns = 200; std::map<std::string, h160> m_libraries; diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index 42b0c8b6..241d6b43 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -36,7 +36,7 @@ class ErrorReporter { public: - ErrorReporter(ErrorList& _errors): + explicit ErrorReporter(ErrorList& _errors): m_errorList(_errors) { } ErrorReporter& operator=(ErrorReporter const& _errorReporter); @@ -75,8 +75,8 @@ public: void typeError( SourceLocation const& _location, - SecondarySourceLocation const& _secondaryLocation, - std::string const& _description + SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(), + std::string const& _description = std::string() ); void typeError(SourceLocation const& _location, std::string const& _description); diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h index 2e8a6bd8..7068629d 100644 --- a/libsolidity/interface/ReadFile.h +++ b/libsolidity/interface/ReadFile.h @@ -27,17 +27,17 @@ namespace dev namespace solidity { -class ReadFile: boost::noncopyable +class ReadCallback: boost::noncopyable { public: - /// File reading result. + /// File reading or generic query result. struct Result { bool success; - std::string contentsOrErrorMessage; + std::string responseOrErrorMessage; }; - /// File reading callback. + /// File reading or generic query callback. using Callback = std::function<Result(std::string const&)>; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index dd135ce5..be823743 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -203,10 +203,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) for (auto const& url: sources[sourceName]["urls"]) { - ReadFile::Result result = m_readFile(url.asString()); + ReadCallback::Result result = m_readFile(url.asString()); if (result.success) { - if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage)) + if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) errors.append(formatError( false, "IOError", @@ -215,13 +215,13 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); else { - m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage); + m_compilerStack.addSource(sourceName, result.responseOrErrorMessage); found = true; break; } } else - failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage); + failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.responseOrErrorMessage); } for (auto const& failure: failures) @@ -394,8 +394,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value contractData(Json::objectValue); contractData["abi"] = m_compilerStack.contractABI(contractName); contractData["metadata"] = m_compilerStack.metadata(contractName); - contractData["userdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecUser); - contractData["devdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecDev); + contractData["userdoc"] = m_compilerStack.natspecUser(contractName); + contractData["devdoc"] = m_compilerStack.natspecDev(contractName); // EVM Json::Value evmData(Json::objectValue); diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index dfaf88cd..11a0b4c2 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -40,7 +40,7 @@ public: /// Creates a new StandardCompiler. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. - StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback()) + explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()) : m_compilerStack(_readFile), m_readFile(_readFile) { } @@ -56,7 +56,7 @@ private: Json::Value compileInternal(Json::Value const& _input); CompilerStack m_compilerStack; - ReadFile::Callback m_readFile; + ReadCallback::Callback m_readFile; }; } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index b98991f3..ddfdb667 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -40,7 +40,7 @@ namespace solidity class Parser::ASTNodeFactory { public: - ASTNodeFactory(Parser const& _parser): + explicit ASTNodeFactory(Parser const& _parser): m_parser(_parser), m_location(_parser.position(), -1, _parser.sourceName()) {} ASTNodeFactory(Parser const& _parser, ASTPointer<ASTNode> const& _childNode): m_parser(_parser), m_location(_childNode->location()) {} @@ -68,6 +68,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) { try { + m_recursionDepth = 0; m_scanner = _scanner; ASTNodeFactory nodeFactory(*this); vector<ASTPointer<ASTNode>> nodes; @@ -90,6 +91,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) fatalParserError(string("Expected pragma, import directive or contract/interface/library definition.")); } } + solAssert(m_recursionDepth == 0, ""); return nodeFactory.createNode<SourceUnit>(nodes); } catch (FatalError const&) @@ -102,6 +104,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) ASTPointer<PragmaDirective> Parser::parsePragmaDirective() { + RecursionGuard recursionGuard(*this); // pragma anything* ; // Currently supported: // pragma solidity ^0.4.0 || ^0.3.0; @@ -132,6 +135,7 @@ ASTPointer<PragmaDirective> Parser::parsePragmaDirective() ASTPointer<ImportDirective> Parser::parseImportDirective() { + RecursionGuard recursionGuard(*this); // import "abc" [as x]; // import * as x from "abc"; // import {a as b, c} from "abc"; @@ -212,6 +216,7 @@ ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docString; if (m_scanner->currentCommentLiteral() != "") @@ -275,6 +280,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName()); vector<ASTPointer<Expression>> arguments; @@ -307,8 +313,25 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) return visibility; } +StateMutability Parser::parseStateMutability(Token::Value _token) +{ + StateMutability stateMutability(StateMutability::NonPayable); + if (_token == Token::Payable) + stateMutability = StateMutability::Payable; + // FIXME: constant should be removed at the next breaking release + else if (_token == Token::View || _token == Token::Constant) + stateMutability = StateMutability::View; + else if (_token == Token::Pure) + stateMutability = StateMutability::Pure; + else + solAssert(false, "Invalid state mutability specifier."); + m_scanner->next(); + return stateMutability; +} + Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) { + RecursionGuard recursionGuard(*this); FunctionHeaderParserResult result; expectToken(Token::Function); if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) @@ -321,23 +344,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN while (true) { 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(); - } - else if (_allowModifiers && token == Token::Identifier) + if (_allowModifiers && token == Token::Identifier) { // This can either be a modifier (function declaration) or the name of the // variable (function type name plus variable). @@ -354,12 +361,30 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN { if (result.visibility != Declaration::Visibility::Default) { - parserError(string("Multiple visibility specifiers.")); + parserError(string( + "Visibility already specified as \"" + + Declaration::visibilityToString(result.visibility) + + "\"." + )); m_scanner->next(); } else result.visibility = parseVisibilitySpecifier(token); } + else if (Token::isStateMutabilitySpecifier(token)) + { + if (result.stateMutability != StateMutability::NonPayable) + { + parserError(string( + "State mutability already specified as \"" + + stateMutabilityToString(result.stateMutability) + + "\"." + )); + m_scanner->next(); + } + else + result.stateMutability = parseStateMutability(token); + } else break; } @@ -376,6 +401,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docstring; if (m_scanner->currentCommentLiteral() != "") @@ -404,13 +430,12 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A return nodeFactory.createNode<FunctionDefinition>( header.name, header.visibility, + header.stateMutability, c_isConstructor, docstring, header.parameters, - header.isDeclaredConst, header.modifiers, header.returnParameters, - header.isPayable, block ); } @@ -421,8 +446,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A header.parameters, header.returnParameters, header.visibility, - header.isDeclaredConst, - header.isPayable + header.stateMutability ); type = parseTypeNameSuffix(type, nodeFactory); VarDeclParserOptions options; @@ -436,6 +460,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A ASTPointer<StructDefinition> Parser::parseStructDefinition() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Struct); ASTPointer<ASTString> name = expectIdentifierToken(); @@ -453,6 +478,7 @@ ASTPointer<StructDefinition> Parser::parseStructDefinition() ASTPointer<EnumValue> Parser::parseEnumValue() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); nodeFactory.markEndPosition(); return nodeFactory.createNode<EnumValue>(expectIdentifierToken()); @@ -460,6 +486,7 @@ ASTPointer<EnumValue> Parser::parseEnumValue() ASTPointer<EnumDefinition> Parser::parseEnumDefinition() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Enum); ASTPointer<ASTString> name = expectIdentifierToken(); @@ -488,6 +515,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( ASTPointer<TypeName> const& _lookAheadArrayType ) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory = _lookAheadArrayType ? ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this); ASTPointer<TypeName> type; @@ -512,7 +540,11 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( { if (visibility != Declaration::Visibility::Default) { - parserError(string("Visibility already specified.")); + parserError(string( + "Visibility already specified as \"" + + Declaration::visibilityToString(visibility) + + "\"." + )); m_scanner->next(); } else @@ -522,7 +554,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( { if (_options.allowIndexed && token == Token::Indexed) isIndexed = true; - else if (token == Token::Const) + else if (token == Token::Constant) isDeclaredConst = true; else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token)) { @@ -576,6 +608,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() { + RecursionGuard recursionGuard(*this); ScopeGuard resetModifierFlag([this]() { m_insideModifier = false; }); m_insideModifier = true; @@ -603,6 +636,7 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() ASTPointer<EventDefinition> Parser::parseEventDefinition() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docstring; if (m_scanner->currentCommentLiteral() != "") @@ -632,6 +666,7 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition() ASTPointer<UsingForDirective> Parser::parseUsingDirective() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Using); @@ -649,6 +684,7 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective() ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<Identifier> name(parseIdentifier()); vector<ASTPointer<Expression>> arguments; @@ -666,6 +702,7 @@ ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() ASTPointer<Identifier> Parser::parseIdentifier() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); nodeFactory.markEndPosition(); return nodeFactory.createNode<Identifier>(expectIdentifierToken()); @@ -673,6 +710,7 @@ ASTPointer<Identifier> Parser::parseIdentifier() ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); nodeFactory.markEndPosition(); vector<ASTString> identifierPath{*expectIdentifierToken()}; @@ -687,6 +725,7 @@ ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName() ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTNodeFactory& nodeFactory) { + RecursionGuard recursionGuard(*this); while (m_scanner->currentToken() == Token::LBrack) { m_scanner->next(); @@ -702,6 +741,7 @@ ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTN ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<TypeName> type; Token::Value token = m_scanner->currentToken(); @@ -737,19 +777,20 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) ASTPointer<FunctionTypeName> Parser::parseFunctionType() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); FunctionHeaderParserResult header = parseFunctionHeader(true, false); return nodeFactory.createNode<FunctionTypeName>( header.parameters, header.returnParameters, header.visibility, - header.isDeclaredConst, - header.isPayable + header.stateMutability ); } ASTPointer<Mapping> Parser::parseMapping() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Mapping); expectToken(Token::LParen); @@ -776,6 +817,7 @@ ASTPointer<ParameterList> Parser::parseParameterList( bool _allowEmpty ) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); vector<ASTPointer<VariableDeclaration>> parameters; VarDeclParserOptions options(_options); @@ -797,6 +839,7 @@ ASTPointer<ParameterList> Parser::parseParameterList( ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::LBrace); vector<ASTPointer<Statement>> statements; @@ -809,6 +852,7 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString) ASTPointer<Statement> Parser::parseStatement() { + RecursionGuard recursionGuard(*this); ASTPointer<ASTString> docString; if (m_scanner->currentCommentLiteral() != "") docString = make_shared<ASTString>(m_scanner->currentCommentLiteral()); @@ -871,6 +915,7 @@ ASTPointer<Statement> Parser::parseStatement() ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Assembly); if (m_scanner->currentToken() == Token::StringLiteral) @@ -888,6 +933,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::If); expectToken(Token::LParen); @@ -908,6 +954,7 @@ ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _d ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::While); expectToken(Token::LParen); @@ -920,6 +967,7 @@ ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> con ASTPointer<WhileStatement> Parser::parseDoWhileStatement(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Do); ASTPointer<Statement> body = parseStatement(); @@ -935,6 +983,7 @@ ASTPointer<WhileStatement> Parser::parseDoWhileStatement(ASTPointer<ASTString> c ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); ASTPointer<Statement> initExpression; ASTPointer<Expression> conditionExpression; @@ -968,6 +1017,7 @@ ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const& ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString) { + RecursionGuard recursionGuard(*this); // These two cases are very hard to distinguish: // x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9; // In the first case, x is a type name, in the second it is the name of a variable. @@ -1030,6 +1080,7 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme ASTPointer<TypeName> const& _lookAheadArrayType ) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); if (_lookAheadArrayType) nodeFactory.setLocation(_lookAheadArrayType->location()); @@ -1093,6 +1144,7 @@ ASTPointer<ExpressionStatement> Parser::parseExpressionStatement( ASTPointer<Expression> const& _lookAheadIndexAccessStructure ) { + RecursionGuard recursionGuard(*this); ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure); return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(_docString, expression); } @@ -1101,6 +1153,7 @@ ASTPointer<Expression> Parser::parseExpression( ASTPointer<Expression> const& _lookAheadIndexAccessStructure ) { + RecursionGuard recursionGuard(*this); ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure); if (Token::isAssignmentOp(m_scanner->currentToken())) { @@ -1129,6 +1182,7 @@ ASTPointer<Expression> Parser::parseBinaryExpression( ASTPointer<Expression> const& _lookAheadIndexAccessStructure ) { + RecursionGuard recursionGuard(*this); ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure); ASTNodeFactory nodeFactory(*this, expression); int precedence = Token::precedence(m_scanner->currentToken()); @@ -1148,6 +1202,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression( ASTPointer<Expression> const& _lookAheadIndexAccessStructure ) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); Token::Value token = m_scanner->currentToken(); @@ -1176,6 +1231,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression( ASTPointer<Expression> const& _lookAheadIndexAccessStructure ) { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); @@ -1233,6 +1289,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression( ASTPointer<Expression> Parser::parsePrimaryExpression() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); Token::Value token = m_scanner->currentToken(); ASTPointer<Expression> expression; @@ -1292,10 +1349,11 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() parserError("Expected expression (inline array elements cannot be omitted)."); else components.push_back(ASTPointer<Expression>()); + if (m_scanner->currentToken() == oppositeToken) break; - else if (m_scanner->currentToken() == Token::Comma) - m_scanner->next(); + + expectToken(Token::Comma); } nodeFactory.markEndPosition(); expectToken(oppositeToken); @@ -1322,6 +1380,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments() { + RecursionGuard recursionGuard(*this); vector<ASTPointer<Expression>> arguments; if (m_scanner->currentToken() != Token::RParen) { @@ -1337,6 +1396,7 @@ vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments() pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::parseFunctionCallArguments() { + RecursionGuard recursionGuard(*this); pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> ret; Token::Value token = m_scanner->currentToken(); if (token == Token::LBrace) @@ -1403,6 +1463,7 @@ ASTPointer<TypeName> Parser::typeNameIndexAccessStructure( ) { solAssert(!_path.empty(), ""); + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); SourceLocation location = _path.front()->location(); location.end = _path.back()->location().end; @@ -1435,6 +1496,7 @@ ASTPointer<Expression> Parser::expressionFromIndexAccessStructure( ) { solAssert(!_path.empty(), ""); + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this, _path.front()); ASTPointer<Expression> expression(_path.front()); for (size_t i = 1; i < _path.size(); ++i) @@ -1458,6 +1520,7 @@ ASTPointer<Expression> Parser::expressionFromIndexAccessStructure( ASTPointer<ParameterList> Parser::createEmptyParameterList() { + RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); nodeFactory.setLocationEmpty(); return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>()); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 19631c58..cfdfea7e 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -35,7 +35,7 @@ class Scanner; class Parser: public ParserBase { public: - Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {} + explicit Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {} ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner); @@ -60,8 +60,7 @@ private: ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> returnParameters; Declaration::Visibility visibility = Declaration::Visibility::Default; - bool isDeclaredConst = false; - bool isPayable = false; + StateMutability stateMutability = StateMutability::NonPayable; std::vector<ASTPointer<ModifierInvocation>> modifiers; }; @@ -73,6 +72,7 @@ private: ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + StateMutability parseStateMutability(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 5657c2c0..fe95b0fe 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -101,6 +101,19 @@ void ParserBase::expectToken(Token::Value _value) m_scanner->next(); } +void ParserBase::increaseRecursionDepth() +{ + m_recursionDepth++; + if (m_recursionDepth >= 3000) + fatalParserError("Maximum recursion depth reached during parsing."); +} + +void ParserBase::decreaseRecursionDepth() +{ + solAssert(m_recursionDepth > 0, ""); + m_recursionDepth--; +} + void ParserBase::parserError(string const& _description) { m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description); diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h index 5b03ab5e..fd0de0d1 100644 --- a/libsolidity/parsing/ParserBase.h +++ b/libsolidity/parsing/ParserBase.h @@ -36,11 +36,25 @@ class Scanner; class ParserBase { public: - ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} + explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} std::shared_ptr<std::string const> const& sourceName() const; protected: + /// Utility class that creates an error and throws an exception if the + /// recursion depth is too deep. + class RecursionGuard + { + public: + explicit RecursionGuard(ParserBase& _parser): m_parser(_parser) + { + m_parser.increaseRecursionDepth(); + } + ~RecursionGuard() { m_parser.decreaseRecursionDepth(); } + private: + ParserBase& m_parser; + }; + /// Start position of the current token int position() const; /// End position of the current token @@ -56,6 +70,10 @@ protected: Token::Value advance(); ///@} + /// Increases the recursion depth and throws an exception if it is too deep. + void increaseRecursionDepth(); + void decreaseRecursionDepth(); + /// Creates a @ref ParserError and annotates it with the current position and the /// given @a _description. void parserError(std::string const& _description); @@ -67,6 +85,8 @@ protected: std::shared_ptr<Scanner> m_scanner; /// The reference to the list of errors and warning to add errors/warnings during parsing ErrorReporter& m_errorReporter; + /// Current recursion depth during parsing. + size_t m_recursionDepth = 0; }; } diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h index d6b48c6f..0adaa6fd 100644 --- a/libsolidity/parsing/Scanner.h +++ b/libsolidity/parsing/Scanner.h @@ -75,7 +75,7 @@ public: int position() const { return m_position; } bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); } char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } - char advanceAndGet(size_t _chars=1); + char advanceAndGet(size_t _chars = 1); char rollback(size_t _amount); void reset() { m_position = 0; } @@ -118,11 +118,11 @@ public: ///@name Information about the current token /// @returns the current token - Token::Value currentToken() + Token::Value currentToken() const { return m_currentToken.token; } - ElementaryTypeNameToken currentElementaryTypeNameToken() + ElementaryTypeNameToken currentElementaryTypeNameToken() const { unsigned firstSize; unsigned secondSize; @@ -219,8 +219,8 @@ private: bool scanEscape(); /// Return the current source position. - int sourcePos() { return m_source.position(); } - bool isSourcePastEndOfInput() { return m_source.isPastEndOfInput(); } + int sourcePos() const { return m_source.position(); } + bool isSourcePastEndOfInput() const { return m_source.isPastEndOfInput(); } TokenDesc m_skippedComment; // desc for current skipped comment TokenDesc m_nextSkippedComment; // desc for next skiped comment diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index d412b3f0..805fbf5d 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -143,7 +143,7 @@ namespace solidity K(As, "as", 0) \ K(Assembly, "assembly", 0) \ K(Break, "break", 0) \ - K(Const, "constant", 0) \ + K(Constant, "constant", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Do, "do", 0) \ @@ -169,6 +169,7 @@ namespace solidity K(Public, "public", 0) \ K(Pragma, "pragma", 0) \ K(Private, "private", 0) \ + K(Pure, "pure", 0) \ K(Return, "return", 0) \ K(Returns, "returns", 0) \ K(Storage, "storage", 0) \ @@ -176,6 +177,7 @@ namespace solidity K(Throw, "throw", 0) \ K(Using, "using", 0) \ K(Var, "var", 0) \ + K(View, "view", 0) \ K(While, "while", 0) \ \ /* Ether subdenominations */ \ @@ -229,14 +231,12 @@ namespace solidity K(Match, "match", 0) \ K(NullLiteral, "null", 0) \ K(Of, "of", 0) \ - K(Pure, "pure", 0) \ K(Relocatable, "relocatable", 0) \ K(Static, "static", 0) \ K(Switch, "switch", 0) \ K(Try, "try", 0) \ K(Type, "type", 0) \ K(TypeOf, "typeof", 0) \ - K(View, "view", 0) \ /* Illegal token - not able to scan. */ \ T(Illegal, "ILLEGAL", 0) \ \ @@ -290,6 +290,7 @@ public: static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; } static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; } static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage; } + static bool isStateMutabilitySpecifier(Value op) { return op == Pure || op == Constant || op == View || op == Payable; } static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; } static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; } static bool isReservedKeyword(Value op) { return (Abstract <= op && op <= TypeOf); } |
