Contract level checker: Disallow free function redefinition and alias

shadowing another free function

Co-authored-by: chriseth <chris@ethereum.org>
This commit is contained in:
Bhargava Shastry 2020-09-21 15:36:00 +02:00
parent 1cc0d642e8
commit e2a2276272
45 changed files with 564 additions and 17 deletions

View File

@ -1,5 +1,8 @@
### 0.7.2 (unreleased)
Important Bugfixes:
* Type Checker: Disallow two or more free functions with identical name (potentially imported and aliased) and parameter types.
Language Features:

View File

@ -1,4 +1,12 @@
[
{
"name": "FreeFunctionRedefinition",
"summary": "The compiler does not flag an error when two or more free functions with the same name and parameter types are defined in a source unit or when an imported free function alias shadows another free function with a different name but identical parameter types.",
"description": "In contrast to functions defined inside contracts, free functions with identical names and parameter types did not create an error. Both definition of free functions with identical name and parameter types and an imported free function with an alias that shadows another function with a different name but identical parameter types were permitted due to which a call to either the multiply defined free function or the imported free function alias within a contract led to the execution of that free function which was defined first within the source unit. Subsequently defined identical free function definitions were silently ignored and their code generation was skipped.",
"introduced": "0.7.1",
"fixed": "0.7.2",
"severity": "low"
},
{
"name": "UsingForCalldata",
"summary": "Function calls to internal library functions with calldata parameters called via ``using for`` can result in invalid data being read.",

View File

@ -1187,7 +1187,9 @@
"released": "2020-07-28"
},
"0.7.1": {
"bugs": [],
"bugs": [
"FreeFunctionRedefinition"
],
"released": "2020-09-02"
}
}

View File

@ -38,15 +38,45 @@ namespace
{
template <class T, class B>
bool hasEqualNameAndParameters(T const& _a, B const& _b)
bool hasEqualParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
return FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
}
template<typename T>
map<ASTString, vector<T const*>> filterDeclarations(
map<ASTString, vector<Declaration const*>> const& _declarations)
{
map<ASTString, vector<T const*>> filteredDeclarations;
for (auto const& [name, overloads]: _declarations)
for (auto const* declaration: overloads)
if (auto typedDeclaration = dynamic_cast<T const*>(declaration))
filteredDeclarations[name].push_back(typedDeclaration);
return filteredDeclarations;
}
}
bool ContractLevelChecker::check(SourceUnit const& _sourceUnit)
{
bool noErrors = true;
findDuplicateDefinitions(
filterDeclarations<FunctionDefinition>(*_sourceUnit.annotation().exportedSymbols)
);
// This check flags duplicate free events when free events become
// a Solidity feature
findDuplicateDefinitions(
filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols)
);
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
noErrors = false;
for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!check(*contract))
noErrors = false;
return noErrors;
}
bool ContractLevelChecker::check(ContractDefinition const& _contract)
@ -143,8 +173,21 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j)
if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
if (hasEqualParameters(*overloads[i], *overloads[j]))
{
solAssert(
(
dynamic_cast<ContractDefinition const*>(overloads[i]->scope()) &&
dynamic_cast<ContractDefinition const*>(overloads[j]->scope()) &&
overloads[i]->name() == overloads[j]->name()
) ||
(
dynamic_cast<SourceUnit const*>(overloads[i]->scope()) &&
dynamic_cast<SourceUnit const*>(overloads[j]->scope())
),
"Override is neither a namesake function/event in contract scope nor "
"a free function/event (alias)."
);
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
}

View File

@ -39,7 +39,7 @@ namespace solidity::frontend
/**
* Component that verifies overloads, abstract contracts, function clashes and others
* checks at contract or function level.
* checks at file, contract, or function level.
*/
class ContractLevelChecker
{
@ -51,11 +51,14 @@ public:
m_errorReporter(_errorReporter)
{}
/// Performs checks on the given source ast.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
bool check(SourceUnit const& _sourceUnit);
private:
/// 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);

View File

@ -121,6 +121,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
))
error = true;
}
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
return !error;
}
@ -533,7 +534,6 @@ bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit)
{
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
ASTVisitor::endVisit(_sourceUnit);
}

View File

@ -366,12 +366,10 @@ bool CompilerStack::analyze()
// 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)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!contractLevelChecker.check(*contract))
noErrors = false;
if (auto sourceAst = source->ast)
noErrors = contractLevelChecker.check(*sourceAst);
// Requires ContractLevelChecker
DocStringAnalyser docStringAnalyser(m_errorReporter);

View File

@ -0,0 +1,10 @@
function f() pure returns (uint) { return 1337; }
contract C {
function f() public pure returns (uint) {
return f();
}
}
// ====
// compileViaYul: also
// ----
// f() -> FAILURE

View File

