solidity/libsolidity/analysis/ContractLevelChecker.cpp

461 lines
15 KiB
C++
Raw Normal View History

/*
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/>.
*/
/**
* Component that verifies overloads, abstract contracts, function clashes and others
* checks at contract or function level.
*/
#include <libsolidity/analysis/ContractLevelChecker.h>
2018-12-17 11:30:08 +00:00
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h>
2018-11-29 16:51:57 +00:00
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
2019-12-11 16:31:36 +00:00
using namespace solidity;
using namespace solidity::langutil;
using namespace solidity::frontend;
2019-09-16 12:33:43 +00:00
namespace
{
template <class T, class B>
bool hasEqualNameAndParameters(T const& _a, B const& _b)
{
2019-09-16 12:33:43 +00:00
return
_a.name() == _b.name() &&
2020-05-26 08:48:17 +00:00
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
}
}
2018-11-29 16:39:57 +00:00
bool ContractLevelChecker::check(ContractDefinition const& _contract)
{
2018-11-29 17:23:58 +00:00
checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract);
m_overrideChecker.check(_contract);
2018-11-29 17:23:58 +00:00
checkBaseConstructorArguments(_contract);
checkAbstractDefinitions(_contract);
2018-11-29 17:32:06 +00:00
checkExternalTypeClashes(_contract);
2018-11-29 17:33:54 +00:00
checkHashCollisions(_contract);
2018-11-29 17:36:26 +00:00
checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
2018-11-29 16:39:57 +00:00
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
2018-11-29 16:39:57 +00:00
2018-11-29 17:23:58 +00:00
void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _contract)
2018-11-29 16:39:57 +00:00
{
/// 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<string, vector<FunctionDefinition const*>> functions;
FunctionDefinition const* constructor = nullptr;
FunctionDefinition const* fallback = nullptr;
FunctionDefinition const* receive = nullptr;
2018-11-29 16:39:57 +00:00
for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->isConstructor())
{
if (constructor)
m_errorReporter.declarationError(
7997_error,
2018-11-29 16:39:57 +00:00
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(
7301_error,
2018-11-29 16:39:57 +00:00
function->location(),
SecondarySourceLocation().append("Another declaration is here:", fallback->location()),
"Only one fallback function is allowed."
);
fallback = function;
}
else if (function->isReceive())
{
if (receive)
m_errorReporter.declarationError(
4046_error,
function->location(),
SecondarySourceLocation().append("Another declaration is here:", receive->location()),
"Only one receive function is allowed."
);
receive = function;
}
2018-11-29 16:39:57 +00:00
else
{
solAssert(!function->name().empty(), "");
functions[function->name()].push_back(function);
}
2020-05-12 09:42:29 +00:00
findDuplicateDefinitions(functions);
2018-11-29 16:39:57 +00:00
}
2018-11-29 17:23:58 +00:00
void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contract)
2018-11-29 16:39:57 +00:00
{
/// Checks that two events with the same name defined in this contract have different
/// argument types
map<string, vector<EventDefinition const*>> events;
for (EventDefinition const* event: _contract.events())
events[event->name()].push_back(event);
2020-05-12 09:42:29 +00:00
findDuplicateDefinitions(events);
2018-11-29 16:39:57 +00:00
}
template <class T>
2020-05-12 09:42:29 +00:00
void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const& _definitions)
2018-11-29 16:39:57 +00:00
{
for (auto const& it: _definitions)
{
vector<T> const& overloads = it.second;
set<size_t> 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 (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
2018-11-29 16:39:57 +00:00
{
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
}
if (ssl.infos.size() > 0)
{
2020-05-12 09:42:29 +00:00
ErrorId error;
string message;
if constexpr (is_same_v<T, FunctionDefinition const*>)
{
error = 1686_error;
2020-07-06 17:16:21 +00:00
message = "Function with same name and parameter types defined twice.";
2020-05-12 09:42:29 +00:00
}
else
{
static_assert(is_same_v<T, EventDefinition const*>, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\"");
error = 5883_error;
2020-07-06 17:16:21 +00:00
message = "Event with same name and parameter types defined twice.";
2020-05-12 09:42:29 +00:00
}
ssl.limitSize(message);
2018-11-29 16:39:57 +00:00
m_errorReporter.declarationError(
2020-05-12 09:42:29 +00:00
error,
2018-11-29 16:39:57 +00:00
overloads[i]->location(),
ssl,
2020-05-12 09:42:29 +00:00
message
2018-11-29 16:39:57 +00:00
);
}
}
}
}
2018-11-29 16:44:38 +00:00
void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract)
2018-11-29 16:51:57 +00:00
{
// Collects functions, static variable getters and modifiers. If they
// override (unimplemented) base class ones, they are replaced.
set<OverrideProxy, OverrideProxy::CompareBySignature> proxies;
2018-11-29 16:51:57 +00:00
auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy)
{
// Overwrite an existing proxy, if it exists.
if (!_overrideProxy.unimplemented())
proxies.erase(_overrideProxy);
proxies.insert(_overrideProxy);
};
// Search from base to derived, collect all functions and modifiers and
// update proxies.
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
{
for (VariableDeclaration const* v: contract->stateVariables())
if (v->isPartOfExternalInterface())
registerProxy(OverrideProxy(v));
for (FunctionDefinition const* function: contract->definedFunctions())
if (!function->isConstructor())
registerProxy(OverrideProxy(function));
for (ModifierDefinition const* modifier: contract->functionModifiers())
registerProxy(OverrideProxy(modifier));
}
2018-11-29 16:51:57 +00:00
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedDeclarations` has already been
2019-11-04 13:12:58 +00:00
// pre-filled by `checkBaseConstructorArguments`.
for (auto const& proxy: proxies)
if (proxy.unimplemented())
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
2019-09-04 22:01:13 +00:00
2019-11-04 13:12:58 +00:00
if (_contract.abstract())
{
if (_contract.contractKind() == ContractKind::Interface)
m_errorReporter.typeError(9348_error, _contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly.");
else if (_contract.contractKind() == ContractKind::Library)
m_errorReporter.typeError(9571_error, _contract.location(), "Libraries cannot be abstract.");
2019-11-04 13:12:58 +00:00
else
solAssert(_contract.contractKind() == ContractKind::Contract, "");
2019-11-04 13:12:58 +00:00
}
2019-09-04 22:01:13 +00:00
2019-11-04 13:12:58 +00:00
// For libraries, we emit errors on function-level, so this is fine as long as we do
// not have inheritance for libraries.
2019-09-04 22:01:13 +00:00
if (
_contract.contractKind() == ContractKind::Contract &&
2019-11-04 13:12:58 +00:00
!_contract.abstract() &&
!_contract.annotation().unimplementedDeclarations.empty()
2019-09-04 22:01:13 +00:00
)
{
SecondarySourceLocation ssl;
for (auto declaration: _contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(
2020-05-07 01:52:53 +00:00
3656_error,
_contract.location(),
ssl,
2020-05-12 09:42:29 +00:00
"Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."
);
}
2018-11-29 16:51:57 +00:00
}
2018-11-29 16:58:15 +00:00
2018-11-29 17:23:58 +00:00
void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition const& _contract)
2018-11-29 16:58:15 +00:00
{
vector<ContractDefinition const*> 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<ContractDefinition const*>(
modifier->name()->annotation().referencedDeclaration
))
{
if (modifier->arguments())
{
if (baseContract->constructor())
annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get());
}
else
m_errorReporter.declarationError(
1563_error,
2018-11-29 16:58:15 +00:00
modifier->location(),
"Modifier-style base constructor call without arguments."
);
}
for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts())
{
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
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().unimplementedDeclarations.push_back(constructor);
2018-11-29 16:58:15 +00:00
}
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());
2018-11-29 16:58:15 +00:00
}
m_errorReporter.declarationError(
3364_error,
2018-11-29 16:58:15 +00:00
*mainLocation,
ssl,
"Base constructor arguments given twice."
);
}
}
2018-11-29 17:26:53 +00:00
2018-11-29 17:32:06 +00:00
void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract)
{
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations;
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
{
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isPartOfExternalInterface())
{
auto functionType = TypeProvider::function(*f);
2018-11-29 17:32:06 +00:00
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
2020-05-26 08:48:17 +00:00
f, functionType->asExternallyCallableFunction(false)
2018-11-29 17:32:06 +00:00
);
}
for (VariableDeclaration const* v: contract->stateVariables())
if (v->isPartOfExternalInterface())
{
auto functionType = TypeProvider::function(*v);
2018-11-29 17:32:06 +00:00
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
2020-05-26 08:48:17 +00:00
v, functionType->asExternallyCallableFunction(false)
2018-11-29 17:32:06 +00:00
);
}
}
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(
9914_error,
2018-11-29 17:32:06 +00:00
it.second[j].first->location(),
"Function overload clash during conversion to external types for arguments."
);
}
2018-11-29 17:33:54 +00:00
void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contract)
{
2019-12-11 16:31:36 +00:00
set<util::FixedHash<4>> hashes;
2018-11-29 17:33:54 +00:00
for (auto const& it: _contract.interfaceFunctionList())
{
2019-12-11 16:31:36 +00:00
util::FixedHash<4> const& hash = it.first;
2018-11-29 17:33:54 +00:00
if (hashes.count(hash))
m_errorReporter.typeError(
1860_error,
2018-11-29 17:33:54 +00:00
_contract.location(),
string("Function signature hash collision for ") + it.second->externalSignature()
);
hashes.insert(hash);
}
}
2018-11-29 17:36:26 +00:00
void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract)
{
if (!_contract.isLibrary())
return;
if (!_contract.baseContracts().empty())
m_errorReporter.typeError(9469_error, _contract.location(), "Library is not allowed to inherit.");
2018-11-29 17:36:26 +00:00
for (auto const& var: _contract.stateVariables())
if (!var->isConstant())
m_errorReporter.typeError(9957_error, var->location(), "Library cannot have non-constant state variables");
2018-11-29 17:36:26 +00:00
}
void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract)
{
if (_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
return;
if (_contract.isLibrary())
{
solAssert(
_contract.baseContracts().empty() || m_errorReporter.hasErrors(),
"Library is not allowed to inherit"
);
return;
}
SecondarySourceLocation errors;
// interfaceFunctionList contains all inherited functions as well
for (auto const& func: _contract.interfaceFunctionList())
{
solAssert(func.second->hasDeclaration(), "Function has no declaration?!");
if (!func.second->declaration().sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
continue;
auto const& currentLoc = func.second->declaration().location();
for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes())
if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false))
{
2019-12-11 16:05:44 +00:00
errors.append("Type only supported by ABIEncoderV2", currentLoc);
break;
}
}
if (!errors.infos.empty())
m_errorReporter.fatalTypeError(
6594_error,
_contract.location(),
errors,
std::string("Contract \"") +
_contract.name() +
2019-12-11 16:05:44 +00:00
"\" does not use ABIEncoderV2 but wants to inherit from a contract " +
"which uses types that require it. " +
"Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature."
);
}
2019-09-16 12:33:43 +00:00
void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract)
{
if (auto const* fallback = _contract.fallbackFunction())
if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction())
m_errorReporter.warning(
3628_error,
_contract.location(),
"This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.",
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
);
}