diff options
Diffstat (limited to 'libsolidity')
22 files changed, 402 insertions, 249 deletions
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index f33de7b7..8750b47b 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -332,7 +332,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) case Location::Memory: return "\"memory\""; case Location::Storage: return "\"storage\""; case Location::CallData: return "\"calldata\""; - case Location::Default: return "none"; + case Location::Unspecified: return "none"; } return {}; }; @@ -368,12 +368,12 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) // Find correct data location. if (_variable.isEventParameter()) { - solAssert(varLoc == Location::Default, ""); + solAssert(varLoc == Location::Unspecified, ""); typeLoc = DataLocation::Memory; } else if (_variable.isStateVariable()) { - solAssert(varLoc == Location::Default, ""); + solAssert(varLoc == Location::Unspecified, ""); typeLoc = _variable.isConstant() ? DataLocation::Memory : DataLocation::Storage; } else if ( @@ -394,7 +394,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) case Location::CallData: typeLoc = DataLocation::CallData; break; - case Location::Default: + case Location::Unspecified: solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set."); } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 60a58665..487a5cca 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -56,7 +56,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) else solAssert(!m_currentFunction, ""); solAssert(m_localVarUseCount.empty(), ""); - m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); m_constructor = _function.isConstructor(); return true; } @@ -64,7 +63,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) void StaticAnalyzer::endVisit(FunctionDefinition const&) { m_currentFunction = nullptr; - m_nonPayablePublic = false; m_constructor = false; for (auto const& var: m_localVarUseCount) if (var.second == 0) @@ -154,14 +152,6 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) ); } - if (m_nonPayablePublic && !m_library) - if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) - if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value") - m_errorReporter.warning( - _memberAccess.location(), - "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?" - ); - if (_memberAccess.memberName() == "callcode") if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) if (type->kind() == FunctionType::Kind::BareCallCode) diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 2a62e391..7f5c743a 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -75,9 +75,6 @@ private: /// Flag that indicates whether the current contract definition is a library. bool m_library = false; - /// Flag that indicates whether a public function does not contain the "payable" modifier. - bool m_nonPayablePublic = false; - /// Number of uses of each (named) local variable in a function, counter is initialized with zero. /// Pairs of AST ids and pointers are used as keys to ensure a deterministic order /// when traversing. diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index ac4fa72b..0bc20f2e 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -138,9 +138,25 @@ void SyntaxChecker::endVisit(ModifierDefinition const& _modifier) m_placeholderFound = false; } -bool SyntaxChecker::visit(WhileStatement const&) +void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _statement) +{ + auto varDecl = dynamic_cast<VariableDeclarationStatement const*>(&_statement); + if (varDecl) + m_errorReporter.syntaxError(_statement.location(), "Variable declarations can only be used inside blocks."); +} + +bool SyntaxChecker::visit(IfStatement const& _ifStatement) +{ + checkSingleStatementVariableDeclaration(_ifStatement.trueStatement()); + if (Statement const* _statement = _ifStatement.falseStatement()) + checkSingleStatementVariableDeclaration(*_statement); + return true; +} + +bool SyntaxChecker::visit(WhileStatement const& _whileStatement) { m_inLoopDepth++; + checkSingleStatementVariableDeclaration(_whileStatement.body()); return true; } @@ -149,9 +165,10 @@ void SyntaxChecker::endVisit(WhileStatement const&) m_inLoopDepth--; } -bool SyntaxChecker::visit(ForStatement const&) +bool SyntaxChecker::visit(ForStatement const& _forStatement) { m_inLoopDepth++; + checkSingleStatementVariableDeclaration(_forStatement.body()); return true; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 897df676..f5716bf9 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -52,6 +52,12 @@ private: virtual bool visit(ModifierDefinition const& _modifier) override; virtual void endVisit(ModifierDefinition const& _modifier) override; + /// Reports an error if _statement is a VariableDeclarationStatement. + /// Used by if/while/for to check for single statement variable declarations + /// without a block. + void checkSingleStatementVariableDeclaration(ASTNode const& _statement); + + virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual void endVisit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 3b1bfe5c..143ac109 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -525,7 +525,7 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) ); } -TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2) +TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2) { vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); if (arguments.size() != 2) @@ -544,10 +544,8 @@ TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall co " to bytes memory requested." ); - TypePointer returnType = make_shared<TupleType>(); - if (arguments.size() < 2) - return returnType; + return {}; // The following is a rather syntactic restriction, but we check it here anyway: // The second argument has to be a tuple expression containing type names. @@ -558,10 +556,10 @@ TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall co arguments[1]->location(), "The second argument to \"abi.decode\" has to be a tuple of types." ); - return returnType; + return {}; } - vector<TypePointer> components; + TypePointers components; for (auto const& typeArgument: tupleExpression->components()) { solAssert(typeArgument, ""); @@ -591,7 +589,7 @@ TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall co components.push_back(make_shared<TupleType>()); } } - return make_shared<TupleType>(components); + return components; } void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) @@ -686,9 +684,7 @@ bool TypeChecker::visit(StructDefinition const& _struct) bool TypeChecker::visit(FunctionDefinition const& _function) { - bool isLibraryFunction = - dynamic_cast<ContractDefinition const*>(_function.scope()) && - dynamic_cast<ContractDefinition const*>(_function.scope())->isLibrary(); + bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library; if (_function.isPayable()) { if (isLibraryFunction) @@ -1217,7 +1213,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (ref->dataStoredIn(DataLocation::Storage)) { string errorText{"Uninitialized storage pointer."}; - if (varDecl.referenceLocation() == VariableDeclaration::Location::Default) + if (varDecl.referenceLocation() == VariableDeclaration::Location::Unspecified) errorText += " Did you mean '<type> memory " + varDecl.name() + "'?"; solAssert(m_scope, ""); m_errorReporter.declarationError(varDecl.location(), errorText); @@ -1782,15 +1778,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (functionType->kind() == FunctionType::Kind::BareStaticCall && !m_evmVersion.hasStaticCall()) m_errorReporter.typeError(_functionCall.location(), "\"staticcall\" is not supported by the VM version."); - auto returnTypes = - allowDynamicTypes ? - functionType->returnParameterTypes() : - functionType->returnParameterTypesWithoutDynamicTypes(); - if (returnTypes.size() == 1) - _functionCall.annotation().type = returnTypes.front(); - else - _functionCall.annotation().type = make_shared<TupleType>(returnTypes); - if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression())) { if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::KECCAK256) @@ -1826,8 +1813,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2); + // Will be assigned to .type at the end (turning multi-elements into a tuple). + TypePointers returnTypes = + allowDynamicTypes ? + functionType->returnParameterTypes() : + functionType->returnParameterTypesWithoutDynamicTypes(); + if (functionType->kind() == FunctionType::Kind::ABIDecode) - _functionCall.annotation().type = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2); + returnTypes = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2); else if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size()) { solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, ""); @@ -1985,6 +1978,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } } + if (returnTypes.size() == 1) + _functionCall.annotation().type = returnTypes.front(); + else + _functionCall.annotation().type = make_shared<TupleType>(returnTypes); + return false; } @@ -2105,7 +2103,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) "after argument-dependent lookup in " + exprType->toString() + (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "."); if (exprType->category() == Type::Category::Contract) - for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr)) + for (auto const& addressMember: AddressType().nativeMembers(nullptr)) if (addressMember.name == memberName) { Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression()); @@ -2356,7 +2354,7 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { // Assign type here if it even looks like an address. This prevents double errors for invalid addresses - _literal.annotation().type = make_shared<IntegerType>(160, IntegerType::Modifier::Address); + _literal.annotation().type = make_shared<AddressType>(); string msg; if (_literal.value().length() != 42) // "0x" + 40 hex digits diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 4be0d1e4..8d25a88e 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -92,9 +92,9 @@ private: void checkExpressionAssignment(Type const& _type, Expression const& _expression); /// Performs type checks for ``abi.decode(bytes memory, (...))`` and returns the - /// return type (which is basically the second argument) if successful. It returns - /// the empty tuple type or error. - TypePointer typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2); + /// vector of return types (which is basically the second argument) if successful. It returns + /// the empty vector on error. + TypePointers typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2); virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index e92ad2fa..113a3177 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -142,7 +142,7 @@ bool ViewPureChecker::visit(FunctionDefinition const& _funDef) { solAssert(!m_currentFunction, ""); m_currentFunction = &_funDef; - m_currentBestMutability = StateMutability::Pure; + m_bestMutabilityAndLocation = {StateMutability::Pure, _funDef.location()}; return true; } @@ -150,7 +150,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) { solAssert(m_currentFunction == &_funDef, ""); if ( - m_currentBestMutability < _funDef.stateMutability() && + m_bestMutabilityAndLocation.mutability < _funDef.stateMutability() && _funDef.stateMutability() != StateMutability::Payable && _funDef.isImplemented() && !_funDef.isConstructor() && @@ -159,22 +159,22 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) ) m_errorReporter.warning( _funDef.location(), - "Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability) + "Function state mutability can be restricted to " + stateMutabilityToString(m_bestMutabilityAndLocation.mutability) ); m_currentFunction = nullptr; } -bool ViewPureChecker::visit(ModifierDefinition const&) +bool ViewPureChecker::visit(ModifierDefinition const& _modifier) { solAssert(m_currentFunction == nullptr, ""); - m_currentBestMutability = StateMutability::Pure; + m_bestMutabilityAndLocation = {StateMutability::Pure, _modifier.location()}; return true; } void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef) { solAssert(m_currentFunction == nullptr, ""); - m_inferredMutability[&_modifierDef] = m_currentBestMutability; + m_inferredMutability[&_modifierDef] = std::move(m_bestMutabilityAndLocation); } void ViewPureChecker::endVisit(Identifier const& _identifier) @@ -219,36 +219,72 @@ void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) }(_inlineAssembly.operations()); } -void ViewPureChecker::reportMutability(StateMutability _mutability, SourceLocation const& _location) +void ViewPureChecker::reportMutability( + StateMutability _mutability, + SourceLocation const& _location, + boost::optional<SourceLocation> const& _nestedLocation +) { - if (m_currentFunction && m_currentFunction->stateMutability() < _mutability) + if (_mutability > m_bestMutabilityAndLocation.mutability) + m_bestMutabilityAndLocation = MutabilityAndLocation{_mutability, _location}; + if (!m_currentFunction || _mutability <= m_currentFunction->stateMutability()) + return; + + // Check for payable here, because any occurrence of `msg.value` + // will set mutability to payable. + if (_mutability == StateMutability::View || ( + _mutability == StateMutability::Payable && + m_currentFunction->stateMutability() == StateMutability::Pure + )) { - if (_mutability == StateMutability::View) - m_errorReporter.typeError( - _location, - "Function declared as pure, but this expression (potentially) reads from the " - "environment or state and thus requires \"view\"." - ); - else if (_mutability == StateMutability::NonPayable) - m_errorReporter.typeError( - _location, - "Function declared as " + - stateMutabilityToString(m_currentFunction->stateMutability()) + - ", but this expression (potentially) modifies the state and thus " - "requires non-payable (the default) or payable." - ); - else - solAssert(false, ""); - - solAssert( - m_currentFunction->stateMutability() == StateMutability::View || - m_currentFunction->stateMutability() == StateMutability::Pure, - "" + m_errorReporter.typeError( + _location, + "Function declared as pure, but this expression (potentially) reads from the " + "environment or state and thus requires \"view\"." ); m_errors = true; } - if (_mutability > m_currentBestMutability) - m_currentBestMutability = _mutability; + else if (_mutability == StateMutability::NonPayable) + { + m_errorReporter.typeError( + _location, + "Function declared as " + + stateMutabilityToString(m_currentFunction->stateMutability()) + + ", but this expression (potentially) modifies the state and thus " + "requires non-payable (the default) or payable." + ); + m_errors = true; + } + else if (_mutability == StateMutability::Payable) + { + // We do not warn for library functions because they cannot be payable anyway. + // Also internal functions should be allowed to use `msg.value`. + if (m_currentFunction->isPublic() && m_currentFunction->inContractKind() != ContractDefinition::ContractKind::Library) + { + if (_nestedLocation) + m_errorReporter.typeError( + _location, + SecondarySourceLocation().append("\"msg.value\" appears here inside the modifier.", *_nestedLocation), + "This modifier uses \"msg.value\" and thus the function has to be payable or internal." + ); + else + m_errorReporter.typeError( + _location, + "\"msg.value\" can only be used in payable public functions. Make the function " + "\"payable\" or use an internal function to avoid this error." + ); + m_errors = true; + } + } + else + solAssert(false, ""); + + solAssert( + m_currentFunction->stateMutability() == StateMutability::View || + m_currentFunction->stateMutability() == StateMutability::Pure || + m_currentFunction->stateMutability() == StateMutability::NonPayable, + "" + ); } void ViewPureChecker::endVisit(FunctionCall const& _functionCall) @@ -287,18 +323,34 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) ASTString const& member = _memberAccess.memberName(); switch (_memberAccess.expression().annotation().type->category()) { - case Type::Category::Integer: + case Type::Category::Address: if (member == "balance") mutability = StateMutability::View; break; case Type::Category::Magic: { - // we can ignore the kind of magic and only look at the name of the member - set<string> static const pureMembers{ - "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode", "data", "sig", "blockhash" + using MagicMember = pair<MagicType::Kind, string>; + set<MagicMember> static const pureMembers{ + {MagicType::Kind::ABI, "decode"}, + {MagicType::Kind::ABI, "encode"}, + {MagicType::Kind::ABI, "encodePacked"}, + {MagicType::Kind::ABI, "encodeWithSelector"}, + {MagicType::Kind::ABI, "encodeWithSignature"}, + {MagicType::Kind::Block, "blockhash"}, + {MagicType::Kind::Message, "data"}, + {MagicType::Kind::Message, "sig"} }; - if (!pureMembers.count(member)) + set<MagicMember> static const payableMembers{ + {MagicType::Kind::Message, "value"} + }; + + auto const& type = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type); + MagicMember magicMember(type.kind(), member); + + if (!pureMembers.count(magicMember)) mutability = StateMutability::View; + if (payableMembers.count(magicMember)) + mutability = StateMutability::Payable; break; } case Type::Category::Struct: @@ -338,7 +390,8 @@ void ViewPureChecker::endVisit(ModifierInvocation const& _modifier) if (ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration)) { solAssert(m_inferredMutability.count(mod), ""); - reportMutability(m_inferredMutability.at(mod), _modifier.location()); + auto const& mutAndLocation = m_inferredMutability.at(mod); + reportMutability(mutAndLocation.mutability, _modifier.location(), mutAndLocation.location); } else solAssert(dynamic_cast<ContractDefinition const*>(_modifier.name()->annotation().referencedDeclaration), ""); diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index 3db52e7e..faa5b698 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -31,16 +31,6 @@ namespace dev namespace solidity { -class ASTNode; -class FunctionDefinition; -class ModifierDefinition; -class Identifier; -class MemberAccess; -class IndexAccess; -class ModifierInvocation; -class FunctionCall; -class InlineAssembly; - class ViewPureChecker: private ASTConstVisitor { public: @@ -50,6 +40,11 @@ public: bool check(); private: + struct MutabilityAndLocation + { + StateMutability mutability; + SourceLocation location; + }; virtual bool visit(FunctionDefinition const& _funDef) override; virtual void endVisit(FunctionDefinition const& _funDef) override; @@ -65,15 +60,19 @@ private: /// Called when an element of mutability @a _mutability is encountered. /// Creates appropriate warnings and errors and sets @a m_currentBestMutability. - void reportMutability(StateMutability _mutability, SourceLocation const& _location); + void reportMutability( + StateMutability _mutability, + SourceLocation const& _location, + boost::optional<SourceLocation> const& _nestedLocation = {} + ); std::vector<std::shared_ptr<ASTNode>> const& m_ast; ErrorReporter& m_errorReporter; bool m_errors = false; - StateMutability m_currentBestMutability = StateMutability::Payable; + MutabilityAndLocation m_bestMutabilityAndLocation = MutabilityAndLocation{StateMutability::Payable, SourceLocation()}; FunctionDefinition const* m_currentFunction = nullptr; - std::map<ModifierDefinition const*, StateMutability> m_inferredMutability; + std::map<ModifierDefinition const*, MutabilityAndLocation> m_inferredMutability; }; } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 635ab024..8e7a81a6 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -517,7 +517,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c using Location = VariableDeclaration::Location; if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter()) - return set<Location>{ Location::Default }; + return set<Location>{ Location::Unspecified }; else if (isStateVariable() && isConstant()) return set<Location>{ Location::Memory }; else if (isExternalCallableParameter()) @@ -546,7 +546,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c } else // Struct members etc. - return set<Location>{ Location::Default }; + return set<Location>{ Location::Unspecified }; } TypePointer VariableDeclaration::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index b953211d..a5cd277d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -655,7 +655,7 @@ private: class VariableDeclaration: public Declaration { public: - enum Location { Default, Storage, Memory, CallData }; + enum Location { Unspecified, Storage, Memory, CallData }; VariableDeclaration( SourceLocation const& _sourceLocation, @@ -666,7 +666,7 @@ public: bool _isStateVar = false, bool _isIndexed = false, bool _isConstant = false, - Location _referenceLocation = Location::Default + Location _referenceLocation = Location::Unspecified ): Declaration(_sourceLocation, _name, _visibility), m_typeName(_type), diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 72b20b3b..beab356c 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -739,7 +739,7 @@ string ASTJsonConverter::location(VariableDeclaration::Location _location) { switch (_location) { - case VariableDeclaration::Location::Default: + case VariableDeclaration::Location::Unspecified: return "default"; case VariableDeclaration::Location::Storage: return "storage"; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b1cd15b4..a302203b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -299,7 +299,7 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) case Token::Byte: return make_shared<FixedBytesType>(1); case Token::Address: - return make_shared<IntegerType>(160, IntegerType::Modifier::Address); + return make_shared<AddressType>(); case Token::Bool: return make_shared<BoolType>(); case Token::Bytes: @@ -439,6 +439,59 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition return members; } +string AddressType::richIdentifier() const +{ + return "t_address"; +} + +bool AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return isImplicitlyConvertibleTo(_convertTo) || + _convertTo.category() == Category::Contract || + _convertTo.category() == Category::Integer || + (_convertTo.category() == Category::FixedBytes && 160 == dynamic_cast<FixedBytesType const&>(_convertTo).numBytes() * 8); +} + +string AddressType::toString(bool) const +{ + return "address"; +} + +u256 AddressType::literalValue(Literal const* _literal) const +{ + solAssert(_literal, ""); + solAssert(_literal->value().substr(0, 2) == "0x", ""); + return u256(_literal->value()); +} + +TypePointer AddressType::unaryOperatorResult(Token::Value _operator) const +{ + return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); +} + + +TypePointer AddressType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + // Addresses can only be compared. + if (!Token::isCompareOp(_operator)) + return TypePointer(); + + return Type::commonType(shared_from_this(), _other); +} + +MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const +{ + return { + {"balance", make_shared<IntegerType>(256)}, + {"call", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, + {"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, + {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false)}, + {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, + {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}, + {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} + }; +} + namespace { @@ -448,7 +501,7 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT if (_operator == Token::SHR) return false; else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType)) - return !otherInt->isAddress(); + return true; else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType)) return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned(); else @@ -460,8 +513,6 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { - if (isAddress()) - solAssert(m_bits == 160, ""); solAssert( m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0, "Invalid bit number for integer type: " + dev::toString(m_bits) @@ -470,10 +521,7 @@ IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier): string IntegerType::richIdentifier() const { - if (isAddress()) - return "t_address"; - else - return "t_" + string(isSigned() ? "" : "u") + "int" + to_string(numBits()); + return "t_" + string(isSigned() ? "" : "u") + "int" + to_string(numBits()); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -483,8 +531,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); if (convertTo.m_bits < m_bits) return false; - if (isAddress()) - return convertTo.isAddress(); else if (isSigned()) return convertTo.isSigned(); else @@ -493,11 +539,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const else if (_convertTo.category() == Category::FixedPoint) { FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo); - - if (isAddress()) - return false; - else - return maxValue() <= convertTo.maxIntegerValue() && minValue() >= convertTo.minIntegerValue(); + return maxValue() <= convertTo.maxIntegerValue() && minValue() >= convertTo.minIntegerValue(); } else return false; @@ -506,6 +548,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || + _convertTo.category() == Category::Address || _convertTo.category() == Category::Contract || _convertTo.category() == Category::Enum || (_convertTo.category() == Category::FixedBytes && numBits() == dynamic_cast<FixedBytesType const&>(_convertTo).numBytes() * 8) || @@ -517,10 +560,7 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const // "delete" is ok for all integer types if (_operator == Token::Delete) return make_shared<TupleType>(); - // no further unary operators for addresses - else if (isAddress()) - return TypePointer(); - // for non-address integers, we allow +, -, ++ and -- + // we allow +, -, ++ and -- else if (_operator == Token::Add || _operator == Token::Sub || _operator == Token::Inc || _operator == Token::Dec || _operator == Token::BitNot) @@ -539,20 +579,10 @@ bool IntegerType::operator==(Type const& _other) const string IntegerType::toString(bool) const { - if (isAddress()) - return "address"; string prefix = isSigned() ? "int" : "uint"; return prefix + dev::toString(m_bits); } -u256 IntegerType::literalValue(Literal const* _literal) const -{ - solAssert(m_modifier == Modifier::Address, ""); - solAssert(_literal, ""); - solAssert(_literal->value().substr(0, 2) == "0x", ""); - return u256(_literal->value()); -} - bigint IntegerType::minValue() const { if (isSigned()) @@ -580,8 +610,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe if (Token::isShiftOp(_operator)) { // Shifts are not symmetric with respect to the type - if (isAddress()) - return TypePointer(); if (isValidShiftAndAmountType(_operator, *_other)) return shared_from_this(); else @@ -599,9 +627,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe return TypePointer(); if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType)) { - // Nothing else can be done with addresses - if (intType->isAddress()) - return TypePointer(); // Signed EXP is not allowed if (Token::Exp == _operator && intType->isSigned()) return TypePointer(); @@ -612,22 +637,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe return commonType; } -MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) const -{ - if (isAddress()) - return { - {"balance", make_shared<IntegerType>(256)}, - {"call", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, - {"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, - {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareDelegateCall, false)}, - {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, - {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}, - {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} - }; - else - return MemberList::MemberMap(); -} - FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, FixedPointType::Modifier _modifier): m_totalBits(_totalBits), m_fractionalDigits(_fractionalDigits), m_modifier(_modifier) { @@ -658,8 +667,7 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.category() == category() || - (_convertTo.category() == Category::Integer && !dynamic_cast<IntegerType const&>(_convertTo).isAddress()); + return _convertTo.category() == category() || _convertTo.category() == Category::Integer; } TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const @@ -912,8 +920,6 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const if (isFractional()) return false; IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo); - if (targetType.isAddress()) - return false; if (m_value == rational(0)) return true; unsigned forSignBit = (targetType.isSigned() ? 1 : 0); @@ -1368,6 +1374,7 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return (_convertTo.category() == Category::Integer && numBytes() * 8 == dynamic_cast<IntegerType const&>(_convertTo).numBits()) || + (_convertTo.category() == Category::Address && numBytes() == 20) || _convertTo.category() == Category::FixedPoint || _convertTo.category() == category(); } @@ -1469,9 +1476,7 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return - isImplicitlyConvertibleTo(_convertTo) || - _convertTo == IntegerType(160, IntegerType::Modifier::Address); + return isImplicitlyConvertibleTo(_convertTo) || _convertTo.category() == Category::Address; } bool ContractType::isPayable() const @@ -1501,8 +1506,6 @@ TypePointer ReferenceType::unaryOperatorResult(Token::Value _operator) const return make_shared<TupleType>(); case DataLocation::Storage: return m_isPointer ? TypePointer() : make_shared<TupleType>(); - default: - solAssert(false, ""); } return TypePointer(); } @@ -1548,8 +1551,6 @@ string ReferenceType::identifierLocationSuffix() const case DataLocation::CallData: id += "_calldata"; break; - default: - solAssert(false, "Unknown location returned by location()"); } if (isPointer()) id += "_ptr"; @@ -2496,7 +2497,14 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const { TypePointers returnParameterTypes = m_returnParameterTypes; - if (m_kind == Kind::External || m_kind == Kind::CallCode || m_kind == Kind::DelegateCall) + if ( + m_kind == Kind::External || + m_kind == Kind::DelegateCall || + m_kind == Kind::BareCall || + m_kind == Kind::BareCallCode || + m_kind == Kind::BareDelegateCall || + m_kind == Kind::BareStaticCall + ) for (auto& param: returnParameterTypes) if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage)) param = make_shared<InaccessibleDynamicType>(); @@ -2518,7 +2526,6 @@ string FunctionType::richIdentifier() const { case Kind::Internal: id += "internal"; break; case Kind::External: id += "external"; break; - case Kind::CallCode: id += "callcode"; break; case Kind::DelegateCall: id += "delegatecall"; break; case Kind::BareCall: id += "barecall"; break; case Kind::BareCallCode: id += "barecallcode"; break; @@ -2556,7 +2563,6 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; case Kind::ABIDecode: id += "abidecode"; break; - default: solAssert(false, "Unknown function location."); break; } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); @@ -2583,12 +2589,8 @@ bool FunctionType::operator==(Type const& _other) const bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (m_kind == Kind::External && _convertTo.category() == Category::Integer) - { - IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); - if (convertTo.isAddress()) + if (m_kind == Kind::External && _convertTo.category() == Category::Address) return true; - } return _convertTo.category() == category(); } @@ -2700,7 +2702,6 @@ unsigned FunctionType::sizeOnStack() const switch(kind) { case Kind::External: - case Kind::CallCode: case Kind::DelegateCall: size = 2; break; @@ -2933,7 +2934,6 @@ string FunctionType::externalSignature() const { case Kind::Internal: case Kind::External: - case Kind::CallCode: case Kind::DelegateCall: case Kind::Event: break; @@ -3257,8 +3257,6 @@ string MagicType::richIdentifier() const return "t_magic_transaction"; case Kind::ABI: return "t_magic_abi"; - default: - solAssert(false, "Unknown kind of magic"); } return ""; } @@ -3277,7 +3275,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const { case Kind::Block: return MemberList::MemberMap({ - {"coinbase", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, + {"coinbase", make_shared<AddressType>()}, {"timestamp", make_shared<IntegerType>(256)}, {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, {"difficulty", make_shared<IntegerType>(256)}, @@ -3286,7 +3284,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const }); case Kind::Message: return MemberList::MemberMap({ - {"sender", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, + {"sender", make_shared<AddressType>()}, {"gas", make_shared<IntegerType>(256)}, {"value", make_shared<IntegerType>(256)}, {"data", make_shared<ArrayType>(DataLocation::CallData)}, @@ -3294,7 +3292,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const }); case Kind::Transaction: return MemberList::MemberMap({ - {"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, + {"origin", make_shared<AddressType>()}, {"gasprice", make_shared<IntegerType>(256)} }); case Kind::ABI: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 0b1b5d6d..7ee66838 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -141,7 +141,7 @@ public: virtual ~Type() = default; enum class Category { - Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, + Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, FixedBytes, Contract, Struct, Function, Enum, Tuple, Mapping, TypeType, Modifier, Magic, Module, InaccessibleDynamic @@ -314,14 +314,45 @@ protected: }; /** - * Any kind of integer type (signed, unsigned, address). + * Type for addresses. + */ +class AddressType: public Type +{ +public: + virtual Category category() const override { return Category::Address; } + + explicit AddressType() + { + } + + virtual std::string richIdentifier() const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + + virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; } + virtual unsigned storageBytes() const override { return 160 / 8; } + virtual bool isValueType() const override { return true; } + + virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + + virtual std::string toString(bool _short) const override; + + virtual u256 literalValue(Literal const* _literal) const override; + + virtual TypePointer encodingType() const override { return shared_from_this(); } + virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } +}; + +/** + * Any kind of integer type (signed, unsigned). */ class IntegerType: public Type { public: enum class Modifier { - Unsigned, Signed, Address + Unsigned, Signed }; virtual Category category() const override { return Category::Integer; } @@ -339,17 +370,12 @@ public: virtual unsigned storageBytes() const override { return m_bits / 8; } virtual bool isValueType() const override { return true; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; - virtual std::string toString(bool _short) const override; - virtual u256 literalValue(Literal const* _literal) const override; - virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } unsigned numBits() const { return m_bits; } - bool isAddress() const { return m_modifier == Modifier::Address; } bool isSigned() const { return m_modifier == Modifier::Signed; } bigint minValue() const; @@ -729,7 +755,7 @@ public: { if (isSuper()) return TypePointer{}; - return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); + return std::make_shared<AddressType>(); } virtual TypePointer interfaceType(bool _inLibrary) const override { @@ -899,7 +925,6 @@ public: { Internal, ///< stack-call using plain JUMP External, ///< external call using CALL - CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage BareCall, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index dda77958..5e5fe84a 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -197,6 +197,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) templ("functionName", functionName); switch (_type.category()) { + case Type::Category::Address: + templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); + break; case Type::Category::Integer: { IntegerType const& type = dynamic_cast<IntegerType const&>(_type); @@ -239,7 +242,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) break; } case Type::Category::Contract: - templ("body", "cleaned := " + cleanupFunction(IntegerType(160, IntegerType::Modifier::Address)) + "(value)"); + templ("body", "cleaned := " + cleanupFunction(AddressType()) + "(value)"); break; case Type::Category::Enum: { @@ -284,6 +287,12 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) auto fromCategory = _from.category(); switch (fromCategory) { + case Type::Category::Address: + body = + Whiskers("converted := <convert>(value)") + ("convert", conversionFunction(IntegerType(160), _to)) + .render(); + break; case Type::Category::Integer: case Type::Category::RationalNumber: case Type::Category::Contract: @@ -314,16 +323,19 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) .render(); } else if (toCategory == Type::Category::FixedPoint) - { solUnimplemented("Not yet implemented - FixedPointType."); - } + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := <convert>(value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); else { solAssert( toCategory == Type::Category::Integer || toCategory == Type::Category::Contract, ""); - IntegerType const addressType(160, IntegerType::Modifier::Address); + IntegerType const addressType(160); IntegerType const& to = toCategory == Type::Category::Integer ? dynamic_cast<IntegerType const&>(_to) : @@ -375,6 +387,11 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) ("shift", shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := <convert>(value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); else { // clear for conversion to longer bytes @@ -410,7 +427,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) solAssert(false, ""); } - solAssert(!body.empty(), ""); + solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); templ("body", body); return templ.render(); }); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index b30851fb..e6ad6d9c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -657,6 +657,11 @@ void CompilerUtils::convertType( if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); } + else if (targetTypeCategory == Type::Category::Address) + { + solAssert(typeOnStack.numBytes() * 8 == 160, ""); + rightShiftNumberOnStack(256 - 160); + } else { // clear for conversion to longer bytes @@ -690,23 +695,33 @@ void CompilerUtils::convertType( break; case Type::Category::FixedPoint: solUnimplemented("Not yet implemented - FixedPointType."); + case Type::Category::Address: case Type::Category::Integer: case Type::Category::Contract: case Type::Category::RationalNumber: if (targetTypeCategory == Type::Category::FixedBytes) { - solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber, - "Invalid conversion to FixedBytesType requested."); + solAssert( + stackTypeCategory == Type::Category::Address || + stackTypeCategory == Type::Category::Integer || + stackTypeCategory == Type::Category::RationalNumber, + "Invalid conversion to FixedBytesType requested." + ); // conversion from bytes to string. no need to clean the high bit // only to shift left because of opposite alignment FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType); if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) + { if (targetBytesType.numBytes() * 8 > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); + } + else if (stackTypeCategory == Type::Category::Address) + solAssert(targetBytesType.numBytes() * 8 == 160, ""); leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); } else if (targetTypeCategory == Type::Category::Enum) { + solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested."); solAssert(_typeOnStack.mobileType(), ""); // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); @@ -733,8 +748,8 @@ void CompilerUtils::convertType( } else { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); - IntegerType addressType(160, IntegerType::Modifier::Address); + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, ""); + IntegerType addressType(160); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer ? dynamic_cast<IntegerType const&>(_targetType) : addressType; if (stackTypeCategory == Type::Category::RationalNumber) @@ -996,10 +1011,8 @@ void CompilerUtils::convertType( m_context << Instruction::ISZERO << Instruction::ISZERO; break; default: - if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Integer) + if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address) { - IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType); - solAssert(targetType.isAddress(), "Function type can only be converted to address."); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack); solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted."); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 53a06090..4150bc11 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -281,19 +281,19 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) if (_tuple.isInlineArray()) { ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type); - + solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); m_context << max(u256(32u), arrayType.memorySize()); utils().allocateMemory(); m_context << Instruction::DUP1; - + for (auto const& component: _tuple.components()) { component->accept(*this); utils().convertType(*component->annotation().type, *arrayType.baseType(), true); - utils().storeInMemoryDynamic(*arrayType.baseType(), true); + utils().storeInMemoryDynamic(*arrayType.baseType(), true); } - + m_context << Instruction::POP; } else @@ -566,7 +566,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; } case FunctionType::Kind::External: - case FunctionType::Kind::CallCode: case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: @@ -1075,7 +1074,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { arguments.front()->accept(*this); TypePointer firstArgType = arguments.front()->annotation().type; - TypePointers const& targetTypes = dynamic_cast<TupleType const&>(*_functionCall.annotation().type).components(); + TypePointers targetTypes; + if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type.get())) + targetTypes = targetTupleType->components(); + else + targetTypes = TypePointers{_functionCall.annotation().type}; if ( *firstArgType == ArrayType(DataLocation::CallData) || *firstArgType == ArrayType(DataLocation::CallData, true) @@ -1169,7 +1172,6 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); break; - case FunctionType::Kind::CallCode: case FunctionType::Kind::External: case FunctionType::Kind::Creation: case FunctionType::Kind::Send: @@ -1257,7 +1259,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - utils().convertType(type, IntegerType(160, IntegerType::Modifier::Address), true); + utils().convertType(type, AddressType(), true); m_context << identifier; } else @@ -1266,11 +1268,16 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) } case Type::Category::Integer: { + solAssert(false, "Invalid member access to integer"); + break; + } + case Type::Category::Address: + { if (member == "balance") { utils().convertType( *_memberAccess.expression().annotation().type, - IntegerType(160, IntegerType::Modifier::Address), + AddressType(), true ); m_context << Instruction::BALANCE; @@ -1278,11 +1285,11 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall", "staticcall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, - IntegerType(160, IntegerType::Modifier::Address), + AddressType(), true ); else - solAssert(false, "Invalid member access to integer"); + solAssert(false, "Invalid member access to address"); break; } case Type::Category::Function: @@ -1571,12 +1578,12 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { CompilerContext::LocationSetter locationSetter(m_context, _literal); TypePointer type = _literal.annotation().type; - + switch (type->category()) { case Type::Category::RationalNumber: case Type::Category::Bool: - case Type::Category::Integer: + case Type::Category::Address: m_context << type->literalValue(&_literal); break; case Type::Category::StringLiteral: @@ -1829,33 +1836,34 @@ void ExpressionCompiler::appendExternalFunctionCall( auto funKind = _functionType.kind(); solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); - - bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; - bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; + + bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; + bool isCallCode = funKind == FunctionType::Kind::BareCallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); unsigned retSize = 0; - TypePointers returnTypes; - if (returnSuccessCondition) - retSize = 0; // return value actually is success condition - else if (haveReturndatacopy) - returnTypes = _functionType.returnParameterTypes(); - else - returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes(); - bool dynamicReturnSize = false; - for (auto const& retType: returnTypes) - if (retType->isDynamicallyEncoded()) - { - solAssert(haveReturndatacopy, ""); - dynamicReturnSize = true; - retSize = 0; - break; - } + TypePointers returnTypes; + if (!returnSuccessConditionAndReturndata) + { + if (haveReturndatacopy) + returnTypes = _functionType.returnParameterTypes(); else - retSize += retType->calldataEncodedSize(); + returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes(); + + for (auto const& retType: returnTypes) + if (retType->isDynamicallyEncoded()) + { + solAssert(haveReturndatacopy, ""); + dynamicReturnSize = true; + retSize = 0; + break; + } + else + retSize += retType->calldataEncodedSize(); + } // Evaluate arguments. TypePointers argumentTypes; @@ -1959,7 +1967,7 @@ void ExpressionCompiler::appendExternalFunctionCall( bool existenceChecked = false; // Check the the target contract exists (has code) for non-low-level calls. - if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) + if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; // TODO: error message? @@ -1999,7 +2007,7 @@ void ExpressionCompiler::appendExternalFunctionCall( (_functionType.gasSet() ? 1 : 0) + (!_functionType.isBareCall() ? 1 : 0); - if (returnSuccessCondition) + if (returnSuccessConditionAndReturndata) m_context << swapInstruction(remainsSize); else { @@ -2010,9 +2018,31 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().popStackSlots(remainsSize); - if (returnSuccessCondition) + if (returnSuccessConditionAndReturndata) { - // already there + // success condition is already there + // The return parameter types can be empty, when this function is used as + // an internal helper function e.g. for ``send`` and ``transfer``. In that + // case we're only interested in the success condition, not the return data. + if (!_functionType.returnParameterTypes().empty()) + { + if (haveReturndatacopy) + { + m_context << Instruction::RETURNDATASIZE; + m_context.appendInlineAssembly(R"({ + switch v case 0 { + v := 0x60 + } default { + v := mload(0x40) + mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) + mstore(v, returndatasize()) + returndatacopy(add(v, 0x20), 0, returndatasize()) + } + })", {"v"}); + } + else + utils().pushZeroPointer(); + } } else if (funKind == FunctionType::Kind::RIPEMD160) { diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 88c1e56a..49c90405 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -394,7 +394,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) void SMTChecker::endVisit(Literal const& _literal) { Type const& type = *_literal.annotation().type; - if (type.category() == Type::Category::Integer || type.category() == Type::Category::RationalNumber) + if (type.category() == Type::Category::Integer || type.category() == Type::Category::Address || type.category() == Type::Category::RationalNumber) { if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type)) solAssert(!rational->isFractional(), ""); @@ -540,6 +540,8 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio TypePointer type = _variable.type(); if (auto const* intType = dynamic_cast<IntegerType const*>(type.get())) checkUnderOverflow(_value, *intType, _location); + else if (dynamic_cast<AddressType const*>(type.get())) + checkUnderOverflow(_value, IntegerType(160), _location); m_interface->addAssertion(newValue(_variable) == _value); } @@ -862,6 +864,7 @@ void SMTChecker::createExpr(Expression const& _e) m_expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e))); break; } + case Type::Category::Address: case Type::Category::Integer: m_expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e))); break; diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp index f3213e03..4fc2dd45 100644 --- a/libsolidity/formal/SSAVariable.cpp +++ b/libsolidity/formal/SSAVariable.cpp @@ -50,7 +50,7 @@ bool SSAVariable::isSupportedType(Type::Category _category) bool SSAVariable::isInteger(Type::Category _category) { - return _category == Type::Category::Integer; + return _category == Type::Category::Integer || _category == Type::Category::Address; } bool SSAVariable::isBool(Type::Category _category) diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp index 5e71fdcc..4f65b1fd 100644 --- a/libsolidity/formal/SymbolicIntVariable.cpp +++ b/libsolidity/formal/SymbolicIntVariable.cpp @@ -29,7 +29,11 @@ SymbolicIntVariable::SymbolicIntVariable( ): SymbolicVariable(_decl, _interface) { - solAssert(m_declaration.type()->category() == Type::Category::Integer, ""); + solAssert( + m_declaration.type()->category() == Type::Category::Integer || + m_declaration.type()->category() == Type::Category::Address, + "" + ); } smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const @@ -44,9 +48,11 @@ void SymbolicIntVariable::setZeroValue(int _seq) void SymbolicIntVariable::setUnknownValue(int _seq) { - auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type()); - m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType)); - m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType)); + auto intType = dynamic_pointer_cast<IntegerType const>(m_declaration.type()); + if (!intType) + intType = make_shared<IntegerType>(160); + m_interface.addAssertion(valueAtSequence(_seq) >= minValue(*intType)); + m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(*intType)); } smt::Expression SymbolicIntVariable::minValue(IntegerType const& _t) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 0bee2a91..c9c26f57 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -564,7 +564,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( bool isIndexed = false; bool isDeclaredConst = false; Declaration::Visibility visibility(Declaration::Visibility::Default); - VariableDeclaration::Location location = VariableDeclaration::Location::Default; + VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified; ASTPointer<ASTString> identifier; while (true) @@ -572,6 +572,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( Token::Value token = m_scanner->currentToken(); if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token)) { + nodeFactory.markEndPosition(); if (visibility != Declaration::Visibility::Default) { parserError(string( @@ -592,7 +593,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( isDeclaredConst = true; else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token)) { - if (location != VariableDeclaration::Location::Default) + if (location != VariableDeclaration::Location::Unspecified) parserError(string("Location already specified.")); else if (!type) parserError(string("Location specifier needs explicit type name.")); @@ -616,21 +617,21 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( } else break; + nodeFactory.markEndPosition(); m_scanner->next(); } } - nodeFactory.markEndPosition(); if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier) { identifier = make_shared<ASTString>(""); solAssert(!_options.allowVar, ""); // allowEmptyName && allowVar makes no sense - if (type) - nodeFactory.setEndPositionFromNode(type); - // if type is null this has already caused an error } else + { + nodeFactory.markEndPosition(); identifier = expectIdentifierToken(); + } ASTPointer<Expression> value; if (_options.allowInitialValue) { diff --git a/libsolidity/parsing/Token.cpp b/libsolidity/parsing/Token.cpp index 5ce74316..27acb7d4 100644 --- a/libsolidity/parsing/Token.cpp +++ b/libsolidity/parsing/Token.cpp @@ -63,7 +63,7 @@ void ElementaryTypeNameToken::assertDetails(Token::Value _baseType, unsigned con { solAssert(_second == 0, "There should not be a second size argument to type " + string(Token::toString(_baseType)) + "."); solAssert( - _first <= 256 && _first % 8 == 0, + _first <= 256 && _first % 8 == 0, "No elementary type " + string(Token::toString(_baseType)) + to_string(_first) + "." ); } @@ -165,7 +165,7 @@ tuple<Token::Value, unsigned int, unsigned int> Token::fromIdentifierOrKeyword(s else return make_tuple(Token::FixedMxN, m, n); } - } + } } return make_tuple(Token::Identifier, 0, 0); } |