@ -0,0 +1,15 @@
==== Source: s1.sol ====
import {f as g} from "s2.sol";
function f() pure returns (uint) { return 1; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
function f() pure returns (uint) { return 2; }
contract C {
function foo() public pure returns (uint) {
return f() - g();
}
}
// ====
// compileViaYul: also
// ----
// foo() -> 1

View File

@ -0,0 +1,14 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
contract C {
function foo() public pure returns (uint) {
return h() - f() - g();
}
}
// ----
// foo() -> -4

View File

@ -0,0 +1,16 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
==== Source: s3.sol ====
import "s1.sol";
contract C {
function foo() public pure returns (uint) {
return f() - g() - h();
}
}
// ----
// foo() -> -4

View File

@ -0,0 +1,16 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
==== Source: s3.sol ====
import "s2.sol";
contract C {
function foo() public pure returns (uint) {
return f() - g() - h();
}
}
// ----
// foo() -> -4

View File

@ -0,0 +1,14 @@
==== Source: s1.sol ====
function f(uint24) pure returns (uint) { return 24; }
function g(bool) pure returns (bool) { return true; }
==== Source: s2.sol ====
import {f as g, g as g} from "s1.sol";
contract C {
function foo() public pure returns (uint, bool) {
return (g(2), g(false));
}
}
// ====
// compileViaYul: also
// ----
// foo() -> 24, true

View File

@ -0,0 +1,18 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {
function h() public pure returns (uint) {
return g();
}
}
// ====
// compileViaYul: also
// ----
// h() -> 1337

View File

@ -0,0 +1,18 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure virtual returns (uint) {
return f() + 1;
}
}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {
function g() public pure override returns (uint) {
return f();
}
}
// ====
// compileViaYul: also
// ----
// g() -> 1337

View File

@ -0,0 +1,18 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure virtual returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {
function g() public pure override returns (uint) {
return super.g();
}
}
// ====
// compileViaYul: also
// ----
// g() -> 1337

View File

@ -0,0 +1,25 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure virtual returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {
function g() public pure virtual override returns (uint) {
return super.g() + 1;
}
}
==== Source: s3.sol ====
import "s2.sol";
contract E is D {
function g() public pure override returns (uint) {
return super.g() + 1;
}
}
// ====
// compileViaYul: also
// ----
// g() -> 1339

View File

@ -0,0 +1,27 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {
function h() public pure returns (uint) {
return g();
}
}
==== Source: s3.sol ====
import "s2.sol";
import {f as f} from "s2.sol";
contract E is D {
function i() public pure returns (uint) {
return f();
}
}
// ====
// compileViaYul: also
// ----
// i() -> 1337

View File

@ -0,0 +1,19 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function g() public pure virtual returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol" as M;
function f() pure returns (uint) { return 6; }
contract D is M.C {
function g() public pure override returns (uint) {
return super.g() + f() * 10000;
}
}
// ====
// compileViaYul: also
// ----
// g() -> 61337

View File

@ -0,0 +1,14 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
function f() pure returns (uint) { return 6; }
contract D {
function h() public pure returns (uint) {
return g() + f() * 10000;
}
}
// ====
// compileViaYul: also
// ----
// h() -> 61337

View File

@ -0,0 +1,15 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
==== Source: s3.sol ====
import {g as h} from "s2.sol";
contract C {
function foo() public pure returns (uint) {
return h();
}
}
// ====
// compileViaYul: also
// ----
// foo() -> 1337

View File

@ -0,0 +1,4 @@
function f(uint24) {}
function f(uint16) {}
function f(int24) {}
function f(bool) {}

View File

@ -0,0 +1,11 @@
function g() pure returns (uint) { return 1; }
function g() pure returns (string memory) { return "1"; }
contract C {
function foo() public pure returns (uint) {
string memory s = g();
return 100/g();
}
}
// ----
// DeclarationError 1686: (0-46): Function with same name and parameter types defined twice.
// TypeError 9574: (168-189): Type uint256 is not implicitly convertible to expected type string memory.

View File

@ -0,0 +1,4 @@
function f() pure returns (uint) { return 1337; }
function f() pure returns (uint) { return 42; }
// ----
// DeclarationError 1686: (0-49): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,5 @@
function f() pure returns (uint) { return 1337; }
function f() pure returns (uint) { return 42; }
function f() pure returns (uint) { return 1; }
// ----
// DeclarationError 1686: (0-49): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,8 @@
function f() pure returns (uint) { return 1337; }
contract C {
function f() public pure returns (uint) {
return f();
}
}
// ----
// Warning 2519: (65-126): This declaration shadows an existing declaration.

View File

