Merge pull request #7878 from ethereum/overrideUnimplementedWithBaseImpl

Do not require overriding for functions in common base with unique implementation.
This commit is contained in:
chriseth 2019-12-09 17:15:07 +01:00 committed by GitHub
commit da192e627e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 315 additions and 59 deletions

View File

@ -237,8 +237,10 @@ bases, it has to explicitly override it:
function foo() public override(Base1, Base2) {}
}
A function defined in a common base contract does not have to be explicitly
overridden when used with multiple inheritance:
An explicit override specifier is not required if
the function is defined in a common base contract
or if there is a unique function in a common base contract
that already overrides all other functions.
::
@ -250,6 +252,19 @@ overridden when used with multiple inheritance:
// No explicit override required
contract D is B, C {}
More formally, it is not required to override a function (directly or
indirectly) inherited from multiple bases if there is a base contract
that is part of all override paths for the signature, and (1) that
base implements the function and no paths from the current contract
to the base mentions a function with that signature or (2) that base
does not implement the function and there is at most one mention of
the function in all paths from the current contract to that base.
In this sense, an override path for a signature is a path through
the inheritance graph that starts at the contract under consideration
and ends at a contract mentioning a function with that signature
that does not override.
.. note::
Functions with the ``private`` visibility cannot be ``virtual``.

View File

