diff options
Diffstat (limited to 'libsolidity')
37 files changed, 839 insertions, 294 deletions
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 34cb61d8..6a858d36 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -38,6 +38,7 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ make_shared<MagicVariableDeclaration>("addmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), + make_shared<MagicVariableDeclaration>("blockhash", make_shared<FunctionType>(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)), make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 296a39c2..f91eaf6e 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -280,7 +280,8 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information // We use the latest EVM version because we will re-run it anyway. assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); + boost::optional<Error::Type> errorTypeForLoose = m_experimental050Mode ? Error::Type::SyntaxError : Error::Type::Warning; + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), errorTypeForLoose, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index bd8ee597..d96f8748 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -50,6 +50,16 @@ void StaticAnalyzer::endVisit(ContractDefinition const&) bool StaticAnalyzer::visit(FunctionDefinition const& _function) { + const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface; + + if (_function.noVisibilitySpecified()) + m_errorReporter.warning( + _function.location(), + "No visibility specified. Defaulting to \"" + + Declaration::visibilityToString(_function.visibility()) + + "\". " + + (isInterface ? "In interfaces it defaults to external." : "") + ); if (_function.isImplemented()) m_currentFunction = &_function; else @@ -139,6 +149,38 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement) bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) { + bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + + if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) + { + if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas") + { + if (v050) + m_errorReporter.typeError( + _memberAccess.location(), + "\"msg.gas\" has been deprecated in favor of \"gasleft()\"" + ); + else + m_errorReporter.warning( + _memberAccess.location(), + "\"msg.gas\" has been deprecated in favor of \"gasleft()\"" + ); + } + if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash") + { + if (v050) + m_errorReporter.typeError( + _memberAccess.location(), + "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" + ); + else + m_errorReporter.warning( + _memberAccess.location(), + "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" + ); + } + } + 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") @@ -151,7 +193,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) if (type->kind() == FunctionType::Kind::BareCallCode) { - if (m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + if (v050) m_errorReporter.typeError( _memberAccess.location(), "\"callcode\" has been deprecated in favour of \"delegatecall\"." diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index ddac194b..343b4ba8 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -214,16 +214,22 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function) { bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (_function.noVisibilitySpecified()) + if (v050 && _function.noVisibilitySpecified()) + m_errorReporter.syntaxError(_function.location(), "No visibility specified."); + + if (_function.isOldStyleConstructor()) { if (v050) - m_errorReporter.syntaxError(_function.location(), "No visibility specified."); + m_errorReporter.syntaxError( + _function.location(), + "Functions are not allowed to have the same name as the contract. " + "If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it." + ); else m_errorReporter.warning( _function.location(), - "No visibility specified. Defaulting to \"" + - Declaration::visibilityToString(_function.visibility()) + - "\"." + "Defining constructors as functions with the same name as the contract is deprecated. " + "Use \"constructor(...) { ... }\" instead." ); } return true; @@ -255,3 +261,17 @@ bool SyntaxChecker::visit(VariableDeclaration const& _declaration) } return true; } + +bool SyntaxChecker::visit(StructDefinition const& _struct) +{ + bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + + if (_struct.members().empty()) + { + if (v050) + m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed."); + else + m_errorReporter.warning(_struct.location(), "Defining empty structs is deprecated."); + } + return true; +} diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 871bf0a9..1579df57 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -71,6 +71,8 @@ private: virtual bool visit(VariableDeclaration const& _declaration) override; + virtual bool visit(StructDefinition const& _struct) 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 9846a0d0..620dfca4 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -34,6 +34,29 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ + +bool typeSupportedByOldABIEncoder(Type const& _type) +{ + if (_type.dataStoredIn(DataLocation::Storage)) + return true; + else if (_type.category() == Type::Category::Struct) + return false; + else if (_type.category() == Type::Category::Array) + { + auto const& arrayType = dynamic_cast<ArrayType const&>(_type); + auto base = arrayType.baseType(); + if (!typeSupportedByOldABIEncoder(*base)) + return false; + else if (base->category() == Type::Category::Array && base->isDynamicallySized()) + return false; + } + return true; +} + +} + bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { @@ -355,7 +378,16 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func function.annotation().superFunction = &super; if (function.visibility() != super.visibility()) + { + // visibility is enforced to be external in interfaces, but a contract can override that with public + if ( + super.inContractKind() == ContractDefinition::ContractKind::Interface && + function.inContractKind() != ContractDefinition::ContractKind::Interface && + function.visibility() == FunctionDefinition::Visibility::Public + ) + return; overrideError(function, super, "Overriding function visibility differs."); + } else if (function.stateMutability() != super.stateMutability()) overrideError( @@ -561,13 +593,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function) 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) + !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && + !typeSupportedByOldABIEncoder(*type(*var)) ) m_errorReporter.typeError( var->location(), - "Structs are only supported in the new experimental ABI encoder. " + "This type is only supported in the new experimental ABI encoder. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); @@ -872,10 +903,15 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) }; solAssert(!_inlineAssembly.annotation().analysisInfo, ""); _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + boost::optional<Error::Type> errorTypeForLoose = + m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) ? + Error::Type::SyntaxError : + Error::Type::Warning; assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, m_evmVersion, + errorTypeForLoose, assembly::AsmFlavour::Loose, identifierAccess ); @@ -1524,16 +1560,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) _functionCall.expression().annotation().isPure && functionType->isPure(); + bool allowDynamicTypes = m_evmVersion.supportsReturndata(); if (!functionType) { m_errorReporter.typeError(_functionCall.location(), "Type is not callable"); _functionCall.annotation().type = make_shared<TupleType>(); return false; } - else if (functionType->returnParameterTypes().size() == 1) - _functionCall.annotation().type = functionType->returnParameterTypes().front(); + + auto returnTypes = + allowDynamicTypes ? + functionType->returnParameterTypes() : + functionType->returnParameterTypesWithoutDynamicTypes(); + if (returnTypes.size() == 1) + _functionCall.annotation().type = returnTypes.front(); else - _functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes()); + _functionCall.annotation().type = make_shared<TupleType>(returnTypes); if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression())) { @@ -1602,9 +1644,20 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) auto const& argType = type(*arguments[i]); if (functionType->takesArbitraryParameters()) { + bool errored = false; if (auto t = dynamic_cast<RationalNumberType const*>(argType.get())) if (!t->mobileType()) + { m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); + errored = true; + } + if (!errored && !( + argType->mobileType() && + argType->mobileType()->interfaceType(false) && + argType->mobileType()->interfaceType(false)->encodingType() && + !(dynamic_cast<StructType const*>(argType->mobileType()->interfaceType(false)->encodingType().get())) + )) + m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded."); } else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) m_errorReporter.typeError( @@ -1831,6 +1884,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (exprType->category() == Type::Category::Contract) { + // Warn about using address members on contracts + bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr)) + if (addressMember.name == memberName && *annotation.type == *addressMember.type) + { + solAssert(!v050, "Address member still present on contract in v0.5.0."); + m_errorReporter.warning( + _memberAccess.location(), + "Using contract member \"" + memberName +"\" inherited from the address type is deprecated." + + " Convert the contract to \"address\" type to access the member." + ); + } + + // Warn about using send or transfer with a non-payable fallback function. if (auto callType = dynamic_cast<FunctionType const*>(type(_memberAccess).get())) { auto kind = callType->kind(); @@ -2043,13 +2110,13 @@ void TypeChecker::endVisit(Literal const& _literal) m_errorReporter.fatalTypeError( _literal.location(), "Hexadecimal numbers cannot be used with unit denominations. " - "You can use an expression of the form '0x1234 * 1 day' instead." + "You can use an expression of the form \"0x1234 * 1 day\" instead." ); else m_errorReporter.warning( _literal.location(), "Hexadecimal numbers with unit denominations are deprecated. " - "You can use an expression of the form '0x1234 * 1 day' instead." + "You can use an expression of the form \"0x1234 * 1 day\" instead." ); } if (!_literal.annotation().type) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 27220b1f..d8ad009d 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -290,6 +290,13 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation); } +ContractDefinition::ContractKind FunctionDefinition::inContractKind() const +{ + auto contractDef = dynamic_cast<ContractDefinition const*>(scope()); + solAssert(contractDef, "Enclosing Scope of FunctionDefinition was not set."); + return contractDef->contractKind(); +} + shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const { if (_internal) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 863ad2fe..56bb412c 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -203,6 +203,7 @@ public: bool isPublic() const { return visibility() >= Visibility::Public; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } + bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; } std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } @@ -606,7 +607,8 @@ public: StateMutability stateMutability() const { return m_stateMutability; } bool isConstructor() const { return m_isConstructor; } - bool isFallback() const { return name().empty(); } + bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); } + bool isFallback() const { return !m_isConstructor && name().empty(); } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); } @@ -623,6 +625,8 @@ public: /// arguments separated by commas all enclosed in parentheses without any spaces. std::string externalSignature() const; + ContractDefinition::ContractKind inContractKind() const; + virtual TypePointer type() const override; /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index a17778b4..30ea7ec5 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -29,8 +29,8 @@ namespace solidity enum class ExperimentalFeature { - SMTChecker, ABIEncoderV2, // new ABI encoder that makes use of JULIA + SMTChecker, V050, // v0.5.0 breaking changes Test, TestOnlyAnalysis @@ -45,8 +45,8 @@ static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames = { - { "SMTChecker", ExperimentalFeature::SMTChecker }, { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 }, + { "SMTChecker", ExperimentalFeature::SMTChecker }, { "v0.5.0", ExperimentalFeature::V050 }, { "__test", ExperimentalFeature::Test }, { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis }, diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 26bde1c4..42fd1c3d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -208,9 +208,9 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) case Token::UInt: return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned); case Token::Fixed: - return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed); + return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed); case Token::UFixed: - return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned); + return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned); case Token::Byte: return make_shared<FixedBytesType>(1); case Token::Address: @@ -304,7 +304,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition ); for (FunctionDefinition const* function: library.definedFunctions()) { - if (!function->isVisibleInDerivedContracts() || seenFunctions.count(function)) + if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function)) continue; seenFunctions.insert(function); FunctionType funType(*function, false); @@ -327,7 +327,7 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType)) return !otherInt->isAddress(); else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType)) - return otherRat->integerType() && !otherRat->integerType()->isSigned(); + return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned(); else return false; } @@ -1262,6 +1262,8 @@ bool ContractType::isPayable() const TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const { + if (isSuper()) + return TypePointer{}; return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); } @@ -1589,8 +1591,6 @@ bool ArrayType::canBeUsedExternally(bool _inLibrary) const return true; else if (!m_baseType->canBeUsedExternally(_inLibrary)) return false; - else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) - return false; else return true; } @@ -2313,6 +2313,18 @@ vector<string> FunctionType::parameterNames() const return vector<string>(m_parameterNames.cbegin() + 1, m_parameterNames.cend()); } +TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const +{ + TypePointers returnParameterTypes = m_returnParameterTypes; + + if (m_kind == Kind::External || m_kind == Kind::CallCode || m_kind == Kind::DelegateCall) + for (auto& param: returnParameterTypes) + if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage)) + param = make_shared<InaccessibleDynamicType>(); + + return returnParameterTypes; +} + TypePointers FunctionType::parameterTypes() const { if (!bound()) @@ -2346,6 +2358,7 @@ string FunctionType::richIdentifier() const case Kind::Log2: id += "log2"; break; case Kind::Log3: id += "log3"; break; case Kind::Log4: id += "log4"; break; + case Kind::GasLeft: id += "gasleft"; break; case Kind::Event: id += "event"; break; case Kind::SetGas: id += "setgas"; break; case Kind::SetValue: id += "setvalue"; break; @@ -2773,18 +2786,9 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) kind = Kind::DelegateCall; } - TypePointers returnParameterTypes = m_returnParameterTypes; - if (kind != Kind::Internal) - { - // Alter dynamic types to be non-accessible. - for (auto& param: returnParameterTypes) - if (param->isDynamicallySized()) - param = make_shared<InaccessibleDynamicType>(); - } - return make_shared<FunctionType>( parameterTypes, - returnParameterTypes, + m_returnParameterTypes, m_parameterNames, m_returnParameterNames, kind, @@ -2876,7 +2880,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current } if (contract.isLibrary()) for (FunctionDefinition const* function: contract.definedFunctions()) - if (function->isVisibleInDerivedContracts()) + if (function->isVisibleAsLibraryMember()) members.push_back(MemberList::Member( function->name(), FunctionType(*function).asMemberFunction(true), @@ -3000,10 +3004,8 @@ bool MagicType::operator==(Type const& _other) const return other.m_kind == m_kind; } -MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const* _contract) const +MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const { - solAssert(_contract, ""); - const bool v050 = _contract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); switch (m_kind) { case Kind::Block: @@ -3016,17 +3018,13 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const* _contra {"gaslimit", make_shared<IntegerType>(256)} }); case Kind::Message: - { - std::vector<MemberList::Member> members = { + return MemberList::MemberMap({ {"sender", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, + {"gas", make_shared<IntegerType>(256)}, {"value", make_shared<IntegerType>(256)}, {"data", make_shared<ArrayType>(DataLocation::CallData)}, {"sig", make_shared<FixedBytesType>(4)} - }; - if (!v050) - members.emplace_back("gas", make_shared<IntegerType>(256)); - return members; - } + }); case Kind::Transaction: return MemberList::MemberMap({ {"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c20a025f..2c392705 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -229,6 +229,9 @@ public: /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } virtual unsigned sizeOnStack() const { return 1; } + /// If it is possible to initialize such a value in memory by just writing zeros + /// of the size memoryHeadSize(). + virtual bool hasSimpleZeroValueInMemory() const { return true; } /// @returns the mobile (in contrast to static) type corresponding to the given type. /// This returns the corresponding IntegerType or FixedPointType for RationalNumberType /// and the pointer type for storage reference types. @@ -568,6 +571,7 @@ public: virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } + virtual bool hasSimpleZeroValueInMemory() const override { return false; } /// Storage references can be pointers or bound references. In general, local variables are of /// pointer type, state variables are bound references. Assignments to pointers or deleting @@ -692,22 +696,27 @@ public: virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded ) const override { + solAssert(!isSuper(), ""); return encodingType()->calldataEncodedSize(_padded); } - virtual unsigned storageBytes() const override { return 20; } - virtual bool canLiveOutsideStorage() const override { return true; } + virtual unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } + virtual bool canLiveOutsideStorage() const override { return !isSuper(); } virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; } - virtual bool isValueType() const override { return true; } + virtual bool isValueType() const override { return !isSuper(); } virtual std::string toString(bool _short) const override; virtual std::string canonicalName() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override { + if (isSuper()) + return TypePointer{}; return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); } virtual TypePointer interfaceType(bool _inLibrary) const override { + if (isSuper()) + return TypePointer{}; return _inLibrary ? shared_from_this() : encodingType(); } @@ -850,6 +859,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { return false; } virtual TypePointer mobileType() const override; /// Converts components to their temporary types and performs some wildcard matching. virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; @@ -973,6 +983,9 @@ public: TypePointers parameterTypes() const; std::vector<std::string> parameterNames() const; TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } + /// @returns the list of return parameter types. All dynamically-sized types (this excludes + /// storage pointers) are replaced by InaccessibleDynamicType instances. + TypePointers returnParameterTypesWithoutDynamicTypes() const; std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; } /// @returns the "self" parameter type for a bound function TypePointer const& selfType() const; @@ -991,6 +1004,7 @@ public: virtual bool isValueType() const override { return true; } virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { return false; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; @@ -1096,6 +1110,8 @@ public: return _inLibrary ? shared_from_this() : TypePointer(); } virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } + /// Cannot be stored in memory, but just in case. + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } TypePointer const& keyType() const { return m_keyType; } TypePointer const& valueType() const { return m_valueType; } @@ -1124,6 +1140,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; @@ -1146,6 +1163,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override { return 0; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string richIdentifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; @@ -1171,6 +1189,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual unsigned sizeOnStack() const override { return 0; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; @@ -1201,6 +1220,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual unsigned sizeOnStack() const override { return 0; } virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; @@ -1230,6 +1250,7 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } virtual bool isValueType() const override { return true; } virtual unsigned sizeOnStack() const override { return 1; } + virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } virtual std::string toString(bool) const override { return "inaccessible dynamic type"; } virtual TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); } }; diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 00f59065..8e890854 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -253,6 +253,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) templ("body", w.render()); break; } + case Type::Category::InaccessibleDynamic: + templ("body", "cleaned := 0"); + break; default: solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index ce8cbb5f..4703fc1f 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -741,10 +741,10 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const if (_type.isByteArray()) // For a "long" byte array, store length as 2*length+1 _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - _context<< Instruction::DUP4 << Instruction::SSTORE; + _context << Instruction::DUP4 << Instruction::SSTORE; // skip if size is not reduced _context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::ISZERO << Instruction::GT; + << Instruction::GT << Instruction::ISZERO; _context.appendConditionalJumpTo(resizeEnd); // size reduced, clear the end of the array diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ebf0213a..47333046 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -193,14 +193,22 @@ Declaration const* CompilerContext::nextFunctionToCompile() const return m_functionCompilationQueue.nextFunctionToCompile(); } -ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const +ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier( + ModifierDefinition const& _modifier +) const { + // Libraries do not allow inheritance and their functions can be inlined, so we should not + // search the inheritance hierarchy (which will be the wrong one in case the function + // is inlined). + if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope())) + if (scope->isLibrary()) + return _modifier; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); for (ContractDefinition const* contract: m_inheritanceHierarchy) for (ModifierDefinition const* modifier: contract->functionModifiers()) - if (modifier->name() == _name) + if (modifier->name() == _modifier.name()) return *modifier; - solAssert(false, "Function modifier " + _name + " not found."); + solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy."); } unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const @@ -330,6 +338,7 @@ void CompilerContext::appendInlineAssembly( analysisInfo, errorReporter, m_evmVersion, + boost::none, assembly::AsmFlavour::Strict, identifierAccess.resolve ).analyze(*parserResult); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index cf626683..7b663277 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -130,7 +130,7 @@ public: void appendMissingLowLevelFunctions(); ABIFunctions& abiFunctions() { return m_abiFunctions; } - ModifierDefinition const& functionModifier(std::string const& _name) const; + ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; /// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 533aca5c..79aef7b0 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -21,12 +21,16 @@ */ #include <libsolidity/codegen/CompilerUtils.h> + #include <libsolidity/ast/AST.h> -#include <libevmasm/Instruction.h> #include <libsolidity/codegen/ArrayUtils.h> #include <libsolidity/codegen/LValue.h> #include <libsolidity/codegen/ABIFunctions.h> +#include <libevmasm/Instruction.h> + +#include <libdevcore/Whiskers.h> + using namespace std; namespace dev @@ -36,11 +40,17 @@ namespace solidity const unsigned CompilerUtils::dataStartOffset = 4; const size_t CompilerUtils::freeMemoryPointer = 64; +const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; +const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; const unsigned CompilerUtils::identityContractAddress = 4; +static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area."); +static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer."); +static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area."); + void CompilerUtils::initialiseFreeMemoryPointer() { - m_context << u256(freeMemoryPointer + 32); + m_context << u256(generalPurposeMemoryStart); storeFreeMemoryPointer(); } @@ -139,7 +149,6 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External ) { - solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); combineExternalFunctionType(true); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; @@ -159,6 +168,163 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } } +void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory, bool _revertOnOutOfBounds) +{ + /// Stack: <source_offset> <length> + if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + // Use the new JULIA-based decoding function + auto stackHeightBefore = m_context.stackHeight(); + abiDecodeV2(_typeParameters, _fromMemory); + solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, ""); + return; + } + + //@todo this does not yet support nested dynamic arrays + + if (_revertOnOutOfBounds) + { + size_t encodedSize = 0; + for (auto const& t: _typeParameters) + encodedSize += t->decodingType()->calldataEncodedSize(true); + m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"}); + } + + m_context << Instruction::DUP2 << Instruction::ADD; + m_context << Instruction::SWAP1; + /// Stack: <input_end> <source_offset> + + // Retain the offset pointer as base_offset, the point from which the data offsets are computed. + m_context << Instruction::DUP1; + for (TypePointer const& parameterType: _typeParameters) + { + // stack: v1 v2 ... v(k-1) input_end base_offset current_offset + TypePointer type = parameterType->decodingType(); + solUnimplementedAssert(type, "No decoding type found."); + if (type->category() == Type::Category::Array) + { + auto const& arrayType = dynamic_cast<ArrayType const&>(*type); + solUnimplementedAssert(!arrayType.baseType()->isDynamicallyEncoded(), "Nested arrays not yet implemented."); + if (_fromMemory) + { + solUnimplementedAssert( + arrayType.baseType()->isValueType(), + "Nested memory arrays not yet implemented here." + ); + // @todo If base type is an array or struct, it is still calldata-style encoded, so + // we would have to convert it like below. + solAssert(arrayType.location() == DataLocation::Memory, ""); + if (arrayType.isDynamicallySized()) + { + // compute data pointer + m_context << Instruction::DUP1 << Instruction::MLOAD; + if (_revertOnOutOfBounds) + { + // Check that the data pointer is valid and that length times + // item size is still inside the range. + Whiskers templ(R"({ + if gt(ptr, 0x100000000) { revert(0, 0) } + ptr := add(ptr, base_offset) + let array_data_start := add(ptr, 0x20) + if gt(array_data_start, input_end) { revert(0, 0) } + let array_length := mload(ptr) + if or( + gt(array_length, 0x100000000), + gt(add(array_data_start, mul(array_length, <item_size>)), input_end) + ) { revert(0, 0) } + })"); + templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true))); + m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"}); + } + else + m_context << Instruction::DUP3 << Instruction::ADD; + // stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k) + moveIntoStack(3); + m_context << u256(0x20) << Instruction::ADD; + } + else + { + // Size has already been checked for this one. + moveIntoStack(2); + m_context << Instruction::DUP3; + m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD; + } + } + else + { + // first load from calldata and potentially convert to memory if arrayType is memory + TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); + if (calldataType->isDynamicallySized()) + { + // put on stack: data_pointer length + loadFromMemoryDynamic(IntegerType(256), !_fromMemory); + m_context << Instruction::SWAP1; + // stack: input_end base_offset next_pointer data_offset + if (_revertOnOutOfBounds) + m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"}); + m_context << Instruction::DUP3 << Instruction::ADD; + // stack: input_end base_offset next_pointer array_head_ptr + if (_revertOnOutOfBounds) + m_context.appendInlineAssembly( + "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }", + {"input_end", "base_offset", "next_ptr", "array_head_ptr"} + ); + // retrieve length + loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); + // stack: input_end base_offset next_pointer array_length data_pointer + m_context << Instruction::SWAP2; + // stack: input_end base_offset data_pointer array_length next_pointer + if (_revertOnOutOfBounds) + { + unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true); + m_context.appendInlineAssembly(R"({ + if or( + gt(array_length, 0x100000000), + gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end) + ) { revert(0, 0) } + })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); + } + } + else + { + // size has already been checked + // stack: input_end base_offset data_offset + m_context << Instruction::DUP1; + m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD; + } + if (arrayType.location() == DataLocation::Memory) + { + // stack: input_end base_offset calldata_ref [length] next_calldata + // copy to memory + // move calldata type up again + moveIntoStack(calldataType->sizeOnStack()); + convertType(*calldataType, arrayType, false, false, true); + // fetch next pointer again + moveToStackTop(arrayType.sizeOnStack()); + } + // move input_end up + // stack: input_end base_offset calldata_ref [length] next_calldata + moveToStackTop(2 + arrayType.sizeOnStack()); + m_context << Instruction::SWAP1; + // stack: base_offset calldata_ref [length] input_end next_calldata + moveToStackTop(2 + arrayType.sizeOnStack()); + m_context << Instruction::SWAP1; + // stack: calldata_ref [length] input_end base_offset next_calldata + } + } + else + { + solAssert(!type->isDynamicallyEncoded(), "Unknown dynamically sized type: " + type->toString()); + loadFromMemoryDynamic(*type, !_fromMemory, true); + // stack: v1 v2 ... v(k-1) input_end base_offset v(k) mem_offset + moveToStackTop(1, type->sizeOnStack()); + moveIntoStack(3, type->sizeOnStack()); + } + // stack: v1 v2 ... v(k-1) v(k) input_end base_offset next_offset + } + popStackSlots(3); +} + void CompilerUtils::encodeToMemory( TypePointers const& _givenTypes, TypePointers const& _targetTypes, @@ -321,15 +487,13 @@ void CompilerUtils::abiEncodeV2( void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) { - // stack: <source_offset> + // stack: <source_offset> <length> [stack top] auto ret = m_context.pushNewTag(); + moveIntoStack(2); + // stack: <return tag> <source_offset> <length> [stack top] + m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::SWAP1; - if (_fromMemory) - // TODO pass correct size for the memory case - m_context << (u256(1) << 63); - else - m_context << Instruction::CALLDATASIZE; - m_context << Instruction::SWAP1; + // stack: <return tag> <end> <start> string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); m_context.appendJumpTo(m_context.namedTag(decoderName)); m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); @@ -338,14 +502,34 @@ void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromM void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { - auto repeat = m_context.newTag(); - m_context << repeat; - pushZeroValue(*_type.baseType()); - storeInMemoryDynamic(*_type.baseType()); - m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; - m_context << Instruction::SUB << Instruction::SWAP1; - m_context << Instruction::DUP2; - m_context.appendConditionalJumpTo(repeat); + if (_type.baseType()->hasSimpleZeroValueInMemory()) + { + solAssert(_type.baseType()->isValueType(), ""); + Whiskers templ(R"({ + let size := mul(length, <element_size>) + // cheap way of zero-initializing a memory range + codecopy(memptr, codesize(), size) + memptr := add(memptr, size) + })"); + templ("element_size", to_string(_type.baseType()->memoryHeadSize())); + m_context.appendInlineAssembly(templ.render(), {"length", "memptr"}); + } + else + { + // TODO: Potential optimization: + // When we create a new multi-dimensional dynamic array, each element + // is initialized to an empty array. It actually does not hurt + // to re-use exactly the same empty array for all elements. Currently, + // a new one is created each time. + auto repeat = m_context.newTag(); + m_context << repeat; + pushZeroValue(*_type.baseType()); + storeInMemoryDynamic(*_type.baseType()); + m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; + m_context << Instruction::SUB << Instruction::SWAP1; + m_context << Instruction::DUP2; + m_context.appendConditionalJumpTo(repeat); + } m_context << Instruction::SWAP1 << Instruction::POP; } @@ -427,7 +611,7 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned) leftShiftNumberOnStack(64); } -void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) +void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly) { m_context << m_context.functionEntryLabel(_function).pushTag(); // If there is a runtime context, we have to merge both labels into the same @@ -435,9 +619,10 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) if (CompilerContext* rtc = m_context.runtimeContext()) { leftShiftNumberOnStack(32); - m_context << - rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << - Instruction::OR; + if (_runtimeOnly) + m_context << + rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << + Instruction::OR; } } @@ -873,6 +1058,13 @@ void CompilerUtils::pushZeroValue(Type const& _type) return; } solAssert(referenceType->location() == DataLocation::Memory, ""); + if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) + if (arrayType->isDynamicallySized()) + { + // Push a memory location that is (hopefully) always zero. + pushZeroPointer(); + return; + } TypePointer type = _type.shared_from_this(); m_context.callLowLevelFunction( @@ -893,13 +1085,8 @@ void CompilerUtils::pushZeroValue(Type const& _type) } else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) { - if (arrayType->isDynamicallySized()) - { - // zero length - _context << u256(0); - utils.storeInMemoryDynamic(IntegerType(256)); - } - else if (arrayType->length() > 0) + solAssert(!arrayType->isDynamicallySized(), ""); + if (arrayType->length() > 0) { _context << arrayType->length() << Instruction::SWAP1; // stack: items_to_do memory_pos @@ -916,6 +1103,11 @@ void CompilerUtils::pushZeroValue(Type const& _type) ); } +void CompilerUtils::pushZeroPointer() +{ + m_context << u256(zeroPointer); +} + void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable)); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 3cde281b..a32c5c6e 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -88,6 +88,15 @@ public: /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWords = true); + /// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers. + /// From memory if @a _fromMemory is true, otherwise from call data. + /// Calls revert if @a _revertOnOutOfBounds is true and the supplied size is shorter + /// than the static data requirements or if dynamic data pointers reach outside of the + /// area. Also has a hard cap of 0x100000000 for any given length/offset field. + /// Stack pre: <source_offset> <length> + /// Stack post: <value0> <value1> ... <valuen> + void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false, bool _revertOnOutOfBounds = false); + /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. /// Removes the values from the stack and leaves the updated memory pointer. @@ -149,7 +158,7 @@ public: /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, /// the data is taken from memory instead of from calldata. /// Can allocate memory. - /// Stack pre: <source_offset> + /// Stack pre: <source_offset> <length> /// Stack post: <value0> <value1> ... <valuen> void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); @@ -179,7 +188,8 @@ public: /// Appends code that combines the construction-time (if available) and runtime function /// entry label of the given function into a single stack slot. /// Note: This might cause the compilation queue of the runtime context to be extended. - void pushCombinedFunctionEntryLabel(Declaration const& _function); + /// If @a _runtimeOnly, the entry label will include the runtime assembly tag. + void pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly = true); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory @@ -200,6 +210,9 @@ public: /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. void pushZeroValue(Type const& _type); + /// Pushes a pointer to the stack that points to a (potentially shared) location in memory + /// that always contains a zero. It is not allowed to write there. + void pushZeroPointer(); /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); @@ -245,6 +258,10 @@ public: /// Position of the free-memory-pointer in memory; static const size_t freeMemoryPointer; + /// Position of the memory slot that is always zero. + static const size_t zeroPointer; + /// Starting offset for memory available to the user (aka the contract). + static const size_t generalPurposeMemoryStart; private: /// Address of the precompiled identity contract. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 5a9498f0..ebd9139a 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -143,8 +143,9 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c for (auto const& modifier: constructor->modifiers()) { auto baseContract = dynamic_cast<ContractDefinition const*>( - modifier->name()->annotation().referencedDeclaration); - if (baseContract) + modifier->name()->annotation().referencedDeclaration + ); + if (baseContract && !modifier->arguments().empty()) if (m_baseArguments.count(baseContract->constructor()) == 0) m_baseArguments[baseContract->constructor()] = &modifier->arguments(); } @@ -156,7 +157,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c ); solAssert(baseContract, ""); - if (m_baseArguments.count(baseContract->constructor()) == 0) + if (!m_baseArguments.count(baseContract->constructor()) && !base->arguments().empty()) m_baseArguments[baseContract->constructor()] = &base->arguments(); } } @@ -238,6 +239,7 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc solAssert(m_baseArguments.count(&_constructor), ""); std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor]; solAssert(arguments, ""); + solAssert(arguments->size() == constructorType.parameterTypes().size(), ""); for (unsigned i = 0; i < arguments->size(); ++i) compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]); } @@ -278,9 +280,10 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) m_context.appendProgramSize(); m_context << Instruction::DUP4 << Instruction::CODECOPY; m_context << Instruction::DUP2 << Instruction::ADD; + m_context << Instruction::DUP1; CompilerUtils(m_context).storeFreeMemoryPointer(); // stack: <memptr> - appendCalldataUnpacker(FunctionType(_constructor).parameterTypes(), true); + CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true); } _constructor.accept(*this); } @@ -367,7 +370,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac { // Parameter for calldataUnpacker m_context << CompilerUtils::dataStartOffset; - appendCalldataUnpacker(functionType->parameterTypes()); + m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB; + CompilerUtils(m_context).abiDecode(functionType->parameterTypes()); } m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); m_context << returnTag; @@ -382,105 +386,6 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac } } -void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory) -{ - // We do not check the calldata size, everything is zero-padded - - if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) - { - // Use the new JULIA-based decoding function - auto stackHeightBefore = m_context.stackHeight(); - CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory); - solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, ""); - return; - } - - //@todo this does not yet support nested dynamic arrays - - // Retain the offset pointer as base_offset, the point from which the data offsets are computed. - m_context << Instruction::DUP1; - for (TypePointer const& parameterType: _typeParameters) - { - // stack: v1 v2 ... v(k-1) base_offset current_offset - TypePointer type = parameterType->decodingType(); - solUnimplementedAssert(type, "No decoding type found."); - if (type->category() == Type::Category::Array) - { - auto const& arrayType = dynamic_cast<ArrayType const&>(*type); - solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); - if (_fromMemory) - { - solUnimplementedAssert( - arrayType.baseType()->isValueType(), - "Nested memory arrays not yet implemented here." - ); - // @todo If base type is an array or struct, it is still calldata-style encoded, so - // we would have to convert it like below. - solAssert(arrayType.location() == DataLocation::Memory, ""); - if (arrayType.isDynamicallySized()) - { - // compute data pointer - m_context << Instruction::DUP1 << Instruction::MLOAD; - m_context << Instruction::DUP3 << Instruction::ADD; - m_context << Instruction::SWAP2 << Instruction::SWAP1; - m_context << u256(0x20) << Instruction::ADD; - } - else - { - m_context << Instruction::SWAP1 << Instruction::DUP2; - m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD; - } - } - else - { - // first load from calldata and potentially convert to memory if arrayType is memory - TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); - if (calldataType->isDynamicallySized()) - { - // put on stack: data_pointer length - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); - // stack: base_offset data_offset next_pointer - m_context << Instruction::SWAP1 << Instruction::DUP3 << Instruction::ADD; - // stack: base_offset next_pointer data_pointer - // retrieve length - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); - // stack: base_offset next_pointer length data_pointer - m_context << Instruction::SWAP2; - // stack: base_offset data_pointer length next_pointer - } - else - { - // leave the pointer on the stack - m_context << Instruction::DUP1; - m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD; - } - if (arrayType.location() == DataLocation::Memory) - { - // stack: base_offset calldata_ref [length] next_calldata - // copy to memory - // move calldata type up again - CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack()); - CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true); - // fetch next pointer again - CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack()); - } - // move base_offset up - CompilerUtils(m_context).moveToStackTop(1 + arrayType.sizeOnStack()); - m_context << Instruction::SWAP1; - } - } - else - { - solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); - CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true); - CompilerUtils(m_context).moveToStackTop(1 + type->sizeOnStack()); - m_context << Instruction::SWAP1; - } - // stack: v1 v2 ... v(k-1) v(k) base_offset mem_offset - } - m_context << Instruction::POP << Instruction::POP; -} - void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary) { CompilerUtils utils(m_context); @@ -1002,7 +907,10 @@ void ContractCompiler::appendModifierOrFunctionCode() appendModifierOrFunctionCode(); else { - ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name()); + ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>( + *modifierInvocation->name()->annotation().referencedDeclaration + ); + ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier); CompilerContext::LocationSetter locationSetter(m_context, modifier); solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), ""); for (unsigned i = 0; i < modifier.parameters().size(); ++i) diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 8559ea58..e04a56fb 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -90,10 +90,6 @@ private: 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. - /// From memory if @a _fromMemory is true, otherwise from call data. - /// Expects source offset on the stack, which is removed. - void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false); void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); void registerStateVariables(ContractDefinition const& _contract); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 0ce70f38..76aa6843 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -139,8 +139,8 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& utils().popStackSlots(paramTypes.size() - 1); } unsigned retSizeOnStack = 0; - solAssert(accessorType.returnParameterTypes().size() >= 1, ""); - auto const& returnTypes = accessorType.returnParameterTypes(); + auto returnTypes = accessorType.returnParameterTypes(); + solAssert(returnTypes.size() >= 1, ""); if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) { // remove offset @@ -518,7 +518,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments[i]->accept(*this); utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]); } - _functionCall.expression().accept(*this); + + { + bool shortcutTaken = false; + if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression())) + if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration)) + { + // Do not directly visit the identifier, because this way, we can avoid + // the runtime entry label to be created at the creation time context. + CompilerContext::LocationSetter locationSetter2(m_context, *identifier); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false); + shortcutTaken = true; + } + + if (!shortcutTaken) + _functionCall.expression().accept(*this); + } + unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes()); if (function.bound()) { @@ -834,8 +850,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::ObjectCreation: { - // Will allocate at the end of memory (MSIZE) and not write at all unless the base - // type is dynamically sized. ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type); _functionCall.expression().accept(*this); solAssert(arguments.size() == 1, ""); @@ -845,15 +859,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().convertType(*arguments[0]->annotation().type, IntegerType(256)); // Stack: requested_length - // Allocate at max(MSIZE, freeMemoryPointer) utils().fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << Instruction::MSIZE; - m_context << Instruction::LT; - auto initialise = m_context.appendConditionalJump(); - // Free memory pointer does not point to empty memory, use MSIZE. - m_context << Instruction::POP; - m_context << Instruction::MSIZE; - m_context << initialise; // Stack: requested_length memptr m_context << Instruction::SWAP1; @@ -878,13 +884,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Check if length is zero m_context << Instruction::DUP1 << Instruction::ISZERO; auto skipInit = m_context.appendConditionalJump(); - - // We only have to initialise if the base type is a not a value type. - if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get())) - { - m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; - utils().zeroInitialiseMemoryArray(arrayType); - } + // Always initialize because the free memory pointer might point at + // a dirty memory area. + m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; + utils().zeroInitialiseMemoryArray(arrayType); m_context << skipInit; m_context << Instruction::POP; break; @@ -924,8 +927,6 @@ bool ExpressionCompiler::visit(NewExpression const&) bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { - bool const v050 = m_context.experimentalFeatureActive(ExperimentalFeature::V050); - CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); // Check whether the member is a bound function. ASTString const& member = _memberAccess.memberName(); @@ -1141,10 +1142,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "origin") m_context << Instruction::ORIGIN; else if (member == "gas") - { - solAssert(!v050, ""); m_context << Instruction::GAS; - } else if (member == "gasprice") m_context << Instruction::GASPRICE; else if (member == "data") @@ -1152,6 +1150,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "sig") m_context << u256(0) << Instruction::CALLDATALOAD << (u256(0xffffffff) << (256 - 32)) << Instruction::AND; + else if (member == "blockhash") + { + } else solAssert(false, "Unknown magic member."); break; @@ -1361,6 +1362,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) + // If the identifier is called right away, this code is executed in visit(FunctionCall...), because + // we want to avoid having a reference to the runtime function entry point in the + // constructor context, since this would force the compiler to include unreferenced + // internal functions in the runtime contex. utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) appendVariable(*variable, static_cast<Expression const&>(_identifier)); @@ -1615,16 +1620,32 @@ void ExpressionCompiler::appendExternalFunctionCall( bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; + bool useStaticCall = + _functionType.stateMutability() <= StateMutability::View && + m_context.experimentalFeatureActive(ExperimentalFeature::V050) && + 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 - for (auto const& retType: _functionType.returnParameterTypes()) + returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes(); + + bool dynamicReturnSize = false; + for (auto const& retType: returnTypes) + if (retType->isDynamicallyEncoded()) { - solAssert(!retType->isDynamicallySized(), "Unable to return dynamic type from external call."); - retSize += retType->calldataEncodedSize(); + solAssert(haveReturndatacopy, ""); + dynamicReturnSize = true; + retSize = 0; + break; } + else + retSize += retType->calldataEncodedSize(); // Evaluate arguments. TypePointers argumentTypes; @@ -1743,6 +1764,8 @@ void ExpressionCompiler::appendExternalFunctionCall( // [value,] addr, gas (stack top) if (isDelegateCall) solAssert(!_functionType.valueSet(), "Value set for delegatecall"); + else if (useStaticCall) + solAssert(!_functionType.valueSet(), "Value set for staticcall"); else if (_functionType.valueSet()) m_context << dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); else @@ -1774,10 +1797,13 @@ void ExpressionCompiler::appendExternalFunctionCall( gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } + // Order is important here, STATICCALL might overlap with DELEGATECALL. if (isDelegateCall) m_context << Instruction::DELEGATECALL; else if (isCallCode) m_context << Instruction::CALLCODE; + else if (useStaticCall) + m_context << Instruction::STATICCALL; else m_context << Instruction::CALL; @@ -1817,20 +1843,42 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().fetchFreeMemoryPointer(); m_context << Instruction::SUB << Instruction::MLOAD; } - else if (!_functionType.returnParameterTypes().empty()) + else if (!returnTypes.empty()) { utils().fetchFreeMemoryPointer(); - bool memoryNeeded = false; - for (auto const& retType: _functionType.returnParameterTypes()) + // Stack: return_data_start + + // The old decoder did not allocate any memory (i.e. did not touch the free + // memory pointer), but kept references to the return data for + // (statically-sized) arrays + bool needToUpdateFreeMemoryPtr = false; + if (dynamicReturnSize || m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + needToUpdateFreeMemoryPtr = true; + else + for (auto const& retType: returnTypes) + if (dynamic_cast<ReferenceType const*>(retType.get())) + needToUpdateFreeMemoryPtr = true; + + // Stack: return_data_start + if (dynamicReturnSize) { - utils().loadFromMemoryDynamic(*retType, false, true, true); - if (dynamic_cast<ReferenceType const*>(retType.get())) - memoryNeeded = true; + solAssert(haveReturndatacopy, ""); + m_context.appendInlineAssembly("{ returndatacopy(return_data_start, 0, returndatasize()) }", {"return_data_start"}); } - if (memoryNeeded) - utils().storeFreeMemoryPointer(); else - m_context << Instruction::POP; + solAssert(retSize > 0, ""); + // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. + // This ensures it can catch badly formatted input from external calls. + m_context << (haveReturndatacopy ? eth::AssemblyItem(Instruction::RETURNDATASIZE) : u256(retSize)); + // Stack: return_data_start return_data_size + if (needToUpdateFreeMemoryPtr) + m_context.appendInlineAssembly(R"({ + // round size to the next multiple of 32 + let newMem := add(start, and(add(size, 0x1f), not(0x1f))) + mstore(0x40, newMem) + })", {"start", "size"}); + + utils().abiDecode(returnTypes, true, true); } } diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 1da5b291..8f4abdc2 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -205,7 +205,7 @@ void SMTChecker::endVisit(Assignment const& _assignment) _assignment.location(), "Assertion checker does not yet implement compound assignment." ); - else if (_assignment.annotation().type->category() != Type::Category::Integer) + else if (!SSAVariable::isSupportedType(_assignment.annotation().type->category())) m_errorReporter.warning( _assignment.location(), "Assertion checker does not yet implement type " + _assignment.annotation().type->toString() @@ -266,14 +266,15 @@ void SMTChecker::endVisit(UnaryOperation const& _op) { case Token::Not: // ! { - solAssert(_op.annotation().type->category() == Type::Category::Bool, ""); + solAssert(SSAVariable::isBool(_op.annotation().type->category()), ""); 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(SSAVariable::isInteger(_op.annotation().type->category()), ""); solAssert(_op.subExpression().annotation().lValueRequested, ""); if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression())) { @@ -370,7 +371,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) { // Will be translated as part of the node that requested the lvalue. } - else if (SSAVariable::supportedType(_identifier.annotation().type.get())) + else if (SSAVariable::isSupportedType(_identifier.annotation().type->category())) defineExpr(_identifier, currentValue(*decl)); else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) { @@ -444,21 +445,37 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) void SMTChecker::compareOperation(BinaryOperation const& _op) { solAssert(_op.annotation().commonType, ""); - if (_op.annotation().commonType->category() == Type::Category::Integer) + if (SSAVariable::isSupportedType(_op.annotation().commonType->category())) { smt::Expression left(expr(_op.leftExpression())); smt::Expression right(expr(_op.rightExpression())); Token::Value op = _op.getOperator(); - smt::Expression value = ( - op == Token::Equal ? (left == right) : - op == Token::NotEqual ? (left != right) : - op == Token::LessThan ? (left < right) : - op == Token::LessThanOrEqual ? (left <= right) : - op == Token::GreaterThan ? (left > right) : - /*op == Token::GreaterThanOrEqual*/ (left >= right) - ); + shared_ptr<smt::Expression> value; + if (SSAVariable::isInteger(_op.annotation().commonType->category())) + { + value = make_shared<smt::Expression>( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (left < right) : + op == Token::LessThanOrEqual ? (left <= right) : + op == Token::GreaterThan ? (left > right) : + /*op == Token::GreaterThanOrEqual*/ (left >= right) + ); + } + else // Bool + { + solAssert(SSAVariable::isBool(_op.annotation().commonType->category()), ""); + value = make_shared<smt::Expression>( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (!left && right) : + op == Token::LessThanOrEqual ? (!left || right) : + op == Token::GreaterThan ? (left && !right) : + /*op == Token::GreaterThanOrEqual*/ (left || !right) + ); + } // TODO: check that other values for op are not possible. - defineExpr(_op, value); + defineExpr(_op, *value); } else m_errorReporter.warning( @@ -728,10 +745,10 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) { - if (SSAVariable::supportedType(_varDecl.type().get())) + if (SSAVariable::isSupportedType(_varDecl.type()->category())) { solAssert(m_variables.count(&_varDecl) == 0, ""); - m_variables.emplace(&_varDecl, SSAVariable(&_varDecl, *m_interface)); + m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface)); return true; } else diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp index 4e6bcbcb..f3213e03 100644 --- a/libsolidity/formal/SSAVariable.cpp +++ b/libsolidity/formal/SSAVariable.cpp @@ -17,6 +17,7 @@ #include <libsolidity/formal/SSAVariable.h> +#include <libsolidity/formal/SymbolicBoolVariable.h> #include <libsolidity/formal/SymbolicIntVariable.h> #include <libsolidity/ast/AST.h> @@ -26,23 +27,35 @@ using namespace dev; using namespace dev::solidity; SSAVariable::SSAVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ) { resetIndex(); - if (dynamic_cast<IntegerType const*>(_decl->type().get())) + if (isInteger(_decl.type()->category())) m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface); + else if (isBool(_decl.type()->category())) + m_symbolicVar = make_shared<SymbolicBoolVariable>(_decl, _interface); else { solAssert(false, ""); } } -bool SSAVariable::supportedType(Type const* _decl) +bool SSAVariable::isSupportedType(Type::Category _category) { - return dynamic_cast<IntegerType const*>(_decl); + return isInteger(_category) || isBool(_category); +} + +bool SSAVariable::isInteger(Type::Category _category) +{ + return _category == Type::Category::Integer; +} + +bool SSAVariable::isBool(Type::Category _category) +{ + return _category == Type::Category::Bool; } void SSAVariable::resetIndex() diff --git a/libsolidity/formal/SSAVariable.h b/libsolidity/formal/SSAVariable.h index 275e8590..bf5dae3b 100644 --- a/libsolidity/formal/SSAVariable.h +++ b/libsolidity/formal/SSAVariable.h @@ -37,7 +37,7 @@ public: /// @param _decl Used to determine the type and forwarded to the symbolic var. /// @param _interface Forwarded to the symbolic var such that it can give constraints to the solver. SSAVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ); @@ -68,8 +68,10 @@ public: void setZeroValue(); void setUnknownValue(); - /// So far Int is supported. - static bool supportedType(Type const* _decl); + /// So far Int and Bool are supported. + static bool isSupportedType(Type::Category _category); + static bool isInteger(Type::Category _category); + static bool isBool(Type::Category _category); private: smt::Expression valueAtSequence(int _seq) const diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 88487310..0bdebb6c 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -46,7 +46,8 @@ enum class Sort { Int, Bool, - IntIntFun // Function of one Int returning a single Int + IntIntFun, // Function of one Int returning a single Int + IntBoolFun // Function of one Int returning a single Bool }; /// C++ representation of an SMTLIB2 expression. @@ -132,10 +133,22 @@ public: Expression operator()(Expression _a) const { solAssert( - sort == Sort::IntIntFun && arguments.empty(), + arguments.empty(), "Attempted function application to non-function." ); - return Expression(name, _a, Sort::Int); + switch (sort) + { + case Sort::IntIntFun: + return Expression(name, _a, Sort::Int); + case Sort::IntBoolFun: + return Expression(name, _a, Sort::Bool); + default: + solAssert( + false, + "Attempted function application to invalid type." + ); + break; + } } std::string const name; @@ -167,9 +180,18 @@ public: virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain) { - solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported."); + solAssert(_domain == Sort::Int, "Function sort not supported."); // Subclasses should do something here - return Expression(std::move(_name), {}, Sort::IntIntFun); + switch (_codomain) + { + case Sort::Int: + return Expression(std::move(_name), {}, Sort::IntIntFun); + case Sort::Bool: + return Expression(std::move(_name), {}, Sort::IntBoolFun); + default: + solAssert(false, "Function sort not supported."); + break; + } } virtual Expression newInteger(std::string _name) { diff --git a/libsolidity/formal/SymbolicBoolVariable.cpp b/libsolidity/formal/SymbolicBoolVariable.cpp new file mode 100644 index 00000000..e5c56e46 --- /dev/null +++ b/libsolidity/formal/SymbolicBoolVariable.cpp @@ -0,0 +1,43 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/formal/SymbolicBoolVariable.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SymbolicBoolVariable::SymbolicBoolVariable( + Declaration const& _decl, + smt::SolverInterface&_interface +): + SymbolicVariable(_decl, _interface) +{ + solAssert(m_declaration.type()->category() == Type::Category::Bool, ""); + m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Bool)); +} + +void SymbolicBoolVariable::setZeroValue(int _seq) +{ + m_interface.addAssertion(valueAtSequence(_seq) == smt::Expression(false)); +} + +void SymbolicBoolVariable::setUnknownValue(int) +{ +} diff --git a/libsolidity/formal/SymbolicBoolVariable.h b/libsolidity/formal/SymbolicBoolVariable.h new file mode 100644 index 00000000..3510b770 --- /dev/null +++ b/libsolidity/formal/SymbolicBoolVariable.h @@ -0,0 +1,47 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SymbolicVariable.h> + +#include <libsolidity/ast/Types.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Specialization of SymbolicVariable for Bool + */ +class SymbolicBoolVariable: public SymbolicVariable +{ +public: + SymbolicBoolVariable( + Declaration const& _decl, + smt::SolverInterface& _interface + ); + + /// Sets the var to false. + void setZeroValue(int _seq); + /// Does nothing since the SMT solver already knows the valid values. + void setUnknownValue(int _seq); +}; + +} +} diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp index d08dc155..eb7b1c17 100644 --- a/libsolidity/formal/SymbolicIntVariable.cpp +++ b/libsolidity/formal/SymbolicIntVariable.cpp @@ -24,12 +24,12 @@ using namespace dev; using namespace dev::solidity; SymbolicIntVariable::SymbolicIntVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ): SymbolicVariable(_decl, _interface) { - solAssert(m_declaration->type()->category() == Type::Category::Integer, ""); + solAssert(m_declaration.type()->category() == Type::Category::Integer, ""); m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int)); } @@ -40,7 +40,7 @@ void SymbolicIntVariable::setZeroValue(int _seq) void SymbolicIntVariable::setUnknownValue(int _seq) { - auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration->type()); + auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type()); m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType)); m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType)); } diff --git a/libsolidity/formal/SymbolicIntVariable.h b/libsolidity/formal/SymbolicIntVariable.h index afa25f1b..eb36b899 100644 --- a/libsolidity/formal/SymbolicIntVariable.h +++ b/libsolidity/formal/SymbolicIntVariable.h @@ -33,7 +33,7 @@ class SymbolicIntVariable: public SymbolicVariable { public: SymbolicIntVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ); diff --git a/libsolidity/formal/SymbolicVariable.cpp b/libsolidity/formal/SymbolicVariable.cpp index 629049ea..d59b55b1 100644 --- a/libsolidity/formal/SymbolicVariable.cpp +++ b/libsolidity/formal/SymbolicVariable.cpp @@ -24,7 +24,7 @@ using namespace dev; using namespace dev::solidity; SymbolicVariable::SymbolicVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ): m_declaration(_decl), @@ -34,7 +34,7 @@ SymbolicVariable::SymbolicVariable( string SymbolicVariable::uniqueSymbol() const { - return m_declaration->name() + "_" + to_string(m_declaration->id()); + return m_declaration.name() + "_" + to_string(m_declaration.id()); } diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h index 93258250..75eb9fa5 100644 --- a/libsolidity/formal/SymbolicVariable.h +++ b/libsolidity/formal/SymbolicVariable.h @@ -37,7 +37,7 @@ class SymbolicVariable { public: SymbolicVariable( - Declaration const* _decl, + Declaration const& _decl, smt::SolverInterface& _interface ); @@ -60,7 +60,7 @@ protected: return (*m_expression)(_seq); } - Declaration const* m_declaration; + Declaration const& m_declaration; std::shared_ptr<smt::Expression> m_expression = nullptr; smt::SolverInterface& m_interface; }; diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index a7f764a5..abf7ddf2 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,7 +54,10 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _label.location, + "The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead." + ); m_info.stackHeightInfo[&_label] = m_stackHeight; warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); return true; @@ -62,7 +65,10 @@ bool AsmAnalyzer::operator()(Label const& _label) bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _instruction.location, + "The use of non-functional instructions is deprecated. Please use functional notation instead." + ); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -170,18 +176,31 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) { - size_t initialStackHeight = m_stackHeight; + int initialStackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, _statement.expression); - if (m_flavour != AsmFlavour::Loose) - if (!expectDeposit(0, initialStackHeight, _statement.location)) + if (m_stackHeight != initialStackHeight && (m_flavour != AsmFlavour::Loose || m_errorTypeForLoose)) + { + Error::Type errorType = m_flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; + string msg = + "Top-level expressions are not supposed to return values (this expression returns " + + boost::lexical_cast<string>(m_stackHeight - initialStackHeight) + + " value" + + (m_stackHeight - initialStackHeight == 1 ? "" : "s") + + "). Use ``pop()`` or assign them."; + m_errorReporter.error(errorType, _statement.location, msg); + if (errorType != Error::Type::Warning) success = false; + } m_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _assignment.location, + "The use of stack assignment is deprecated. Please use assignment in functional notation instead." + ); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; @@ -577,10 +596,22 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio ); if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) - m_errorReporter.warning( + { + solAssert(m_flavour == AsmFlavour::Loose, ""); + m_errorReporter.error( + m_errorTypeForLoose ? *m_errorTypeForLoose : Error::Type::Warning, _location, "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\", \"if\" or \"for\" statements instead." ); + } +} + +void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) +{ + if (m_flavour != AsmFlavour::Loose) + solAssert(false, _description); + else if (m_errorTypeForLoose) + m_errorReporter.error(*m_errorTypeForLoose, _location, _description); } diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 867711c7..8d2a71f0 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -30,6 +30,7 @@ #include <libsolidity/inlineasm/AsmDataForward.h> #include <boost/variant.hpp> +#include <boost/optional.hpp> #include <functional> #include <memory> @@ -56,9 +57,17 @@ public: AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, EVMVersion _evmVersion, + boost::optional<Error::Type> _errorTypeForLoose, AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_evmVersion(_evmVersion), m_flavour(_flavour) {} + ): + m_resolver(_resolver), + m_info(_analysisInfo), + m_errorReporter(_errorReporter), + m_evmVersion(_evmVersion), + m_flavour(_flavour), + m_errorTypeForLoose(_errorTypeForLoose) + {} bool analyze(assembly::Block const& _block); @@ -91,6 +100,11 @@ private: void expectValidType(std::string const& type, SourceLocation const& _location); void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location); + /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler + /// exception (if the flavour is not Loose), reports an error/warning + /// (if m_errorTypeForLoose is set) or does nothing. + void checkLooseFeature(SourceLocation const& _location, std::string const& _description); + int m_stackHeight = 0; julia::ExternalIdentifierAccess::Resolver m_resolver; Scope* m_currentScope = nullptr; @@ -101,6 +115,7 @@ private: ErrorReporter& m_errorReporter; EVMVersion m_evmVersion; AsmFlavour m_flavour = AsmFlavour::Loose; + boost::optional<Error::Type> m_errorTypeForLoose; }; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 7a9fffbf..7f97336b 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -91,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_evmVersion, languageToAsmFlavour(m_language)); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/EVMVersion.h b/libsolidity/interface/EVMVersion.h index 13c4ec94..b68e1f4e 100644 --- a/libsolidity/interface/EVMVersion.h +++ b/libsolidity/interface/EVMVersion.h @@ -49,7 +49,7 @@ public: static boost::optional<EVMVersion> fromString(std::string const& _version) { - for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium()}) + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople()}) if (_version == v.name()) return v; return {}; diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 0409de72..d058d556 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -119,21 +119,17 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) return _end; } auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); - if (nameEndPos == _end) - { - appendError("End of param name not found: " + string(nameStartPos, _end)); - return _end; - } auto paramName = string(nameStartPos, nameEndPos); auto descStartPos = skipWhitespace(nameEndPos, _end); - if (descStartPos == _end) + auto nlPos = find(descStartPos, _end, '\n'); + + if (descStartPos == nlPos) { appendError("No description given for param " + paramName); return _end; } - auto nlPos = find(descStartPos, _end, '\n'); auto paramDesc = string(descStartPos, nlPos); newTag("param"); m_lastTag->paramName = paramName; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 8c97f55f..3dbd4c8f 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -238,7 +238,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp Token::Value currentTokenValue = m_scanner->currentToken(); if (currentTokenValue == Token::RBrace) break; - else if (currentTokenValue == Token::Function) + else if ( + currentTokenValue == Token::Function || + (currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor") + ) // This can be a function or a state variable of function type (especially // complicated to distinguish fallback function from function type state variable) subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); @@ -329,15 +332,31 @@ StateMutability Parser::parseStateMutability(Token::Value _token) return stateMutability; } -Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader( + bool _forceEmptyName, + bool _allowModifiers, + ASTString const* _contractName +) { RecursionGuard recursionGuard(*this); FunctionHeaderParserResult result; - expectToken(Token::Function); - if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) - result.name = make_shared<ASTString>(); // anonymous function + + result.isConstructor = false; + + if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor") + result.isConstructor = true; + else if (m_scanner->currentToken() != Token::Function) + solAssert(false, "Function or constructor expected."); + m_scanner->next(); + + if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared<ASTString>(); else result.name = expectIdentifierToken(); + + if (!result.name->empty() && _contractName && *result.name == *_contractName) + result.isConstructor = true; + VarDeclParserOptions options; options.allowLocationSpecifier = true; result.parameters = parseParameterList(options); @@ -407,7 +426,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A if (m_scanner->currentCommentLiteral() != "") docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - FunctionHeaderParserResult header = parseFunctionHeader(false, true); + FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName); if ( !header.modifiers.empty() || @@ -426,12 +445,11 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A } else m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *header.name == *_contractName); return nodeFactory.createNode<FunctionDefinition>( header.name, header.visibility, header.stateMutability, - c_isConstructor, + header.isConstructor, docstring, header.parameters, header.modifiers, diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 3f780af9..eb120a61 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -56,6 +56,7 @@ private: /// This struct is shared for parsing a function header and a function type. struct FunctionHeaderParserResult { + bool isConstructor; ASTPointer<ASTString> name; ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> returnParameters; @@ -73,7 +74,11 @@ private: ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); StateMutability parseStateMutability(Token::Value _token); - FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + FunctionHeaderParserResult parseFunctionHeader( + bool _forceEmptyName, + bool _allowModifiers, + ASTString const* _contractName = nullptr + ); ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<StructDefinition> parseStructDefinition(); |