diff options
Diffstat (limited to 'libsolidity')
41 files changed, 854 insertions, 295 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 4d546e68..83f37f47 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -28,51 +28,42 @@ using namespace std; using namespace dev; using namespace dev::solidity; -/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { - TypePointer const& subType = _operation.subExpression().annotation().type; - if (!dynamic_cast<RationalNumberType const*>(subType.get())) - m_errorReporter.fatalTypeError(_operation.subExpression().location(), "Invalid constant expression."); - TypePointer t = subType->unaryOperatorResult(_operation.getOperator()); - _operation.annotation().type = t; + auto sub = type(_operation.subExpression()); + if (sub) + setType(_operation, sub->unaryOperatorResult(_operation.getOperator())); } -/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { - TypePointer const& leftType = _operation.leftExpression().annotation().type; - TypePointer const& rightType = _operation.rightExpression().annotation().type; - if (!dynamic_cast<RationalNumberType const*>(leftType.get())) - m_errorReporter.fatalTypeError(_operation.leftExpression().location(), "Invalid constant expression."); - if (!dynamic_cast<RationalNumberType const*>(rightType.get())) - m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression."); - TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - if (!commonType) + auto left = type(_operation.leftExpression()); + auto right = type(_operation.rightExpression()); + if (left && right) { - m_errorReporter.typeError( - _operation.location(), - "Operator " + - string(Token::toString(_operation.getOperator())) + - " not compatible with types " + - leftType->toString() + - " and " + - rightType->toString() + auto commonType = left->binaryOperatorResult(_operation.getOperator(), right); + if (!commonType) + m_errorReporter.fatalTypeError( + _operation.location(), + "Operator " + + string(Token::toString(_operation.getOperator())) + + " not compatible with types " + + left->toString() + + " and " + + right->toString() + ); + setType( + _operation, + Token::isCompareOp(_operation.getOperator()) ? + make_shared<BoolType>() : + commonType ); - commonType = leftType; } - _operation.annotation().commonType = commonType; - _operation.annotation().type = - Token::isCompareOp(_operation.getOperator()) ? - make_shared<BoolType>() : - commonType; } void ConstantEvaluator::endVisit(Literal const& _literal) { - _literal.annotation().type = Type::forLiteral(_literal); - if (!_literal.annotation().type) - m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); + setType(_literal, Type::forLiteral(_literal)); } void ConstantEvaluator::endVisit(Identifier const& _identifier) @@ -81,18 +72,34 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier) if (!variableDeclaration) return; if (!variableDeclaration->isConstant()) - m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant."); + return; - ASTPointer<Expression> value = variableDeclaration->value(); + ASTPointer<Expression> const& value = variableDeclaration->value(); if (!value) - m_errorReporter.fatalTypeError(_identifier.location(), "Constant identifier declaration must have a constant value."); - - if (!value->annotation().type) + return; + else if (!m_types->count(value.get())) { if (m_depth > 32) m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted)."); - ConstantEvaluator e(*value, m_errorReporter, m_depth + 1); + ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value); } - _identifier.annotation().type = value->annotation().type; + setType(_identifier, type(*value)); +} + +void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type) +{ + if (_type && _type->category() == Type::Category::RationalNumber) + (*m_types)[&_node] = _type; +} + +TypePointer ConstantEvaluator::type(ASTNode const& _node) +{ + return (*m_types)[&_node]; +} + +TypePointer ConstantEvaluator::evaluate(Expression const& _expr) +{ + _expr.accept(*this); + return type(_expr); } diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index 6725d610..77a357b6 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -38,22 +38,32 @@ class TypeChecker; class ConstantEvaluator: private ASTConstVisitor { public: - ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter, size_t _newDepth = 0): + ConstantEvaluator( + ErrorReporter& _errorReporter, + size_t _newDepth = 0, + std::shared_ptr<std::map<ASTNode const*, TypePointer>> _types = std::make_shared<std::map<ASTNode const*, TypePointer>>() + ): m_errorReporter(_errorReporter), - m_depth(_newDepth) + m_depth(_newDepth), + m_types(_types) { - _expr.accept(*this); } + TypePointer evaluate(Expression const& _expr); + private: virtual void endVisit(BinaryOperation const& _operation); virtual void endVisit(UnaryOperation const& _operation); virtual void endVisit(Literal const& _literal); virtual void endVisit(Identifier const& _identifier); + void setType(ASTNode const& _node, TypePointer const& _type); + TypePointer type(ASTNode const& _node); + ErrorReporter& m_errorReporter; /// Current recursion depth. - size_t m_depth; + size_t m_depth = 0; + std::shared_ptr<std::map<ASTNode const*, TypePointer>> m_types; }; } diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index b33c8568..7508ad9e 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -23,6 +23,7 @@ #include <libsolidity/analysis/DeclarationContainer.h> #include <libsolidity/ast/AST.h> #include <libsolidity/ast/Types.h> +#include <libdevcore/StringUtils.h> using namespace std; using namespace dev; @@ -105,7 +106,7 @@ bool DeclarationContainer::registerDeclaration( return true; } -std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const +vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const { solAssert(!_name.empty(), "Attempt to resolve empty name."); auto result = m_declarations.find(_name); @@ -115,3 +116,22 @@ std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString cons return m_enclosingContainer->resolveName(_name, true); return vector<Declaration const*>({}); } + +vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const +{ + static size_t const MAXIMUM_EDIT_DISTANCE = 2; + + vector<ASTString> similar; + + for (auto const& declaration: m_declarations) + { + string const& declarationName = declaration.first; + if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE)) + similar.push_back(declarationName); + } + + if (m_enclosingContainer) + similar += m_enclosingContainer->similarNames(_name); + + return similar; +} diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index 301998b7..f9b1bda4 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -58,6 +58,10 @@ public: /// @returns whether declaration is valid, and if not also returns previous declaration. Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const; + /// @returns existing declaration names similar to @a _name. + /// Searches this and all parent containers. + std::vector<ASTString> similarNames(ASTString const& _name) const; + private: ASTNode const* m_enclosingNode; DeclarationContainer const* m_enclosingContainer; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 5d010693..5e4d414b 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -25,6 +25,7 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/interface/ErrorReporter.h> +#include <libdevcore/StringUtils.h> #include <boost/algorithm/string.hpp> @@ -425,6 +426,11 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer return result; } +string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const +{ + return quotedAlternativesList(m_currentScope->similarNames(_name)); +} + DeclarationRegistrationHelper::DeclarationRegistrationHelper( map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, ASTNode& _astRoot, diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index d83697cd..9aea07ab 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -93,6 +93,9 @@ public: /// Generate and store warnings about variables that are named like instructions. void warnVariablesNamedLikeInstructions(); + /// @returns a list of similar identifiers in the current and enclosing scopes. May return empty string if no suggestions. + std::string similarNameSuggestions(ASTString const& _name) const; + private: /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index f22c95cc..0bb5e3fe 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -47,7 +47,13 @@ bool ReferencesResolver::visit(Identifier const& _identifier) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name()); if (declarations.empty()) - fatalDeclarationError(_identifier.location(), "Undeclared identifier."); + { + string suggestions = m_resolver.similarNameSuggestions(_identifier.name()); + string errorMessage = + "Undeclared identifier." + + (suggestions.empty()? "": " Did you mean " + std::move(suggestions) + "?"); + declarationError(_identifier.location(), errorMessage); + } else if (declarations.size() == 1) _identifier.annotation().referencedDeclaration = declarations.front(); else @@ -90,7 +96,10 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) { Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath()); if (!declaration) - fatalDeclarationError(_typeName.location(), "Identifier not found or not unique."); + { + declarationError(_typeName.location(), "Identifier not found or not unique."); + return; + } _typeName.annotation().referencedDeclaration = declaration; @@ -101,7 +110,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration)) _typeName.annotation().type = make_shared<ContractType>(*contract); else - fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); + typeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) @@ -112,17 +121,25 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) case VariableDeclaration::Visibility::External: break; default: - fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + return; } if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) - fatalTypeError(_typeName.location(), "Only external function types can be payable."); + { + typeError(_typeName.location(), "Only external function types can be payable."); + return; + } + if (_typeName.visibility() == VariableDeclaration::Visibility::External) for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); if (!t->annotation().type->canBeUsedExternally(false)) - fatalTypeError(t->location(), "Internal type cannot be used for external function type."); + { + typeError(t->location(), "Internal type cannot be used for external function type."); + return; + } } _typeName.annotation().type = make_shared<FunctionType>(_typeName); @@ -142,15 +159,21 @@ void ReferencesResolver::endVisit(Mapping const& _typeName) void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) { TypePointer baseType = _typeName.baseType().annotation().type; + if (!baseType) + { + solAssert(!m_errorReporter.errors().empty(), ""); + return; + } if (baseType->storageBytes() == 0) fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array."); if (Expression const* length = _typeName.length()) { - if (!length->annotation().type) - ConstantEvaluator e(*length, m_errorReporter); - auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get()); + TypePointer lengthTypeGeneric = length->annotation().type; + if (!lengthTypeGeneric) + lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length); + RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric.get()); if (!lengthType || !lengthType->mobileType()) - fatalTypeError(length->location(), "Invalid array length, expected integer literal."); + fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression."); else if (lengthType->isFractional()) fatalTypeError(length->location(), "Array with fractional length specified."); else if (lengthType->isNegative()) @@ -206,7 +229,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations()); + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } @@ -321,17 +344,13 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) type = ref->copyForLocation(typeLoc, isPointer); } else if (varLoc != Location::Default && !ref) - fatalTypeError(_variable.location(), "Storage location can only be given for array or struct types."); - - if (!type) - fatalTypeError(_variable.location(), "Invalid type name."); + typeError(_variable.location(), "Storage location can only be given for array or struct types."); + _variable.annotation().type = type; } else if (!_variable.canHaveAutoType()) - fatalTypeError(_variable.location(), "Explicit type needed."); + typeError(_variable.location(), "Explicit type needed."); // otherwise we have a "var"-declaration whose type is resolved by the first assignment - - _variable.annotation().type = type; } void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description) diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index b6cc04da..5a3745b0 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -224,3 +224,17 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node) return true; } + +bool SyntaxChecker::visit(VariableDeclaration const& _declaration) +{ + bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + + if (!_declaration.typeName()) + { + if (v050) + m_errorReporter.syntaxError(_declaration.location(), "Use of the \"var\" keyword is deprecated."); + else + m_errorReporter.warning(_declaration.location(), "Use of the \"var\" keyword is deprecated."); + } + return true; +} diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index d5d72f14..871bf0a9 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -69,6 +69,8 @@ private: virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(FunctionTypeName const& _node) override; + virtual bool visit(VariableDeclaration const& _declaration) override; + ErrorReporter& m_errorReporter; /// Flag that indicates whether a function modifier actually contains '_'. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 73047e76..d67142e4 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -171,13 +171,7 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con ssl.append("Another declaration is here:", (*it)->location()); string msg = "More than one constructor defined."; - size_t occurrences = ssl.infos.size(); - if (occurrences > 32) - { - ssl.infos.resize(32); - msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences."; - } - + ssl.limitSize(msg); m_errorReporter.declarationError( functions[_contract.name()].front()->location(), ssl, @@ -219,12 +213,7 @@ void TypeChecker::findDuplicateDefinitions(map<string, vector<T>> const& _defini if (ssl.infos.size() > 0) { - size_t occurrences = ssl.infos.size(); - if (occurrences > 32) - { - ssl.infos.resize(32); - _message += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences."; - } + ssl.limitSize(_message); m_errorReporter.declarationError( overloads[i]->location(), @@ -570,6 +559,17 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); + if ( + _function.visibility() > FunctionDefinition::Visibility::Internal && + type(*var)->category() == Type::Category::Struct && + !type(*var)->dataStoredIn(DataLocation::Storage) && + !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) + ) + m_errorReporter.typeError( + var->location(), + "Structs are only supported in the new experimental ABI encoder. " + "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." + ); var->accept(*this); } @@ -604,6 +604,8 @@ bool TypeChecker::visit(FunctionDefinition const& _function) { if (_function.visibility() < FunctionDefinition::Visibility::Public) m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private."); + else if (_function.visibility() != FunctionDefinition::Visibility::External) + m_errorReporter.warning(_function.location(), "Functions in interfaces should be declared external."); } if (_function.isConstructor()) m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces."); @@ -873,7 +875,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, - false, + assembly::AsmFlavour::Loose, identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) @@ -1060,7 +1062,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) _statement.initialValue()->location(), "Invalid rational " + valueComponentType->toString() + - " (absolute value too large or divison by zero)." + " (absolute value too large or division by zero)." ); else solAssert(false, ""); @@ -1551,8 +1553,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size()) { + bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall; + string msg = - "Wrong argument count for function call: " + + "Wrong argument count for " + + string(isStructConstructorCall ? "struct constructor" : "function call") + + ": " + toString(arguments.size()) + " arguments given but expected " + toString(parameterTypes.size()) + @@ -1668,10 +1674,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) SecondarySourceLocation ssl; for (auto function: contract->annotation().unimplementedFunctions) ssl.append("Missing implementation:", function->location()); + string msg = "Trying to create an instance of an abstract contract."; + ssl.limitSize(msg); m_errorReporter.typeError( _newExpression.location(), ssl, - "Trying to create an instance of an abstract contract." + msg ); } if (!contract->constructorIsPublic()) diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 7e41fc16..13c3ab68 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -40,19 +40,20 @@ public: void operator()(assembly::Label const&) { } void operator()(assembly::Instruction const& _instruction) { - if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction)) - m_reportMutability(StateMutability::NonPayable, _instruction.location); - else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction)) - m_reportMutability(StateMutability::View, _instruction.location); + checkInstruction(_instruction.location, _instruction.instruction); } void operator()(assembly::Literal const&) {} void operator()(assembly::Identifier const&) {} void operator()(assembly::FunctionalInstruction const& _instr) { - (*this)(_instr.instruction); + checkInstruction(_instr.location, _instr.instruction); for (auto const& arg: _instr.arguments) boost::apply_visitor(*this, arg); } + void operator()(assembly::ExpressionStatement const& _expr) + { + boost::apply_visitor(*this, _expr.expression); + } void operator()(assembly::StackAssignment const&) {} void operator()(assembly::Assignment const& _assignment) { @@ -102,6 +103,13 @@ public: private: std::function<void(StateMutability, SourceLocation const&)> m_reportMutability; + void checkInstruction(SourceLocation _location, solidity::Instruction _instruction) + { + if (eth::SemanticInformation::invalidInViewFunctions(_instruction)) + m_reportMutability(StateMutability::NonPayable, _location); + else if (eth::SemanticInformation::invalidInPureFunctions(_instruction)) + m_reportMutability(StateMutability::View, _location); + } }; } @@ -267,6 +275,22 @@ void ViewPureChecker::endVisit(FunctionCall const& _functionCall) reportMutability(mut, _functionCall.location()); } +bool ViewPureChecker::visit(MemberAccess const& _memberAccess) +{ + // Catch the special case of `this.f.selector` which is a pure expression. + ASTString const& member = _memberAccess.memberName(); + if ( + _memberAccess.expression().annotation().type->category() == Type::Category::Function && + member == "selector" + ) + if (auto const* expr = dynamic_cast<MemberAccess const*>(&_memberAccess.expression())) + if (auto const* exprInt = dynamic_cast<Identifier const*>(&expr->expression())) + if (exprInt->name() == "this") + // Do not continue visiting. + return false; + return true; +} + void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) { StateMutability mutability = StateMutability::Pure; diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index fec060b6..0b882cd8 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -56,6 +56,7 @@ private: virtual bool visit(ModifierDefinition const& _modifierDef) override; virtual void endVisit(ModifierDefinition const& _modifierDef) override; virtual void endVisit(Identifier const& _identifier) override; + virtual bool visit(MemberAccess const& _memberAccess) override; virtual void endVisit(MemberAccess const& _memberAccess) override; virtual void endVisit(IndexAccess const& _indexAccess) override; virtual void endVisit(ModifierInvocation const& _modifier) override; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 21daac2c..e4b7e4fd 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -949,11 +949,25 @@ bool RationalNumberType::operator==(Type const& _other) const return m_value == other.m_value; } +string RationalNumberType::bigintToReadableString(dev::bigint const& _num) +{ + string str = _num.str(); + if (str.size() > 32) + { + int omitted = str.size() - 8; + str = str.substr(0, 4) + "...(" + to_string(omitted) + " digits omitted)..." + str.substr(str.size() - 4, 4); + } + return str; +} + string RationalNumberType::toString(bool) const { if (!isFractional()) - return "int_const " + m_value.numerator().str(); - return "rational_const " + m_value.numerator().str() + '/' + m_value.denominator().str(); + return "int_const " + bigintToReadableString(m_value.numerator()); + + string numerator = bigintToReadableString(m_value.numerator()); + string denominator = bigintToReadableString(m_value.denominator()); + return "rational_const " + numerator + " / " + denominator; } u256 RationalNumberType::literalValue(Literal const*) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 635279ab..2e7d05ba 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -257,7 +257,7 @@ public: } virtual u256 literalValue(Literal const*) const { - solAssert(false, "Literal value requested for type without literals."); + solAssert(false, "Literal value requested for type without literals: " + toString(false)); } /// @returns a (simpler) type that is encoded in the same way for external function calls. @@ -437,6 +437,10 @@ private: /// @returns true if the literal is a valid rational number. static std::tuple<bool, rational> parseRational(std::string const& _value); + + /// @returns a truncated readable representation of the bigint keeping only + /// up to 4 leading and 4 trailing digits. + static std::string bigintToReadableString(dev::bigint const& num); }; /** diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 6648be06..00f59065 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -120,7 +120,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) Whiskers templ(R"( function <functionName>(headStart, dataEnd) -> <valueReturnParams> { - switch slt(sub(dataEnd, headStart), <minimumSize>) case 1 { revert(0, 0) } + if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) } <decodeElements> } )"); @@ -151,7 +151,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) R"( { let offset := <load>(add(headStart, <pos>)) - switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { revert(0, 0) } <values> := <abiDecode>(add(headStart, offset), dataEnd) } )" : @@ -1134,7 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from R"( // <readableTypeName> function <functionName>(offset, end) -> array { - switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } let length := <retrieveLength> array := <allocate>(<allocationSize>(length)) let dst := array @@ -1169,7 +1169,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from else { string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); - templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }"); + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }"); templ("retrieveElementPos", "src"); templ("baseEncodedSize", baseEncodedSize); } @@ -1197,11 +1197,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) templ = R"( // <readableTypeName> function <functionName>(offset, end) -> arrayPos, length { - switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } length := calldataload(offset) - switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { revert(0, 0) } arrayPos := add(offset, 0x20) - switch gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) case 1 { revert(0, 0) } + if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) } } )"; else @@ -1209,7 +1209,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) // <readableTypeName> function <functionName>(offset, end) -> arrayPos { arrayPos := offset - switch gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) case 1 { revert(0, 0) } + if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) } } )"; Whiskers w{templ}; @@ -1235,13 +1235,13 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ Whiskers templ( R"( function <functionName>(offset, end) -> array { - switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } let length := <load>(offset) array := <allocate>(<allocationSize>(length)) mstore(array, length) let src := add(offset, 0x20) let dst := add(array, 0x20) - switch gt(add(src, length), end) case 1 { revert(0, 0) } + if gt(add(src, length), end) { revert(0, 0) } <copyToMemFun>(src, dst, length) } )" @@ -1268,7 +1268,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr Whiskers templ(R"( // <readableTypeName> function <functionName>(headStart, end) -> value { - switch slt(sub(end, headStart), <minimumSize>) case 1 { revert(0, 0) } + if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) } value := <allocate>(<memorySize>) <#members> { @@ -1296,7 +1296,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr dynamic ? R"( let offset := <load>(add(headStart, <pos>)) - switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { revert(0, 0) } mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) )" : R"( @@ -1501,7 +1501,7 @@ string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) Whiskers w(R"( function <functionName>(length) -> size { // Make sure we can allocate memory without overflow - switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { revert(0, 0) } size := <allocationSize> <addLengthSlot> } @@ -1620,7 +1620,7 @@ string ABIFunctions::allocationFunction() memPtr := mload(<freeMemoryPointer>) let newFreePtr := add(memPtr, size) // protect against overflow - switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) } + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } mstore(<freeMemoryPointer>, newFreePtr) } )") diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 44264a07..d3afada5 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -51,6 +51,7 @@ void Compiler::compileClone( map<ContractDefinition const*, eth::Assembly const*> const& _contracts ) { + solAssert(!_contract.isLibrary(), ""); ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ce9c3b7f..7a88475a 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--"); - auto parserResult = assembly::Parser(errorReporter).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner); #ifdef SOL_OUTPUT_ASM cout << assembly::AsmPrinter()(*parserResult) << endl; #endif assembly::AsmAnalysisInfo analysisInfo; bool analyzerResult = false; if (parserResult) - analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult); + analyzerResult = assembly::AsmAnalyzer( + analysisInfo, + errorReporter, + assembly::AsmFlavour::Strict, + identifierAccess.resolve + ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) { string message = @@ -347,6 +352,9 @@ void CompilerContext::appendInlineAssembly( solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); + + // Reset the source location to the one of the node (instead of the CODEGEN source location) + updateSourceLocation(); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 7743fd3f..a155a3a5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -174,6 +174,9 @@ public: eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } + /// Appends a zero-address that can be replaced by something else at deploy time (if the + /// position in bytecode is known). + void appendDeployTimeAddress() { m_asm->append(eth::PushDeployTimeAddress); } /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes @@ -187,8 +190,8 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } - /// Appends inline assembly. @a _replacements are string-matching replacements that are performed - /// prior to parsing the inline assembly. + /// Appends inline assembly (strict mode). + /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly. /// @param _localVariables assigns stack positions to variables with the last one being the stack top /// @param _system if true, this is a "system-level" assembly where all functions use named labels. void appendInlineAssembly( diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a81ba518..f463db94 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -64,6 +64,12 @@ void ContractCompiler::compileContract( ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); + + if (_contract.isLibrary()) + // Check whether this is a call (true) or a delegatecall (false). + // This has to be the first code in the contract. + appendDelegatecallCheck(); + initializeContext(_contract, _contracts); appendFunctionSelector(_contract); appendMissingFunctions(); @@ -75,8 +81,13 @@ size_t ContractCompiler::compileConstructor( ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); - initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract); + if (_contract.isLibrary()) + return deployLibrary(_contract); + else + { + initializeContext(_contract, _contracts); + return packIntoContractCreator(_contract); + } } size_t ContractCompiler::compileClone( @@ -122,6 +133,7 @@ void ContractCompiler::appendCallValueCheck() void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) { + solAssert(!_contract.isLibrary(), "Tried to initialize library."); CompilerContext::LocationSetter locationSetter(m_context, _contract); // Determine the arguments that are used for the base constructors. std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts; @@ -163,6 +175,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { solAssert(!!m_runtimeCompiler, ""); + solAssert(!_contract.isLibrary(), "Tried to use contract creator or library."); appendInitAndConstructorCode(_contract); @@ -188,6 +201,34 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont return m_context.runtimeSub(); } +size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract) +{ + solAssert(!!m_runtimeCompiler, ""); + solAssert(_contract.isLibrary(), "Tried to deploy contract as library."); + + CompilerContext::LocationSetter locationSetter(m_context, _contract); + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.pushSubroutineSize(m_context.runtimeSub()); + m_context.pushSubroutineOffset(m_context.runtimeSub()); + m_context.appendInlineAssembly(R"( + { + // If code starts at 11, an mstore(0) writes to the full PUSH20 plus data + // without the need for a shift. + let codepos := 11 + codecopy(codepos, subOffset, subSize) + // Check that the first opcode is a PUSH20 + switch eq(0x73, byte(0, mload(codepos))) + case 0 { invalid() } + mstore(0, address()) + mstore8(codepos, 0x73) + return(codepos, subSize) + } + )", {"subSize", "subOffset"}); + + return m_context.runtimeSub(); +} + void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, _constructor); @@ -244,11 +285,26 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) _constructor.accept(*this); } +void ContractCompiler::appendDelegatecallCheck() +{ + // Special constant that will be replaced by the address at deploy time. + // At compilation time, this is just "PUSH20 00...000". + m_context.appendDeployTimeAddress(); + m_context << Instruction::ADDRESS << Instruction::EQ; + // The result on the stack is + // "We have not been called via DELEGATECALL". +} + void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract) { map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions(); map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints; + if (_contract.isLibrary()) + { + solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected."); + } + FunctionDefinition const* fallback = _contract.fallbackFunction(); eth::AssemblyItem notFound = m_context.newTag(); // directly jump to fallback if the data is too short to contain a function selector @@ -260,7 +316,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac if (!interfaceFunctions.empty()) CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); - // stack now is: 1 0 <funhash> + // stack now is: <can-call-non-view-functions>? <funhash> for (auto const& it: interfaceFunctions) { callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); @@ -272,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << notFound; if (fallback) { + solAssert(!_contract.isLibrary(), ""); if (!fallback->isPayable()) appendCallValueCheck(); @@ -291,6 +348,13 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); m_context << callDataUnpackerEntryPoints.at(it.first); + if (_contract.isLibrary() && functionType->stateMutability() > StateMutability::View) + { + // If the function is not a view function and is called without DELEGATECALL, + // we revert. + m_context << dupInstruction(2); + m_context.appendConditionalRevert(); + } m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. @@ -441,6 +505,7 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract) { + solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); for (VariableDeclaration const* variable: _contract.stateVariables()) if (variable->value() && !variable->isConstant()) ExpressionCompiler(m_context, m_optimise).appendStateVariableInitialization(*variable); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 7c5ee59f..1fd80d05 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -75,10 +75,19 @@ private: /// with a new and initialized context. Adds the constructor code. /// @returns the identifier of the runtime sub assembly size_t packIntoContractCreator(ContractDefinition const& _contract); + /// Appends code that deploys the given contract as a library. + /// Will also add code that modifies the contract in memory by injecting the current address + /// for the call protector. + size_t deployLibrary(ContractDefinition const& _contract); /// Appends state variable initialisation and constructor code. void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); void appendConstructor(FunctionDefinition const& _constructor); + /// Appends code that returns a boolean flag on the stack that tells whether + /// the contract has been called via delegatecall (false) or regular call (true). + /// This is done by inserting a specific push constant as the first instruction + /// whose data will be modified in memory at deploy time. + void appendDelegatecallCheck(); void appendFunctionSelector(ContractDefinition const& _contract); void appendCallValueCheck(); /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index bb8c4a94..8e1cf019 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1014,6 +1014,30 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) _memberAccess.expression().accept(*this); return false; } + // Another special case for `this.f.selector` which does not need the address. + // There are other uses of `.selector` which do need the address, but we want this + // specific use to be a pure expression. + if ( + _memberAccess.expression().annotation().type->category() == Type::Category::Function && + member == "selector" + ) + if (auto const* expr = dynamic_cast<MemberAccess const*>(&_memberAccess.expression())) + if (auto const* exprInt = dynamic_cast<Identifier const*>(&expr->expression())) + if (exprInt->name() == "this") + if (Declaration const* declaration = expr->annotation().referencedDeclaration) + { + u256 identifier; + if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration)) + identifier = FunctionType(*variable).externalIdentifier(); + else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration)) + identifier = FunctionType(*function).externalIdentifier(); + else + solAssert(false, "Contract member is neither variable nor function."); + m_context << identifier; + /// need to store store it as bytes4 + utils().leftShiftNumberOnStack(224); + return false; + } _memberAccess.expression().accept(*this); switch (_memberAccess.expression().annotation().type->category()) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 1050621e..a64024b3 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -55,7 +55,7 @@ void SMTChecker::analyze(SourceUnit const& _source) void SMTChecker::endVisit(VariableDeclaration const& _varDecl) { if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) - assignment(_varDecl, *_varDecl.value()); + assignment(_varDecl, *_varDecl.value(), _varDecl.location()); } bool SMTChecker::visit(FunctionDefinition const& _function) @@ -71,6 +71,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) m_interface->reset(); m_currentSequenceCounter.clear(); m_nextFreeSequenceCounter.clear(); + m_pathConditions.clear(); m_conditionalExecutionHappened = false; initializeLocalVariables(_function); return true; @@ -90,15 +91,16 @@ bool SMTChecker::visit(IfStatement const& _node) checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); - visitBranch(_node.trueStatement(), expr(_node.condition())); + auto countersEndFalse = m_currentSequenceCounter; + auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition())); vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); if (_node.falseStatement()) { - visitBranch(*_node.falseStatement(), !expr(_node.condition())); + countersEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition())); touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); } - resetVariables(touchedVariables); + mergeVariables(touchedVariables, expr(_node.condition()), countersEndTrue, countersEndFalse); return false; } @@ -178,8 +180,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) else if (knownVariable(*_varDecl.declarations()[0])) { if (_varDecl.initialValue()) - // TODO more checks? - assignment(*_varDecl.declarations()[0], *_varDecl.initialValue()); + assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location()); } else m_errorReporter.warning( @@ -208,7 +209,10 @@ void SMTChecker::endVisit(Assignment const& _assignment) { Declaration const* decl = identifier->annotation().referencedDeclaration; if (knownVariable(*decl)) - assignment(*decl, _assignment.rightHandSide()); + { + assignment(*decl, _assignment.rightHandSide(), _assignment.location()); + defineExpr(_assignment, expr(_assignment.rightHandSide())); + } else m_errorReporter.warning( _assignment.location(), @@ -230,7 +234,81 @@ void SMTChecker::endVisit(TupleExpression const& _tuple) "Assertion checker does not yet implement tules and inline arrays." ); else - m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0])); + defineExpr(_tuple, expr(*_tuple.components()[0])); +} + +void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location) +{ + checkCondition( + _value < minValue(_type), + _location, + "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")", + "value", + &_value + ); + checkCondition( + _value > maxValue(_type), + _location, + "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")", + "value", + &_value + ); +} + +void SMTChecker::endVisit(UnaryOperation const& _op) +{ + switch (_op.getOperator()) + { + case Token::Not: // ! + { + solAssert(_op.annotation().type->category() == Type::Category::Bool, ""); + defineExpr(_op, !expr(_op.subExpression())); + break; + } + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + { + solAssert(_op.annotation().type->category() == Type::Category::Integer, ""); + solAssert(_op.subExpression().annotation().lValueRequested, ""); + if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression())) + { + Declaration const* decl = identifier->annotation().referencedDeclaration; + if (knownVariable(*decl)) + { + auto innerValue = currentValue(*decl); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + assignment(*decl, newValue, _op.location()); + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement such assignments." + ); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement such increments / decrements." + ); + break; + } + case Token::Add: // + + defineExpr(_op, expr(_op.subExpression())); + break; + case Token::Sub: // - + { + defineExpr(_op, 0 - expr(_op.subExpression())); + if (auto intType = dynamic_cast<IntegerType const*>(_op.annotation().type.get())) + checkUnderOverflow(expr(_op), *intType, _op.location()); + break; + } + default: + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); + } } void SMTChecker::endVisit(BinaryOperation const& _op) @@ -268,16 +346,14 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) 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])); + addPathImpliedExpression(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. + checkBooleanNotConstant(*args[0], "Condition is always $VALUE."); + addPathImpliedExpression(expr(*args[0])); } } @@ -290,7 +366,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) // Will be translated as part of the node that requested the lvalue. } else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get())) - m_interface->addAssertion(expr(_identifier) == currentValue(*decl)); + defineExpr(_identifier, currentValue(*decl)); else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) { if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) @@ -306,10 +382,10 @@ void SMTChecker::endVisit(Literal const& _literal) if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type)) solAssert(!rational->isFractional(), ""); - m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal))); + defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); } else if (type.category() == Type::Category::Bool) - m_interface->addAssertion(expr(_literal) == smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); + defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); else m_errorReporter.warning( _literal.location(), @@ -326,36 +402,30 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) case Token::Add: case Token::Sub: case Token::Mul: + case Token::Div: { solAssert(_op.annotation().commonType, ""); solAssert(_op.annotation().commonType->category() == Type::Category::Integer, ""); + auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType); 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::Div ? division(left, right, intType) : /*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 - ); + if (_op.getOperator() == Token::Div) + { + checkCondition(right == 0, _op.location(), "Division by zero", "value", &right); + m_interface->addAssertion(right != 0); + } + + checkUnderOverflow(value, intType, _op.location()); - m_interface->addAssertion(expr(_op) == value); + defineExpr(_op, value); break; } default: @@ -383,7 +453,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op) /*op == Token::GreaterThanOrEqual*/ (left >= right) ); // TODO: check that other values for op are not possible. - m_interface->addAssertion(expr(_op) == value); + defineExpr(_op, value); } else m_errorReporter.warning( @@ -400,9 +470,9 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) { // @TODO check that both of them are not constant if (_op.getOperator() == Token::And) - m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression())); + defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression())); else - m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression())); + defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression())); } else m_errorReporter.warning( @@ -411,30 +481,50 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) ); } -void SMTChecker::assignment(Declaration const& _variable, Expression const& _value) +smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) { - // TODO more checks? - // TODO add restrictions about type (might be assignment from smaller type) - m_interface->addAssertion(newValue(_variable) == expr(_value)); + // Signed division in SMTLIB2 rounds differently for negative division. + if (_type.isSigned()) + return (smt::Expression::ite( + _left >= 0, + smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))), + smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right)) + )); + else + return _left / _right; +} + +void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location) +{ + assignment(_variable, expr(_value), _location); } -void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location) { - visitBranch(_statement, &_condition); + TypePointer type = _variable.type(); + if (auto const* intType = dynamic_cast<IntegerType const*>(type.get())) + checkUnderOverflow(_value, *intType, _location); + m_interface->addAssertion(newValue(_variable) == _value); } -void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) +SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +{ + return visitBranch(_statement, &_condition); +} + +SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) { VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; - m_interface->push(); if (_condition) - m_interface->addAssertion(*_condition); + pushPathCondition(*_condition); _statement.accept(*this); - m_interface->pop(); + if (_condition) + popPathCondition(); m_conditionalExecutionHappened = true; - m_currentSequenceCounter = sequenceCountersStart; + std::swap(sequenceCountersStart, m_currentSequenceCounter); + return sequenceCountersStart; } void SMTChecker::checkCondition( @@ -446,7 +536,7 @@ void SMTChecker::checkCondition( ) { m_interface->push(); - m_interface->addAssertion(_condition); + addPathConjoinedExpression(_condition); vector<smt::Expression> expressionsToEvaluate; vector<string> expressionNames; @@ -472,7 +562,7 @@ void SMTChecker::checkCondition( } smt::CheckResult result; vector<string> values; - tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate); + tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); string conditionalComment; if (m_conditionalExecutionHappened) @@ -518,13 +608,13 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co return; m_interface->push(); - m_interface->addAssertion(expr(_condition)); - auto positiveResult = checkSatisifable(); + addPathConjoinedExpression(expr(_condition)); + auto positiveResult = checkSatisfiable(); m_interface->pop(); m_interface->push(); - m_interface->addAssertion(!expr(_condition)); - auto negatedResult = checkSatisifable(); + addPathConjoinedExpression(!expr(_condition)); + auto negatedResult = checkSatisfiable(); m_interface->pop(); if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) @@ -554,7 +644,7 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co } pair<smt::CheckResult, vector<string>> -SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate) +SMTChecker::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate) { smt::CheckResult result; vector<string> values; @@ -584,9 +674,9 @@ SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _exp return make_pair(result, values); } -smt::CheckResult SMTChecker::checkSatisifable() +smt::CheckResult SMTChecker::checkSatisfiable() { - return checkSatisifableAndGenerateModel({}).first; + return checkSatisfiableAndGenerateModel({}).first; } void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) @@ -614,6 +704,22 @@ void SMTChecker::resetVariables(vector<Declaration const*> _variables) } } +void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse) +{ + set<Declaration const*> uniqueVars(_variables.begin(), _variables.end()); + for (auto const* decl: uniqueVars) + { + int trueCounter = _countersEndTrue.at(decl); + int falseCounter = _countersEndFalse.at(decl); + solAssert(trueCounter != falseCounter, ""); + m_interface->addAssertion(newValue(*decl) == smt::Expression::ite( + _condition, + valueAtSequence(*decl, trueCounter), + valueAtSequence(*decl, falseCounter)) + ); + } +} + bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) { if (dynamic_cast<IntegerType const*>(_varDecl.type().get())) @@ -696,6 +802,18 @@ smt::Expression SMTChecker::expr(Expression const& _e) { if (!m_expressions.count(&_e)) { + m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." ); + createExpr(_e); + } + return m_expressions.at(&_e); +} + +void SMTChecker::createExpr(Expression const& _e) +{ + if (m_expressions.count(&_e)) + m_errorReporter.warning(_e.location(), "Internal error: Expression created twice in SMT solver." ); + else + { solAssert(_e.annotation().type, ""); switch (_e.annotation().type->category()) { @@ -716,7 +834,12 @@ smt::Expression SMTChecker::expr(Expression const& _e) solAssert(false, "Type not implemented."); } } - return m_expressions.at(&_e); +} + +void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) +{ + createExpr(_e); + m_interface->addAssertion(expr(_e) == _value); } smt::Expression SMTChecker::var(Declaration const& _decl) @@ -724,3 +847,31 @@ smt::Expression SMTChecker::var(Declaration const& _decl) solAssert(m_variables.count(&_decl), ""); return m_variables.at(&_decl); } + +void SMTChecker::popPathCondition() +{ + solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); + m_pathConditions.pop_back(); +} + +void SMTChecker::pushPathCondition(smt::Expression const& _e) +{ + m_pathConditions.push_back(currentPathConditions() && _e); +} + +smt::Expression SMTChecker::currentPathConditions() +{ + if (m_pathConditions.size() == 0) + return smt::Expression(true); + return m_pathConditions.back(); +} + +void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) +{ + m_interface->addAssertion(currentPathConditions() && _e); +} + +void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) +{ + m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e)); +} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 8e07d74d..b57f0f96 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -26,6 +26,7 @@ #include <map> #include <string> +#include <vector> namespace dev { @@ -57,6 +58,7 @@ private: virtual void endVisit(ExpressionStatement const& _node) override; virtual void endVisit(Assignment const& _node) override; virtual void endVisit(TupleExpression const& _node) override; + virtual void endVisit(UnaryOperation const& _node) override; virtual void endVisit(BinaryOperation const& _node) override; virtual void endVisit(FunctionCall const& _node) override; virtual void endVisit(Identifier const& _node) override; @@ -66,12 +68,21 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); - void assignment(Declaration const& _variable, Expression const& _value); + /// Division expression in the given type. Requires special treatment because + /// of rounding for signed division. + smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); - // Visits the branch given by the statement, pushes and pops the SMT checker. - // @param _condition if present, asserts that this condition is true within the branch. - void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); - void visitBranch(Statement const& _statement, smt::Expression _condition); + void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location); + void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location); + + /// Maps a variable to an SSA index. + using VariableSequenceCounters = std::map<Declaration const*, int>; + + /// Visits the branch given by the statement, pushes and pops the current path conditions. + /// @param _condition if present, asserts that this condition is true within the branch. + /// @returns the variable sequence counter after visiting the branch. + VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); + VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression _condition); /// Check that a condition can be satisfied. void checkCondition( @@ -88,14 +99,21 @@ private: Expression const& _condition, std::string const& _description ); + /// Checks that the value is in the range given by the type. + void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location); + std::pair<smt::CheckResult, std::vector<std::string>> - checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate); + checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate); - smt::CheckResult checkSatisifable(); + smt::CheckResult checkSatisfiable(); void initializeLocalVariables(FunctionDefinition const& _function); void resetVariables(std::vector<Declaration const*> _variables); + /// Given two different branches and the touched variables, + /// merge the touched variables into after-branch ite variables + /// using the branch condition as guard. + void mergeVariables(std::vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse); /// Tries to create an uninitialized variable and returns true on success. /// This fails if the type is not supported. bool createVariable(VariableDeclaration const& _varDecl); @@ -124,15 +142,27 @@ private: static smt::Expression minValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t); - using VariableSequenceCounters = std::map<Declaration const*, int>; - - /// Returns the expression corresponding to the AST node. Creates a new expression - /// if it does not exist yet. + /// Returns the expression corresponding to the AST node. Throws if the expression does not exist. smt::Expression expr(Expression const& _e); + /// Creates the expression (value can be arbitrary) + void createExpr(Expression const& _e); + /// Creates the expression and sets its value. + void defineExpr(Expression const& _e, smt::Expression _value); /// 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); + /// Adds a new path condition + void pushPathCondition(smt::Expression const& _e); + /// Remove the last path condition + void popPathCondition(); + /// Returns the conjunction of all path conditions or True if empty + smt::Expression currentPathConditions(); + /// Conjoin the current path conditions with the given parameter and add to the solver + void addPathConjoinedExpression(smt::Expression const& _e); + /// Add to the solver: the given expression implied by the current path conditions + void addPathImpliedExpression(smt::Expression const& _e); + std::shared_ptr<smt::SolverInterface> m_interface; std::shared_ptr<VariableUsage> m_variableUsage; bool m_conditionalExecutionHappened = false; @@ -140,6 +170,7 @@ private: std::map<Declaration const*, int> m_nextFreeSequenceCounter; std::map<Expression const*, smt::Expression> m_expressions; std::map<Declaration const*, smt::Expression> m_variables; + std::vector<smt::Expression> m_pathConditions; ErrorReporter& m_errorReporter; FunctionDefinition const* m_currentFunction = nullptr; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index c9adf863..88487310 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -72,6 +72,11 @@ public: }, _trueValue.sort); } + static Expression implies(Expression _a, Expression _b) + { + return !std::move(_a) || std::move(_b); + } + friend Expression operator!(Expression _a) { return Expression("not", std::move(_a), Sort::Bool); @@ -120,6 +125,10 @@ public: { return Expression("*", std::move(_a), std::move(_b), Sort::Int); } + friend Expression operator/(Expression _a, Expression _b) + { + return Expression("/", std::move(_a), std::move(_b), Sort::Int); + } Expression operator()(Expression _a) const { solAssert( diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index e5c1aef4..769e6edb 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -127,7 +127,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) {">=", 2}, {"+", 2}, {"-", 2}, - {"*", 2} + {"*", 2}, + {"/", 2} }; string const& n = _expr.name; if (m_functions.count(n)) @@ -173,6 +174,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) 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]; diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 2804ddfc..2d6e58de 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,14 +54,15 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); m_info.stackHeightInfo[&_label] = m_stackHeight; + warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); return true; } bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -140,22 +141,34 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(!m_julia, ""); + solAssert(m_flavour != AsmFlavour::IULIA, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) success = false; // Parser already checks that the number of arguments is correct. - solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); - if (!(*this)(_instr.instruction)) - success = false; + auto const& info = instructionInfo(_instr.instruction); + solAssert(info.args == int(_instr.arguments.size()), ""); + m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instr] = m_stackHeight; + warnOnInstructions(_instr.instruction, _instr.location); + return success; +} + +bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) +{ + size_t initialStackHeight = m_stackHeight; + bool success = boost::apply_visitor(*this, _statement.expression); + if (m_flavour != AsmFlavour::Loose) + if (!expectDeposit(0, initialStackHeight, _statement.location)) + success = false; + m_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; @@ -217,14 +230,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); solAssert(virtualBlock, ""); Scope& varScope = scope(virtualBlock); - for (auto const& var: _funDef.arguments + _funDef.returns) + for (auto const& var: _funDef.parameters + _funDef.returnVariables) { expectValidType(var.type, var.location); m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name))); } int const stackHeight = m_stackHeight; - m_stackHeight = _funDef.arguments.size() + _funDef.returns.size(); + m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size(); bool success = (*this)(_funDef.body); @@ -405,13 +418,13 @@ bool AsmAnalyzer::operator()(Block const& _block) return success; } -bool AsmAnalyzer::expectExpression(Statement const& _statement) +bool AsmAnalyzer::expectExpression(Expression const& _expr) { bool success = true; int const initialHeight = m_stackHeight; - if (!boost::apply_visitor(*this, _statement)) + if (!boost::apply_visitor(*this, _expr)) success = false; - if (!expectDeposit(1, initialHeight, locationOf(_statement))) + if (!expectDeposit(1, initialHeight, locationOf(_expr))) success = false; return success; } @@ -495,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block) } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) { - if (!m_julia) + if (m_flavour != AsmFlavour::IULIA) return; if (!builtinTypes.count(type)) @@ -522,11 +535,11 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio "the Metropolis hard fork. Before that it acts as an invalid instruction." ); - if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI) + if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) m_errorReporter.warning( _location, - "Jump instructions are low-level EVM features that can lead to " + "Jump instructions and labels are low-level EVM features that can lead to " "incorrect stack access. Because of that they are discouraged. " - "Please consider using \"switch\" or \"for\" statements instead." + "Please consider using \"switch\", \"if\" or \"for\" statements instead." ); } diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index e484b876..7a81dbf8 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -54,9 +54,9 @@ public: explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, - bool _julia = false, + AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {} + ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {} bool analyze(assembly::Block const& _block); @@ -65,6 +65,7 @@ public: bool operator()(assembly::Identifier const&); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::Label const& _label); + bool operator()(assembly::ExpressionStatement const&); bool operator()(assembly::StackAssignment const&); bool operator()(assembly::Assignment const& _assignment); bool operator()(assembly::VariableDeclaration const& _variableDeclaration); @@ -77,7 +78,7 @@ public: private: /// Visits the statement and expects it to deposit one item onto the stack. - bool expectExpression(Statement const& _statement); + bool expectExpression(Expression const& _expr); bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); /// Verifies that a variable to be assigned to exists and has the same size @@ -96,7 +97,7 @@ private: std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; ErrorReporter& m_errorReporter; - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index a792a1b8..2982d5e0 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -58,23 +58,25 @@ struct StackAssignment { SourceLocation location; Identifier variableName; }; /// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy /// a single stack slot and expects a single expression on the right hand returning /// the same amount of items as the number of variables. -struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; }; +struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" -struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; }; -struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; }; +struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector<Expression> arguments; }; +struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Expression> arguments; }; +/// Statement that contains only a single expression +struct ExpressionStatement { SourceLocation location; Expression expression; }; /// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted -struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; }; +struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector<Statement> statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") -struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; }; +struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList parameters; TypedNameList returnVariables; Block body; }; /// Conditional execution without "else" part. -struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; }; +struct If { SourceLocation location; std::shared_ptr<Expression> condition; Block body; }; /// Switch case or default case struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; }; /// Switch statement -struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; }; -struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; }; +struct Switch { SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; }; +struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; }; struct LocationExtractor: boost::static_visitor<SourceLocation> { diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h index d627b41a..3a9600fe 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -43,10 +43,22 @@ struct FunctionDefinition; struct FunctionCall; struct If; struct Switch; +struct Case; struct ForLoop; +struct ExpressionStatement; struct Block; -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; +struct TypedName; + +using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>; +using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; + +enum class AsmFlavour +{ + Loose, // no types, EVM instructions as function, jumps and direct stack manipulations + Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations + IULIA // same as Strict mode with types +}; } } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 8f171005..306b07e6 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -77,9 +77,7 @@ assembly::Statement Parser::parseStatement() { assembly::If _if = createWithLocation<assembly::If>(); m_scanner->next(); - _if.condition = make_shared<Statement>(parseExpression()); - if (_if.condition->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as conditions for if - try to append \"()\"."); + _if.condition = make_shared<Expression>(parseExpression()); _if.body = parseBlock(); return _if; } @@ -87,9 +85,7 @@ assembly::Statement Parser::parseStatement() { assembly::Switch _switch = createWithLocation<assembly::Switch>(); m_scanner->next(); - _switch.expression = make_shared<Statement>(parseExpression()); - if (_switch.expression->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\"."); + _switch.expression = make_shared<Expression>(parseExpression()); while (m_scanner->currentToken() == Token::Case) _switch.cases.emplace_back(parseCase()); if (m_scanner->currentToken() == Token::Default) @@ -107,14 +103,14 @@ assembly::Statement Parser::parseStatement() return parseForLoop(); case Token::Assign: { - if (m_julia) + if (m_flavour != AsmFlavour::Loose) break; assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>(); advance(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = currentLiteral(); - if (!m_julia && instructions().count(assignment.variableName.name)) + if (instructions().count(assignment.variableName.name)) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); @@ -127,18 +123,21 @@ assembly::Statement Parser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - Statement statement(parseElementaryOperation(false)); + ElementaryOperation elementary(parseElementaryOperation()); switch (currentToken()) { case Token::LParen: - return parseCall(std::move(statement)); + { + Expression expr = parseCall(std::move(elementary)); + return ExpressionStatement{locationOf(expr), expr}; + } case Token::Comma: { // if a comma follows, a multiple assignment is assumed - if (statement.type() != typeid(assembly::Identifier)) + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \",\" (multiple assignment)."); - assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement); + assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary); Assignment assignment = createWithLocation<Assignment>(identifier.location); assignment.variableNames.emplace_back(identifier); @@ -146,43 +145,43 @@ assembly::Statement Parser::parseStatement() do { expectToken(Token::Comma); - statement = parseElementaryOperation(false); - if (statement.type() != typeid(assembly::Identifier)) + elementary = parseElementaryOperation(); + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Variable name expected in multiple assignemnt."); - assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement)); + assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary)); } while (currentToken() == Token::Comma); expectToken(Token::Colon); expectToken(Token::Assign); - assignment.value.reset(new Statement(parseExpression())); + assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; return assignment; } case Token::Colon: { - if (statement.type() != typeid(assembly::Identifier)) + if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \":\"."); - assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement); + assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary); advance(); // identifier:=: should be parsed as identifier: =: (i.e. a label), // while identifier:= (being followed by a non-colon) as identifier := (assignment). if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) { assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location); - if (!m_julia && instructions().count(identifier.name)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); advance(); assignment.variableNames.emplace_back(identifier); - assignment.value.reset(new Statement(parseExpression())); + assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; return assignment; } else { // label - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Labels are not supported."); Label label = createWithLocation<Label>(identifier.location); label.name = identifier.name; @@ -190,11 +189,25 @@ assembly::Statement Parser::parseStatement() } } default: - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Call or assignment expected."); break; } - return statement; + if (elementary.type() == typeid(assembly::Identifier)) + { + Expression expr = boost::get<assembly::Identifier>(elementary); + return ExpressionStatement{locationOf(expr), expr}; + } + else if (elementary.type() == typeid(assembly::Literal)) + { + Expression expr = boost::get<assembly::Literal>(elementary); + return ExpressionStatement{locationOf(expr), expr}; + } + else + { + solAssert(elementary.type() == typeid(assembly::Instruction), "Invalid elementary operation."); + return boost::get<assembly::Instruction>(elementary); + } } assembly::Case Parser::parseCase() @@ -206,10 +219,10 @@ assembly::Case Parser::parseCase() else if (m_scanner->currentToken() == Token::Case) { m_scanner->next(); - assembly::Statement statement = parseElementaryOperation(); - if (statement.type() != typeid(assembly::Literal)) + ElementaryOperation literal = parseElementaryOperation(); + if (literal.type() != typeid(assembly::Literal)) fatalParserError("Literal expected."); - _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement))); + _case.value = make_shared<Literal>(boost::get<assembly::Literal>(std::move(literal))); } else fatalParserError("Case or default case expected."); @@ -224,22 +237,39 @@ assembly::ForLoop Parser::parseForLoop() ForLoop forLoop = createWithLocation<ForLoop>(); expectToken(Token::For); forLoop.pre = parseBlock(); - forLoop.condition = make_shared<Statement>(parseExpression()); - if (forLoop.condition->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as conditions for the for statement."); + forLoop.condition = make_shared<Expression>(parseExpression()); forLoop.post = parseBlock(); forLoop.body = parseBlock(); forLoop.location.end = forLoop.body.location.end; return forLoop; } -assembly::Statement Parser::parseExpression() +assembly::Expression Parser::parseExpression() { RecursionGuard recursionGuard(*this); - Statement operation = parseElementaryOperation(true); + // In strict mode, this might parse a plain Instruction, but + // it will be converted to a FunctionalInstruction inside + // parseCall below. + ElementaryOperation operation = parseElementaryOperation(); if (operation.type() == typeid(Instruction)) { Instruction const& instr = boost::get<Instruction>(operation); + // Disallow instructions returning multiple values (and DUP/SWAP) as expression. + if ( + instructionInfo(instr.instruction).ret != 1 || + isDupInstruction(instr.instruction) || + isSwapInstruction(instr.instruction) + ) + fatalParserError( + "Instruction \"" + + instructionNames().at(instr.instruction) + + "\" not allowed in this context." + ); + if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen) + fatalParserError( + "Non-functional instructions are not allowed in this context." + ); + // Enforce functional notation for instructions requiring multiple arguments. int args = instructionInfo(instr.instruction).args; if (args > 0 && currentToken() != Token::LParen) fatalParserError(string( @@ -252,8 +282,20 @@ assembly::Statement Parser::parseExpression() } if (currentToken() == Token::LParen) return parseCall(std::move(operation)); + else if (operation.type() == typeid(Instruction)) + { + // Instructions not taking arguments are allowed as expressions. + solAssert(m_flavour == AsmFlavour::Loose, ""); + Instruction& instr = boost::get<Instruction>(operation); + return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; + } + else if (operation.type() == typeid(assembly::Identifier)) + return boost::get<assembly::Identifier>(operation); else - return operation; + { + solAssert(operation.type() == typeid(assembly::Literal), ""); + return boost::get<assembly::Literal>(operation); + } } std::map<string, dev::solidity::Instruction> const& Parser::instructions() @@ -296,10 +338,10 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames() return s_instructionNames; } -assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +Parser::ElementaryOperation Parser::parseElementaryOperation() { RecursionGuard recursionGuard(*this); - Statement ret; + ElementaryOperation ret; switch (currentToken()) { case Token::Identifier: @@ -317,15 +359,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = currentLiteral(); // first search the set of instructions. - if (!m_julia && instructions().count(literal)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(literal)) { dev::solidity::Instruction const& instr = instructions().at(literal); - if (_onlySinglePusher) - { - InstructionInfo info = dev::solidity::instructionInfo(instr); - if (info.ret != 1) - fatalParserError("Instruction \"" + literal + "\" not allowed in this context."); - } ret = Instruction{location(), instr}; } else @@ -364,7 +400,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) "" }; advance(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); literal.location.end = endPosition(); @@ -377,7 +413,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) } default: fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Literal or identifier expected." : "Literal, identifier or instruction expected." ); @@ -402,7 +438,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { expectToken(Token::Colon); expectToken(Token::Assign); - varDecl.value.reset(new Statement(parseExpression())); + varDecl.value.reset(new Expression(parseExpression())); varDecl.location.end = locationOf(*varDecl.value).end; } else @@ -419,7 +455,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() expectToken(Token::LParen); while (currentToken() != Token::RParen) { - funDef.arguments.emplace_back(parseTypedName()); + funDef.parameters.emplace_back(parseTypedName()); if (currentToken() == Token::RParen) break; expectToken(Token::Comma); @@ -431,7 +467,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() expectToken(Token::GreaterThan); while (true) { - funDef.returns.emplace_back(parseTypedName()); + funDef.returnVariables.emplace_back(parseTypedName()); if (currentToken() == Token::LBrace) break; expectToken(Token::Comma); @@ -442,16 +478,17 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() return funDef; } -assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) +assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) { RecursionGuard recursionGuard(*this); - if (_instruction.type() == typeid(Instruction)) + if (_initialOp.type() == typeid(Instruction)) { - solAssert(!m_julia, "Instructions are invalid in JULIA"); + solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA"); + Instruction& instruction = boost::get<Instruction>(_initialOp); FunctionalInstruction ret; - ret.instruction = std::move(boost::get<Instruction>(_instruction)); - ret.location = ret.instruction.location; - solidity::Instruction instr = ret.instruction.instruction; + ret.instruction = instruction.instruction; + ret.location = std::move(instruction.location); + solidity::Instruction instr = ret.instruction; InstructionInfo instrInfo = instructionInfo(instr); if (solidity::isDupInstruction(instr)) fatalParserError("DUPi instructions not allowed for functional notation"); @@ -498,10 +535,10 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) expectToken(Token::RParen); return ret; } - else if (_instruction.type() == typeid(Identifier)) + else if (_initialOp.type() == typeid(Identifier)) { FunctionCall ret; - ret.functionName = std::move(boost::get<Identifier>(_instruction)); + ret.functionName = std::move(boost::get<Identifier>(_initialOp)); ret.location = ret.functionName.location; expectToken(Token::LParen); while (currentToken() != Token::RParen) @@ -517,7 +554,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction) } else fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Function name expected." : "Assembly instruction or function name required in front of \"(\")" ); @@ -530,7 +567,7 @@ TypedName Parser::parseTypedName() RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation<TypedName>(); typedName.name = expectAsmIdentifier(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); typedName.location.end = endPosition(); @@ -542,12 +579,18 @@ TypedName Parser::parseTypedName() string Parser::expectAsmIdentifier() { string name = currentLiteral(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { - if (currentToken() == Token::Bool) + switch (currentToken()) { + case Token::Return: + case Token::Byte: + case Token::Address: + case Token::Bool: advance(); return name; + default: + break; } } else if (instructions().count(name)) diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index e46d1732..015aeef3 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,13 +37,16 @@ namespace assembly class Parser: public ParserBase { public: - explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {} + explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose): + ParserBase(_errorReporter), m_flavour(_flavour) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner); protected: + using ElementaryOperation = boost::variant<assembly::Instruction, assembly::Literal, assembly::Identifier>; + /// Creates an inline assembly node with the given source location. template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const { @@ -65,20 +68,23 @@ protected: Case parseCase(); ForLoop parseForLoop(); /// Parses a functional expression that has to push exactly one stack element - Statement parseExpression(); + assembly::Expression parseExpression(); static std::map<std::string, dev::solidity::Instruction> const& instructions(); static std::map<dev::solidity::Instruction, std::string> const& instructionNames(); - Statement parseElementaryOperation(bool _onlySinglePusher = false); + /// Parses an elementary operation, i.e. a literal, identifier or instruction. + /// This will parse instructions even in strict mode as part of the full parser + /// for FunctionalInstruction. + ElementaryOperation parseElementaryOperation(); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); - Statement parseCall(Statement&& _instruction); + assembly::Expression parseCall(ElementaryOperation&& _initialOp); TypedName parseTypedName(); std::string expectAsmIdentifier(); static bool isValidNumberLiteral(std::string const& _literal); private: - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 0f183244..bacd7a94 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -94,7 +94,7 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional { solAssert(!m_julia, ""); return - (*this)(_functionalInstruction.instruction) + + boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) + "(" + boost::algorithm::join( _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), @@ -102,6 +102,11 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional ")"; } +string AsmPrinter::operator()(ExpressionStatement const& _statement) +{ + return boost::apply_visitor(*this, _statement.expression); +} + string AsmPrinter::operator()(assembly::Label const& _label) { solAssert(!m_julia, ""); @@ -144,17 +149,17 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin { string out = "function " + _functionDefinition.name + "("; out += boost::algorithm::join( - _functionDefinition.arguments | boost::adaptors::transformed( + _functionDefinition.parameters | boost::adaptors::transformed( [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } ), ", " ); out += ")"; - if (!_functionDefinition.returns.empty()) + if (!_functionDefinition.returnVariables.empty()) { out += " -> "; out += boost::algorithm::join( - _functionDefinition.returns | boost::adaptors::transformed( + _functionDefinition.returnVariables | boost::adaptors::transformed( [this](TypedName argument) { return argument.name + appendTypeName(argument.type); } ), ", " diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index eadf81d9..5bd87aba 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -42,6 +42,7 @@ public: std::string operator()(assembly::Literal const& _literal); std::string operator()(assembly::Identifier const& _identifier); std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction); + std::string operator()(assembly::ExpressionStatement const& _expr); std::string operator()(assembly::Label const& _label); std::string operator()(assembly::StackAssignment const& _assignment); std::string operator()(assembly::Assignment const& _assignment); diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index 77ae9102..86f3809c 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -45,6 +45,11 @@ ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter): m_currentScope = &scope(nullptr); } +bool ScopeFiller::operator()(ExpressionStatement const& _expr) +{ + return boost::apply_visitor(*this, _expr.expression); +} + bool ScopeFiller::operator()(Label const& _item) { if (!m_currentScope->registerLabel(_item.name)) @@ -71,10 +76,10 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) { bool success = true; vector<Scope::JuliaType> arguments; - for (auto const& _argument: _funDef.arguments) + for (auto const& _argument: _funDef.parameters) arguments.push_back(_argument.type); vector<Scope::JuliaType> returns; - for (auto const& _return: _funDef.returns) + for (auto const& _return: _funDef.returnVariables) returns.push_back(_return.type); if (!m_currentScope->registerFunction(_funDef.name, arguments, returns)) { @@ -91,7 +96,7 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) varScope.superScope = m_currentScope; m_currentScope = &varScope; varScope.functionScope = true; - for (auto const& var: _funDef.arguments + _funDef.returns) + for (auto const& var: _funDef.parameters + _funDef.returnVariables) if (!registerVariable(var, _funDef.location, varScope)) success = false; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index ed28abbf..bb023f61 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -53,6 +53,7 @@ public: bool operator()(assembly::Literal const&) { return true; } bool operator()(assembly::Identifier const&) { return true; } bool operator()(assembly::FunctionalInstruction const&) { return true; } + bool operator()(assembly::ExpressionStatement const& _expr); bool operator()(assembly::Label const& _label); bool operator()(assembly::StackAssignment const&) { return true; } bool operator()(assembly::Assignment const&) { return true; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 504ad92c..1b4bd270 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -38,6 +38,25 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ +assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) +{ + switch (_language) + { + case AssemblyStack::Language::Assembly: + return assembly::AsmFlavour::Loose; + case AssemblyStack::Language::StrictAssembly: + return assembly::AsmFlavour::Strict; + case AssemblyStack::Language::JULIA: + return assembly::AsmFlavour::IULIA; + } + solAssert(false, ""); + return assembly::AsmFlavour::IULIA; +} + +} + Scanner const& AssemblyStack::scanner() const { @@ -50,7 +69,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string m_errors.clear(); m_analysisSuccessful = false; m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName); - m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner); + m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner); if (!m_errorReporter.errors().empty()) return false; solAssert(m_parserResult, ""); @@ -72,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann bool AssemblyStack::analyzeParsed() { m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 2ae596ed..6ae7e8d1 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -51,7 +51,7 @@ struct MachineAssemblyObject class AssemblyStack { public: - enum class Language { JULIA, Assembly }; + enum class Language { JULIA, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; explicit AssemblyStack(Language _language = Language::Assembly): diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 5713256a..3b5e65e8 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -734,9 +734,12 @@ void CompilerStack::compileContract( try { - Compiler cloneCompiler(m_optimize, m_optimizeRuns); - cloneCompiler.compileClone(_contract, _compiledContracts); - compiledContract.cloneObject = cloneCompiler.assembledObject(); + if (!_contract.isLibrary()) + { + Compiler cloneCompiler(m_optimize, m_optimizeRuns); + cloneCompiler.compileClone(_contract, _compiledContracts); + compiledContract.cloneObject = cloneCompiler.assembledObject(); + } } catch (eth::AssemblyException const&) { diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h index 09301b10..7c66d572 100644 --- a/libsolidity/interface/Exceptions.h +++ b/libsolidity/interface/Exceptions.h @@ -109,6 +109,18 @@ public: infos.push_back(std::make_pair(_errMsg, _sourceLocation)); return *this; } + /// Limits the number of secondary source locations to 32 and appends a notice to the + /// error message. + void limitSize(std::string& _message) + { + size_t occurrences = infos.size(); + if (occurrences > 32) + { + infos.resize(32); + _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences."; + } + } + std::vector<errorSourceLocationInfo> infos; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index ad01821e..04f5bd25 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -81,15 +81,15 @@ Json::Value formatErrorWithException( else message = _message; + Json::Value sourceLocation; if (location && location->sourceName) { - Json::Value sourceLocation = Json::objectValue; sourceLocation["file"] = *location->sourceName; sourceLocation["start"] = location->start; sourceLocation["end"] = location->end; } - return formatError(_warning, _type, _component, message, formattedMessage, location); + return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation); } set<string> requestedContractNames(Json::Value const& _outputSelection) @@ -193,7 +193,7 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere for (auto const& ref: linkReferences) { string const& fullname = ref.second; - size_t colon = fullname.find(':'); + size_t colon = fullname.rfind(':'); solAssert(colon != string::npos, ""); string file = fullname.substr(0, colon); string name = fullname.substr(colon + 1); @@ -461,7 +461,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value contractsOutput = Json::objectValue; for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>()) { - size_t colon = contractName.find(':'); + size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); string file = contractName.substr(0, colon); string name = contractName.substr(colon + 1); diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 821e81d2..05b877b5 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -644,15 +644,11 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition() expectToken(Token::Event); ASTPointer<ASTString> name(expectIdentifierToken()); - ASTPointer<ParameterList> parameters; - if (m_scanner->currentToken() == Token::LParen) - { - VarDeclParserOptions options; - options.allowIndexed = true; - parameters = parseParameterList(options); - } - else - parameters = createEmptyParameterList(); + + VarDeclParserOptions options; + options.allowIndexed = true; + ASTPointer<ParameterList> parameters = parseParameterList(options); + bool anonymous = false; if (m_scanner->currentToken() == Token::Anonymous) { |