@ -112,24 +112,32 @@ inline std::set<T> operator+(std::set<T>&& _a, U&& _b)
return ret;
}
/// Remove one set from another one.
template <class... T>
inline std::set<T...>& operator-=(std::set<T...>& _a, std::set<T...> const& _b)
/// Remove the elements of a container from a set.
template <class C, class... T>
inline std::set<T...>& operator-=(std::set<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
return _a;
}
template <class... T>
inline std::set<T...> operator-(std::set<T...> const& _a, std::set<T...> const& _b)
template <class C, class... T>
inline std::set<T...> operator-(std::set<T...> const& _a, C const& _b)
{
auto result = _a;
result -= _b;
return result;
}
/// Remove the elements of a container from a multiset.
template <class C, class... T>
inline std::multiset<T...>& operator-=(std::multiset<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
return _a;
}
namespace dev
{

View File

@ -238,8 +238,8 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract)
{
FunctionMultiSet const& funcSet = inheritedFunctions(&_contract);
ModifierMultiSet const& modSet = inheritedModifiers(&_contract);
FunctionMultiSet const& funcSet = inheritedFunctions(_contract);
ModifierMultiSet const& modSet = inheritedModifiers(_contract);
checkModifierOverrides(funcSet, modSet, _contract.functionModifiers());
@ -666,56 +666,132 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
void ContractLevelChecker::checkAmbiguousOverrides(ContractDefinition const& _contract) const
{
vector<FunctionDefinition const*> contractFuncs = _contract.definedFunctions();
// Fetch inherited functions and sort them by signature.
// We get at least one function per signature and direct base contract, which is
// enough because we re-construct the inheritance graph later.
FunctionMultiSet nonOverriddenFunctions = inheritedFunctions(_contract);
// Remove all functions that match the signature of a function in the current contract.
nonOverriddenFunctions -= _contract.definedFunctions();
auto const resolvedBases = resolveDirectBaseContracts(_contract);
FunctionMultiSet inheritedFuncs = inheritedFunctions(&_contract);;
// Check the sets of the most-inherited functions
for (auto it = inheritedFuncs.cbegin(); it != inheritedFuncs.cend(); it = inheritedFuncs.upper_bound(*it))
// Walk through the set of functions signature by signature.
for (auto it = nonOverriddenFunctions.cbegin(); it != nonOverriddenFunctions.cend();)
{
auto [begin,end] = inheritedFuncs.equal_range(*it);
static constexpr auto compareById = [](auto const* a, auto const* b) { return a->id() < b->id(); };
std::set<FunctionDefinition const*, decltype(compareById)> baseFunctions(compareById);
for (auto nextSignature = nonOverriddenFunctions.upper_bound(*it); it != nextSignature; ++it)
baseFunctions.insert(*it);
// Only one function
if (next(begin) == end)
if (baseFunctions.size() <= 1)
continue;
// Not an overridable function
if ((*it)->isConstructor())
// Construct the override graph for this signature.
// Reserve node 0 for the current contract and node
// 1 for an artificial top node to which all override paths
// connect at the end.
struct OverrideGraph
{
for (begin++; begin != end; begin++)
solAssert((*begin)->isConstructor(), "All functions in range expected to be constructors!");
continue;
}
// Function has been explicitly overridden
if (contains_if(
contractFuncs,
[&] (FunctionDefinition const* _f) {
return hasEqualNameAndParameters(*_f, **it);
OverrideGraph(decltype(baseFunctions) const& _baseFunctions)
{
for (auto const* baseFunction: _baseFunctions)
addEdge(0, visit(baseFunction));
}
))
continue;
std::map<FunctionDefinition const*, int> nodes;
std::map<int, FunctionDefinition const*> nodeInv;
std::map<int, std::set<int>> edges;
int numNodes = 2;
void addEdge(int _a, int _b)
{
edges[_a].insert(_b);
edges[_b].insert(_a);
}
private:
/// Completes the graph starting from @a _function and
/// @returns the node ID.
int visit(FunctionDefinition const* _function)
{
auto it = nodes.find(_function);
if (it != nodes.end())
return it->second;
int currentNode = numNodes++;
nodes[_function] = currentNode;
nodeInv[currentNode] = _function;
if (_function->overrides())
for (auto const* baseFunction: _function->annotation().baseFunctions)
addEdge(currentNode, visit(baseFunction));
else
addEdge(currentNode, 1);
set<FunctionDefinition const*> ambiguousFunctions;
SecondarySourceLocation ssl;
return currentNode;
}
} overrideGraph(baseFunctions);
for (;begin != end; begin++)
// Detect cut vertices following https://en.wikipedia.org/wiki/Biconnected_component#Pseudocode
// Can ignore the root node, since it is never a cut vertex in our case.
struct CutVertexFinder
{
ambiguousFunctions.insert(*begin);
ssl.append("Definition here: ", (*begin)->location());
CutVertexFinder(OverrideGraph const& _graph): m_graph(_graph)
{
run();
}
std::set<FunctionDefinition const*> const& cutVertices() const { return m_cutVertices; }
private:
OverrideGraph const& m_graph;
std::vector<bool> m_visited = std::vector<bool>(m_graph.numNodes, false);
std::vector<int> m_depths = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_low = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_parent = std::vector<int>(m_graph.numNodes, -1);
std::set<FunctionDefinition const*> m_cutVertices{};
void run(int _u = 0, int _depth = 0)
{
m_visited.at(_u) = true;
m_depths.at(_u) = m_low.at(_u) = _depth;
for (int v: m_graph.edges.at(_u))
if (!m_visited.at(v))
{
m_parent[v] = _u;
run(v, _depth + 1);
if (m_low[v] >= m_depths[_u] && m_parent[_u] != -1)
m_cutVertices.insert(m_graph.nodeInv.at(_u));
m_low[_u] = min(m_low[_u], m_low[v]);
}
else if (v != m_parent[_u])
m_low[_u] = min(m_low[_u], m_depths[v]);
}
} cutVertexFinder{overrideGraph};
// Remove all base functions overridden by cut vertices (they don't need to be overridden).
for (auto const* function: cutVertexFinder.cutVertices())
{
std::set<FunctionDefinition const*> toTraverse = function->annotation().baseFunctions;
while (!toTraverse.empty())
{
auto const* base = *toTraverse.begin();
toTraverse.erase(toTraverse.begin());
baseFunctions.erase(base);
for (auto const* f: base->annotation().baseFunctions)
toTraverse.insert(f);
}
// Remove unimplemented base functions at the cut vertices themselves as well.
if (!function->isImplemented())
baseFunctions.erase(function);
}
// Make sure the functions are not from the same base contract
if (ambiguousFunctions.size() == 1)
// If more than one function is left, they have to be overridden.
if (baseFunctions.size() <= 1)
continue;
SecondarySourceLocation ssl;
for (auto const* baseFunction: baseFunctions)
ssl.append("Definition here: ", baseFunction->location());
m_errorReporter.typeError(
_contract.location(),
ssl,
"Derived contract must override function \"" +
(*it)->name() +
(*baseFunctions.begin())->name() +
"\". Function with the same name and parameter types defined in two or more base classes."
);
}
@ -845,50 +921,52 @@ void ContractLevelChecker::checkOverrideList(FunctionMultiSet const& _funcSet, F
);
}
ContractLevelChecker::FunctionMultiSet const& ContractLevelChecker::inheritedFunctions(ContractDefinition const* _contract) const
ContractLevelChecker::FunctionMultiSet const& ContractLevelChecker::inheritedFunctions(ContractDefinition const& _contract) const
{
if (!m_inheritedFunctions.count(_contract))
if (!m_inheritedFunctions.count(&_contract))
{
FunctionMultiSet set;
for (auto const* base: resolveDirectBaseContracts(*_contract))
for (auto const* base: resolveDirectBaseContracts(_contract))
{
std::set<FunctionDefinition const*, LessFunction> tmpSet =
convertContainer<decltype(tmpSet)>(base->definedFunctions());
std::set<FunctionDefinition const*, LessFunction> functionsInBase;
for (FunctionDefinition const* fun: base->definedFunctions())
if (!fun->isConstructor())
functionsInBase.emplace(fun);
for (auto const& func: inheritedFunctions(base))
tmpSet.insert(func);
for (auto const& func: inheritedFunctions(*base))
functionsInBase.insert(func);
set += tmpSet;
set += functionsInBase;
}
m_inheritedFunctions[_contract] = set;
m_inheritedFunctions[&_contract] = set;
}
return m_inheritedFunctions[_contract];
return m_inheritedFunctions[&_contract];
}
ContractLevelChecker::ModifierMultiSet const& ContractLevelChecker::inheritedModifiers(ContractDefinition const* _contract) const
ContractLevelChecker::ModifierMultiSet const& ContractLevelChecker::inheritedModifiers(ContractDefinition const& _contract) const
{
auto const& result = m_contractBaseModifiers.find(_contract);
auto const& result = m_contractBaseModifiers.find(&_contract);
if (result != m_contractBaseModifiers.cend())
return result->second;
ModifierMultiSet set;
for (auto const* base: resolveDirectBaseContracts(*_contract))
for (auto const* base: resolveDirectBaseContracts(_contract))
{
std::set<ModifierDefinition const*, LessFunction> tmpSet =
convertContainer<decltype(tmpSet)>(base->functionModifiers());
for (auto const& mod: inheritedModifiers(base))
for (auto const& mod: inheritedModifiers(*base))
tmpSet.insert(mod);
set += tmpSet;
}
return m_contractBaseModifiers[_contract] = set;
return m_contractBaseModifiers[&_contract] = set;
}
void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract)