@ -0,0 +1,9 @@
function f() pure returns (uint) { return 1337; }
function f() view returns (uint) { return 42; }
contract C {
function g() public pure virtual returns (uint) {
return f();
}
}
// ----
// DeclarationError 1686: (0-49): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,19 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
function g() pure returns (uint) { return 42; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
==== Source: s3.sol ====
// imports f()->1337 as g()
import "s2.sol";
// imports f()->1337 as f() and
// g()->42 as g
import {f as f, g as g} from "s1.sol";
contract C {
function foo() public pure returns (uint) {
// calls f()->1337 / f()->1337
return f() / g();
}
}
// ----
// DeclarationError 1686: (s1.sol:0-49): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,19 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
function g() pure returns (uint) { return 42; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
==== Source: s3.sol ====
// imports f()->1337 as g()
import "s2.sol";
// imports f()->1337 as f() and
// g()->42 as g
import "s1.sol";
contract C {
function foo() public pure returns (uint) {
// calls f()->1337 / f()->1337
return f() / g();
}
}
// ----
// DeclarationError 1686: (s1.sol:0-49): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,6 @@
==== Source: s1.sol ====
import {f as g} from "s2.sol";
function f() pure returns (uint) { return 1; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
function f() pure returns (uint) { return 2; }

View File

@ -0,0 +1,7 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }

View File

@ -0,0 +1,14 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
==== Source: s3.sol ====
import "s1.sol";
import "s2.sol";
// ----
// DeclarationError 1686: (s1.sol:39-93): Function with same name and parameter types defined twice.
// DeclarationError 1686: (s2.sol:31-77): Function with same name and parameter types defined twice.
// DeclarationError 1686: (s2.sol:78-124): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
==== Source: s3.sol ====
import "s1.sol";

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); }
==== Source: s2.sol ====
import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; }
function g() pure returns (uint) { return 4; }
==== Source: s3.sol ====
import "s2.sol";

View File

@ -0,0 +1,10 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as f} from "s1.sol";
import {f as f} from "s1.sol";
contract C {
function g() public pure returns (uint) {
return f();
}
}

View File

@ -0,0 +1,10 @@
==== Source: s1.sol ====
function f(uint) pure returns (uint) { return 24; }
function g() pure returns (bool) { return true; }
==== Source: s2.sol ====
import {f as g, g as g} from "s1.sol";
contract C {
function foo() public pure returns (uint, bool) {
return (g(2), g());
}
}

View File

@ -0,0 +1,19 @@
==== Source: s1.sol ====
function f() pure returns (uint16) { return 1337; }
function g() pure returns (uint8) { return 42; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
==== Source: s3.sol ====
// imports f(uint16)->1337 as g(uint16)
import "s2.sol";
// imports f(uint16)->1337 as f(uint16) and
// g(uint8)->42 as g(uint8)
import {f as f, g as g} from "s1.sol";
contract C {
function foo() public pure returns (uint) {
// calls f()->1337 / f()->1337
return f() / g();
}
}
// ----
// DeclarationError 1686: (s1.sol:0-51): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {}
==== Source: s2.sol ====
import "s1.sol";
function f() pure returns (uint) { return 42; }
contract D is C {}
// ----
// DeclarationError 1686: (s2.sol:17-64): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,12 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {}
==== Source: s2.sol ====
import "s1.sol";
contract D is C {}
==== Source: s3.sol ====
import "s2.sol";
function f() pure returns (uint) { return 42; }
contract E is D {}
// ----
// DeclarationError 1686: (s3.sol:17-64): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,19 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
contract C {
function f() public pure virtual returns (uint) {
return f();
}
}
==== Source: s2.sol ====
import "s1.sol";
function f() pure returns (uint) { return 42; }
contract D is C {
function f() public pure override returns (uint) {
return f();
}
}
// ----
// Warning 2519: (s1.sol:65-134): This declaration shadows an existing declaration.
// Warning 2519: (s2.sol:85-155): This declaration shadows an existing declaration.
// DeclarationError 1686: (s2.sol:17-64): Function with same name and parameter types defined twice.

View File

@ -0,0 +1,10 @@
==== Source: s1.sol ====
contract C {
function f() public pure returns (uint) {
return 1337;
}
}
==== Source: s2.sol ====
import {C.f as g} from "s1.sol";
// ----
// ParserError 2314: (s2.sol:9-10): Expected '}' but got '.'

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import "s1.sol";
contract C {
function g() public pure returns (uint) {
return f();
}
}

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as f, f as f, f as f} from "s1.sol";
contract C {
function g() public pure returns (uint) {
return f();
}
}

View File

@ -0,0 +1,11 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as g} from "s1.sol";
==== Source: s3.sol ====
import {g as h} from "s2.sol";
contract C {
function foo() public pure returns (uint) {
return h();
}
}

View File

@ -0,0 +1,9 @@
==== Source: s1.sol ====
function f() pure returns (uint) { return 1337; }
==== Source: s2.sol ====
import {f as f, f as f} from "s1.sol";
contract C {
function g() public pure returns (uint) {
return f();
}
}