/* 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 . */ /** * Component that verifies overloads, abstract contracts, function clashes and others * checks at contract or function level. */ #include #include #include #include using namespace std; using namespace dev; using namespace langutil; using namespace dev::solidity; bool ContractLevelChecker::check(ContractDefinition const& _contract) { checkContractDuplicateFunctions(_contract); checkContractDuplicateEvents(_contract); checkContractIllegalOverrides(_contract); checkContractAbstractFunctions(_contract); checkContractBaseConstructorArguments(_contract); return Error::containsOnlyWarnings(m_errorReporter.errors()); } void ContractLevelChecker::checkContractDuplicateFunctions(ContractDefinition const& _contract) { /// Checks that two functions with the same name defined in this contract have different /// argument types and that there is at most one constructor. map> functions; FunctionDefinition const* constructor = nullptr; FunctionDefinition const* fallback = nullptr; for (FunctionDefinition const* function: _contract.definedFunctions()) if (function->isConstructor()) { if (constructor) m_errorReporter.declarationError( function->location(), SecondarySourceLocation().append("Another declaration is here:", constructor->location()), "More than one constructor defined." ); constructor = function; } else if (function->isFallback()) { if (fallback) m_errorReporter.declarationError( function->location(), SecondarySourceLocation().append("Another declaration is here:", fallback->location()), "Only one fallback function is allowed." ); fallback = function; } else { solAssert(!function->name().empty(), ""); functions[function->name()].push_back(function); } findDuplicateDefinitions(functions, "Function with same name and arguments defined twice."); } void ContractLevelChecker::checkContractDuplicateEvents(ContractDefinition const& _contract) { /// Checks that two events with the same name defined in this contract have different /// argument types map> events; for (EventDefinition const* event: _contract.events()) events[event->name()].push_back(event); findDuplicateDefinitions(events, "Event with same name and arguments defined twice."); } template void ContractLevelChecker::findDuplicateDefinitions(map> const& _definitions, string _message) { for (auto const& it: _definitions) { vector const& overloads = it.second; set reported; for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i) { SecondarySourceLocation ssl; for (size_t j = i + 1; j < overloads.size(); ++j) if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes( *FunctionType(*overloads[j]).asCallableFunction(false)) ) { ssl.append("Other declaration is here:", overloads[j]->location()); reported.insert(j); } if (ssl.infos.size() > 0) { ssl.limitSize(_message); m_errorReporter.declarationError( overloads[i]->location(), ssl, _message ); } } } } void ContractLevelChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) { // TODO unify this at a later point. for this we need to put the constness and the access specifier // into the types map> functions; map modifiers; // We search from derived to base, so the stored item causes the error. for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) { for (FunctionDefinition const* function: contract->definedFunctions()) { if (function->isConstructor()) continue; // constructors can neither be overridden nor override anything string const& name = function->name(); if (modifiers.count(name)) m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier."); for (FunctionDefinition const* overriding: functions[name]) checkFunctionOverride(*overriding, *function); functions[name].push_back(function); } for (ModifierDefinition const* modifier: contract->functionModifiers()) { string const& name = modifier->name(); ModifierDefinition const*& override = modifiers[name]; if (!override) override = modifier; else if (ModifierType(*override) != ModifierType(*modifier)) m_errorReporter.typeError(override->location(), "Override changes modifier signature."); if (!functions[name].empty()) m_errorReporter.typeError(override->location(), "Override changes modifier to function."); } } } void ContractLevelChecker::checkFunctionOverride(FunctionDefinition const& _function, FunctionDefinition const& _super) { FunctionTypePointer functionType = FunctionType(_function).asCallableFunction(false); FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false); if (!functionType->hasEqualParameterTypes(*superType)) return; if (!functionType->hasEqualReturnTypes(*superType)) overrideError(_function, _super, "Overriding function return types differ."); if (!_function.annotation().superFunction) _function.annotation().superFunction = &_super; if (_function.visibility() != _super.visibility()) { // Visibility change from external to public is fine. // Any other change is disallowed. if (!( _super.visibility() == FunctionDefinition::Visibility::External && _function.visibility() == FunctionDefinition::Visibility::Public )) overrideError(_function, _super, "Overriding function visibility differs."); } if (_function.stateMutability() != _super.stateMutability()) overrideError( _function, _super, "Overriding function changes state mutability from \"" + stateMutabilityToString(_super.stateMutability()) + "\" to \"" + stateMutabilityToString(_function.stateMutability()) + "\"." ); } void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) { m_errorReporter.typeError( function.location(), SecondarySourceLocation().append("Overridden function is here:", super.location()), message ); } void ContractLevelChecker::checkContractAbstractFunctions(ContractDefinition const& _contract) { // Mapping from name to function definition (exactly one per argument type equality class) and // flag to indicate whether it is fully implemented. using FunTypeAndFlag = std::pair; map> functions; // Search from base to derived for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (FunctionDefinition const* function: contract->definedFunctions()) { // Take constructors out of overload hierarchy if (function->isConstructor()) continue; auto& overloads = functions[function->name()]; FunctionTypePointer funType = make_shared(*function)->asCallableFunction(false); auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) { return funType->hasEqualParameterTypes(*_funAndFlag.first); }); if (it == overloads.end()) overloads.push_back(make_pair(funType, function->isImplemented())); else if (it->second) { if (!function->isImplemented()) m_errorReporter.typeError(function->location(), "Redeclaring an already implemented function as abstract"); } else if (function->isImplemented()) it->second = true; } // Set to not fully implemented if at least one flag is false. for (auto const& it: functions) for (auto const& funAndFlag: it.second) if (!funAndFlag.second) { FunctionDefinition const* function = dynamic_cast(&funAndFlag.first->declaration()); solAssert(function, ""); _contract.annotation().unimplementedFunctions.push_back(function); break; } } void ContractLevelChecker::checkContractBaseConstructorArguments(ContractDefinition const& _contract) { vector const& bases = _contract.annotation().linearizedBaseContracts; // Determine the arguments that are used for the base constructors. for (ContractDefinition const* contract: bases) { if (FunctionDefinition const* constructor = contract->constructor()) for (auto const& modifier: constructor->modifiers()) if (auto baseContract = dynamic_cast( modifier->name()->annotation().referencedDeclaration )) { if (modifier->arguments()) { if (baseContract->constructor()) annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get()); } else m_errorReporter.declarationError( modifier->location(), "Modifier-style base constructor call without arguments." ); } for (ASTPointer const& base: contract->baseContracts()) { ContractDefinition const* baseContract = dynamic_cast( base->name().annotation().referencedDeclaration ); solAssert(baseContract, ""); if (baseContract->constructor() && base->arguments() && !base->arguments()->empty()) annotateBaseConstructorArguments(_contract, baseContract->constructor(), base.get()); } } // check that we get arguments for all base constructors that need it. // If not mark the contract as abstract (not fully implemented) for (ContractDefinition const* contract: bases) if (FunctionDefinition const* constructor = contract->constructor()) if (contract != &_contract && !constructor->parameters().empty()) if (!_contract.annotation().baseConstructorArguments.count(constructor)) _contract.annotation().unimplementedFunctions.push_back(constructor); } void ContractLevelChecker::annotateBaseConstructorArguments( ContractDefinition const& _currentContract, FunctionDefinition const* _baseConstructor, ASTNode const* _argumentNode ) { solAssert(_baseConstructor, ""); solAssert(_argumentNode, ""); auto insertionResult = _currentContract.annotation().baseConstructorArguments.insert( std::make_pair(_baseConstructor, _argumentNode) ); if (!insertionResult.second) { ASTNode const* previousNode = insertionResult.first->second; SourceLocation const* mainLocation = nullptr; SecondarySourceLocation ssl; if ( _currentContract.location().contains(previousNode->location()) || _currentContract.location().contains(_argumentNode->location()) ) { mainLocation = &previousNode->location(); ssl.append("Second constructor call is here:", _argumentNode->location()); } else { mainLocation = &_currentContract.location(); ssl.append("First constructor call is here: ", _argumentNode->location()); ssl.append("Second constructor call is here: ", previousNode->location()); } m_errorReporter.declarationError( *mainLocation, ssl, "Base constructor arguments given twice." ); } }