View File

@ -106,8 +106,8 @@ private:
/// Returns all functions of bases that have not yet been overwritten.
/// May contain the same function multiple times when used with shared bases.
FunctionMultiSet const& inheritedFunctions(ContractDefinition const* _contract) const;
ModifierMultiSet const& inheritedModifiers(ContractDefinition const* _contract) const;
FunctionMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const;
ModifierMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const;
/// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);

View File

@ -0,0 +1,20 @@
interface I {
function f() external;
function g() external;
}
interface J {
function f() external;
}
abstract contract A is I, J {
function f() external override (I, J) {}
function g() external override virtual;
}
abstract contract B is I {
function f() external override virtual;
function g() external override {}
}
contract C is A, B {
}
// ----
// TypeError: (342-364): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.
// TypeError: (342-364): Derived contract must override function "g". Function with the same name and parameter types defined in two or more base classes.

View File

@ -0,0 +1,17 @@
interface I {
function f() external;
function g() external;
}
interface J {
function f() external;
}
abstract contract A is I, J {
function f() external override (I, J) {}
}
abstract contract B is I {
function g() external override {}
}
contract C is A, B {
}
// ----
// TypeError: (254-276): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.

View File

@ -0,0 +1,12 @@
contract A {
function f() external virtual {}
}
contract B {
function f() external virtual {}
}
contract C is A, B {
function f() external override (A, B) {}
}
contract X is C {
}
// ----

View File

@ -0,0 +1,15 @@
contract A {
function f() external virtual {}
}
contract B {
function f() external virtual {}
}
contract C is A, B {
function f() external override (A, B);
}
contract X is C {
}
// ----
// TypeError: (120-158): Overriding an implemented function with an unimplemented function is not allowed.
// TypeError: (120-158): Overriding an implemented function with an unimplemented function is not allowed.
// TypeError: (120-158): Functions without implementation must be marked virtual.

View File

@ -0,0 +1,17 @@
interface I {
function f() external;
function g() external;
}
abstract contract A is I {
function f() external override {}
function g() external override virtual;
}
abstract contract B is I {
function g() external override {}
function f() external override virtual;
}
contract C is A, B {
}
// ----
// TypeError: (292-314): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.
// TypeError: (292-314): Derived contract must override function "g". Function with the same name and parameter types defined in two or more base classes.

View File

@ -0,0 +1,12 @@
interface I {
function f() external;
function g() external;
}
abstract contract A is I {
function f() external override {}
}
abstract contract B is I {
function g() external override {}
}
contract C is A, B {
}

View File

@ -0,0 +1,13 @@
abstract contract A {
function f() external {}
function g() external virtual;
}
abstract contract B {
function g() external {}
function f() external virtual;
}
contract C is A, B {
}
// ----
// TypeError: (176-198): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.
// TypeError: (176-198): Derived contract must override function "g". Function with the same name and parameter types defined in two or more base classes.

View File

@ -0,0 +1,19 @@
interface I {
function f() external;
function g() external;
}
interface J {
function f() external;
}
abstract contract IJ is I, J {
function f() external virtual override (I, J);
}
abstract contract A is IJ
{
function f() external override {}
}
abstract contract B is IJ
{
function g() external override {}
}
contract C is A, B {}

View File

@ -0,0 +1,19 @@
interface I {
function f() external;
function g() external;
}
interface J {
function f() external;
}
abstract contract IJ is I, J {
function f() external virtual override (I, J);
}
abstract contract A is IJ
{
function f() external virtual override;
}
abstract contract B is IJ
{
function g() external override {}
}
abstract contract C is A, B {}

View File

@ -0,0 +1,6 @@
contract A { function f() public pure virtual {} }
contract B is A { function f() public pure virtual override {} }
contract C is A, B { }
contract D is A, B { function f() public pure override(A, B) {} }
// ----
// TypeError: (116-138): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.

View File

@ -0,0 +1,5 @@
abstract contract A { function f() public pure virtual; }
contract B is A { function f() public pure virtual override {} }
contract C is A, B { }
contract D is A, B { function f() public pure override(A, B) {} }
// ----