diff --git a/Changelog.md b/Changelog.md index 1c3fcde46..630801e48 100644 --- a/Changelog.md +++ b/Changelog.md @@ -24,6 +24,8 @@ Bugfixes: * Type Checker: Disallow struct return types for getters of public state variables unless the new ABI encoder is active. * Type Checker: Fix internal compiler error when a field of a struct used as a parameter in a function type has a non-existent type. * Type Checker: Disallow functions ``sha3`` and ``suicide`` also without a function call. + * Type Checker: Fixed internal error when trying to create abstract contract in some cases. + * Type Checker: Fixed internal error related to double declaration of events. * Type Checker: Disallow inline arrays of mapping type. Build System: diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index d2e0c8549..dc4c6d156 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -1,6 +1,7 @@ # Until we have a clear separation, libyul has to be included here set(sources analysis/ConstantEvaluator.cpp + analysis/ContractLevelChecker.cpp analysis/ControlFlowAnalyzer.cpp analysis/ControlFlowBuilder.cpp analysis/ControlFlowGraph.cpp diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp new file mode 100644 index 000000000..58dcfe4d6 --- /dev/null +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -0,0 +1,452 @@ +/* + 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) +{ + checkDuplicateFunctions(_contract); + checkDuplicateEvents(_contract); + checkIllegalOverrides(_contract); + checkAbstractFunctions(_contract); + checkBaseConstructorArguments(_contract); + checkConstructor(_contract); + checkFallbackFunction(_contract); + checkExternalTypeClashes(_contract); + checkHashCollisions(_contract); + checkLibraryRequirements(_contract); + + return Error::containsOnlyWarnings(m_errorReporter.errors()); +} + +void ContractLevelChecker::checkDuplicateFunctions(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::checkDuplicateEvents(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::checkIllegalOverrides(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::checkAbstractFunctions(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::checkBaseConstructorArguments(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." + ); + } + +} + +void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract) +{ + FunctionDefinition const* constructor = _contract.constructor(); + if (!constructor) + return; + + if (!constructor->returnParameters().empty()) + m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); + if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + constructor->location(), + "Constructor must be payable or non-payable, but is \"" + + stateMutabilityToString(constructor->stateMutability()) + + "\"." + ); + if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal) + m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal."); +} + +void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract) +{ + FunctionDefinition const* fallback = _contract.fallbackFunction(); + if (!fallback) + return; + + if (_contract.isLibrary()) + m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions."); + if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + fallback->location(), + "Fallback function must be payable or non-payable, but is \"" + + stateMutabilityToString(fallback->stateMutability()) + + "\"." + ); + if (!fallback->parameters().empty()) + m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters."); + if (!fallback->returnParameters().empty()) + m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values."); + if (fallback->visibility() != FunctionDefinition::Visibility::External) + m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\"."); +} + +void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract) +{ + map>> externalDeclarations; + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + { + for (FunctionDefinition const* f: contract->definedFunctions()) + if (f->isPartOfExternalInterface()) + { + auto functionType = make_shared(*f); + // under non error circumstances this should be true + if (functionType->interfaceFunctionType()) + externalDeclarations[functionType->externalSignature()].push_back( + make_pair(f, functionType->asCallableFunction(false)) + ); + } + for (VariableDeclaration const* v: contract->stateVariables()) + if (v->isPartOfExternalInterface()) + { + auto functionType = make_shared(*v); + // under non error circumstances this should be true + if (functionType->interfaceFunctionType()) + externalDeclarations[functionType->externalSignature()].push_back( + make_pair(v, functionType->asCallableFunction(false)) + ); + } + } + for (auto const& it: externalDeclarations) + for (size_t i = 0; i < it.second.size(); ++i) + for (size_t j = i + 1; j < it.second.size(); ++j) + if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second)) + m_errorReporter.typeError( + it.second[j].first->location(), + "Function overload clash during conversion to external types for arguments." + ); +} + +void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contract) +{ + set> hashes; + for (auto const& it: _contract.interfaceFunctionList()) + { + FixedHash<4> const& hash = it.first; + if (hashes.count(hash)) + m_errorReporter.typeError( + _contract.location(), + string("Function signature hash collision for ") + it.second->externalSignature() + ); + hashes.insert(hash); + } +} + +void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract) +{ + if (!_contract.isLibrary()) + return; + + if (!_contract.baseContracts().empty()) + m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit."); + + for (auto const& var: _contract.stateVariables()) + if (!var->isConstant()) + m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); +} diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h new file mode 100644 index 000000000..15cbf45dc --- /dev/null +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -0,0 +1,87 @@ +/* + 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. + */ + +#pragma once + +#include + +#include + +namespace langutil +{ +class ErrorReporter; +} + +namespace dev +{ +namespace solidity +{ + +/** + * Component that verifies overloads, abstract contracts, function clashes and others + * checks at contract or function level. + */ +class ContractLevelChecker +{ +public: + /// @param _errorReporter provides the error logging functionality. + explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) + {} + + /// Performs checks on the given contract. + /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings + bool check(ContractDefinition const& _contract); + +private: + /// Checks that two functions defined in this contract with the same name have different + /// arguments and that there is at most one constructor. + void checkDuplicateFunctions(ContractDefinition const& _contract); + void checkDuplicateEvents(ContractDefinition const& _contract); + template + void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); + void checkIllegalOverrides(ContractDefinition const& _contract); + /// Reports a type error with an appropriate message if overridden function signature differs. + /// Also stores the direct super function in the AST annotations. + void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); + void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); + void checkAbstractFunctions(ContractDefinition const& _contract); + void checkBaseConstructorArguments(ContractDefinition const& _contract); + void annotateBaseConstructorArguments( + ContractDefinition const& _currentContract, + FunctionDefinition const* _baseConstructor, + ASTNode const* _argumentNode + ); + void checkConstructor(ContractDefinition const& _contract); + void checkFallbackFunction(ContractDefinition const& _contract); + /// Checks that different functions with external visibility end up having different + /// external argument types (i.e. different signature). + void checkExternalTypeClashes(ContractDefinition const& _contract); + /// Checks for hash collisions in external function signatures. + void checkHashCollisions(ContractDefinition const& _contract); + /// Checks that all requirements for a library are fulfilled if this is a library. + void checkLibraryRequirements(ContractDefinition const& _contract); + + langutil::ErrorReporter& m_errorReporter; +}; + +} +} diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index a80ca7d60..9350df059 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include @@ -89,417 +88,14 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { m_scope = &_contract; - // We force our own visiting order here. The structs have to be excluded below. - set visited; - for (auto const& s: _contract.definedStructs()) - visited.insert(s); - ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.baseContracts(), *this); - checkContractDuplicateFunctions(_contract); - checkContractDuplicateEvents(_contract); - checkContractIllegalOverrides(_contract); - checkContractAbstractFunctions(_contract); - checkContractBaseConstructorArguments(_contract); - - FunctionDefinition const* function = _contract.constructor(); - if (function) - { - if (!function->returnParameters().empty()) - m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); - if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - function->location(), - "Constructor must be payable or non-payable, but is \"" + - stateMutabilityToString(function->stateMutability()) + - "\"." - ); - if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal) - m_errorReporter.typeError(function->location(), "Constructor must be public or internal."); - } - - for (FunctionDefinition const* function: _contract.definedFunctions()) - if (function->isFallback()) - { - if (_contract.isLibrary()) - m_errorReporter.typeError(function->location(), "Libraries cannot have fallback functions."); - if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - function->location(), - "Fallback function must be payable or non-payable, but is \"" + - stateMutabilityToString(function->stateMutability()) + - "\"." - ); - if (!function->parameters().empty()) - m_errorReporter.typeError(function->parameterList().location(), "Fallback function cannot take parameters."); - if (!function->returnParameters().empty()) - m_errorReporter.typeError(function->returnParameterList()->location(), "Fallback function cannot return values."); - if (function->visibility() != FunctionDefinition::Visibility::External) - m_errorReporter.typeError(function->location(), "Fallback function must be defined as \"external\"."); - } - for (auto const& n: _contract.subNodes()) - if (!visited.count(n.get())) - n->accept(*this); - - checkContractExternalTypeClashes(_contract); - // check for hash collisions in function signatures - set> hashes; - for (auto const& it: _contract.interfaceFunctionList()) - { - FixedHash<4> const& hash = it.first; - if (hashes.count(hash)) - m_errorReporter.typeError( - _contract.location(), - string("Function signature hash collision for ") + it.second->externalSignature() - ); - hashes.insert(hash); - } - - if (_contract.isLibrary()) - checkLibraryRequirements(_contract); + n->accept(*this); return false; } -void TypeChecker::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 TypeChecker::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 TypeChecker::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 TypeChecker::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 TypeChecker::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(&dereference(*modifier->name()))) - { - 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()) - { - auto baseContract = dynamic_cast(&dereference(base->name())); - 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 TypeChecker::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." - ); - } - -} - -void TypeChecker::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 TypeChecker::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 TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) -{ - m_errorReporter.typeError( - function.location(), - SecondarySourceLocation().append("Overridden function is here:", super.location()), - message - ); -} - -void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract) -{ - map>> externalDeclarations; - for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) - { - for (FunctionDefinition const* f: contract->definedFunctions()) - if (f->isPartOfExternalInterface()) - { - auto functionType = make_shared(*f); - // under non error circumstances this should be true - if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(f, functionType->asCallableFunction(false)) - ); - } - for (VariableDeclaration const* v: contract->stateVariables()) - if (v->isPartOfExternalInterface()) - { - auto functionType = make_shared(*v); - // under non error circumstances this should be true - if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(v, functionType->asCallableFunction(false)) - ); - } - } - for (auto const& it: externalDeclarations) - for (size_t i = 0; i < it.second.size(); ++i) - for (size_t j = i + 1; j < it.second.size(); ++j) - if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second)) - m_errorReporter.typeError( - it.second[j].first->location(), - "Function overload clash during conversion to external types for arguments." - ); -} - -void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) -{ - solAssert(_contract.isLibrary(), ""); - if (!_contract.baseContracts().empty()) - m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit."); - - for (auto const& var: _contract.stateVariables()) - if (!var->isConstant()) - m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); -} - void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) { TupleType const& lhs = dynamic_cast(*type(_assignment.leftHandSide())); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index c98a4c7f2..ebfcdadce 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -66,27 +66,6 @@ public: private: bool visit(ContractDefinition const& _contract) override; - /// Checks that two functions defined in this contract with the same name have different - /// arguments and that there is at most one constructor. - void checkContractDuplicateFunctions(ContractDefinition const& _contract); - void checkContractDuplicateEvents(ContractDefinition const& _contract); - void checkContractIllegalOverrides(ContractDefinition const& _contract); - /// Reports a type error with an appropriate message if overridden function signature differs. - /// Also stores the direct super function in the AST annotations. - void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); - void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); - void checkContractAbstractFunctions(ContractDefinition const& _contract); - void checkContractBaseConstructorArguments(ContractDefinition const& _contract); - void annotateBaseConstructorArguments( - ContractDefinition const& _currentContract, - FunctionDefinition const* _baseConstructor, - ASTNode const* _argumentNode - ); - /// Checks that different functions with external visibility end up having different - /// external argument types (i.e. different signature). - void checkContractExternalTypeClashes(ContractDefinition const& _contract); - /// Checks that all requirements for a library are fulfilled if this is a library. - void checkLibraryRequirements(ContractDefinition const& _contract); /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage. /// Should only be called if the left hand side is tuple-typed. void checkDoubleStorageAssignment(Assignment const& _assignment); @@ -157,9 +136,6 @@ private: void endVisit(ElementaryTypeNameExpression const& _expr) override; void endVisit(Literal const& _literal) override; - template - void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); - bool contractDependenciesAreCyclic( ContractDefinition const& _contract, std::set const& _seenContracts = std::set() diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index de4a7ec20..623ccca82 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -225,8 +226,21 @@ bool CompilerStack::analyze() m_contracts[contract->fullyQualifiedName()].contract = contract; } - // This cannot be done in the above loop, because cross-contract types couldn't be resolved. - // A good example is `LibraryName.TypeName x;`. + // Next, we check inheritance, overrides, function collisions and other things at + // contract or function level. + // This also calculates whether a contract is abstract, which is needed by the + // type checker. + ContractLevelChecker contractLevelChecker(m_errorReporter); + for (Source const* source: m_sourceOrder) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + if (!contractLevelChecker.check(*contract)) + noErrors = false; + + // New we run full type checks that go down to the expression level. This + // cannot be done earlier, because we need cross-contract types and information + // about whether a contract is abstract for the `new` expression. + // This populates the `type` annotation for all expressions. // // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed, // which is only done one step later. diff --git a/test/libsolidity/syntaxTests/constructor/abstract_creation_forward_reference.sol b/test/libsolidity/syntaxTests/constructor/abstract_creation_forward_reference.sol new file mode 100644 index 000000000..2e6aeaa5e --- /dev/null +++ b/test/libsolidity/syntaxTests/constructor/abstract_creation_forward_reference.sol @@ -0,0 +1,15 @@ +// This used to cause an internal error because of the visitation order. +contract Test { + function createChild() public { + Child asset = new Child(); + } +} + +contract Parent { + constructor(address _address) public {} +} + +contract Child is Parent { +} +// ---- +// TypeError: (146-155): Trying to create an instance of an abstract contract. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol new file mode 100644 index 000000000..d2a411ec8 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(Data.S memory a) public {} +} +contract Data { + struct S { S x; } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (63-78): Internal or recursive type is not allowed for public or external functions.