mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Make ambigous override checker generic.
This commit is contained in:
parent
6d2e59cc80
commit
152f42c6b2
@ -744,127 +744,147 @@ void ContractLevelChecker::checkAmbiguousOverrides(ContractDefinition const& _co
|
||||
// Walk through the set of functions signature by signature.
|
||||
for (auto it = nonOverriddenFunctions.cbegin(); it != nonOverriddenFunctions.cend();)
|
||||
{
|
||||
static constexpr auto compareById = [](auto const* a, auto const* b) { return a->id() < b->id(); };
|
||||
std::set<FunctionDefinition const*, decltype(compareById)> baseFunctions(compareById);
|
||||
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)> compareById = [](auto const* a, auto const* b) { return a->id() < b->id(); };
|
||||
std::set<CallableDeclaration const*, std::function<bool(CallableDeclaration const*, CallableDeclaration const*)>> baseFunctions(compareById);
|
||||
for (auto nextSignature = nonOverriddenFunctions.upper_bound(*it); it != nextSignature; ++it)
|
||||
baseFunctions.insert(*it);
|
||||
|
||||
if (baseFunctions.size() <= 1)
|
||||
continue;
|
||||
|
||||
// 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
|
||||
{
|
||||
OverrideGraph(decltype(baseFunctions) const& _baseFunctions)
|
||||
{
|
||||
for (auto const* baseFunction: _baseFunctions)
|
||||
addEdge(0, visit(baseFunction));
|
||||
}
|
||||
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);
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
} overrideGraph(baseFunctions);
|
||||
|
||||
// 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 \"" +
|
||||
(*baseFunctions.begin())->name() +
|
||||
"\". Function with the same name and parameter types defined in two or more base classes."
|
||||
);
|
||||
checkAmbiguousOverridesInternal(std::move(baseFunctions), _contract.location());
|
||||
}
|
||||
}
|
||||
|
||||
void ContractLevelChecker::checkAmbiguousOverridesInternal(set<
|
||||
CallableDeclaration const*,
|
||||
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)>
|
||||
> _baseCallables, SourceLocation const& _location) const
|
||||
{
|
||||
if (_baseCallables.size() <= 1)
|
||||
return;
|
||||
|
||||
// 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
|
||||
{
|
||||
OverrideGraph(decltype(_baseCallables) const& __baseCallables)
|
||||
{
|
||||
for (auto const* baseFunction: __baseCallables)
|
||||
addEdge(0, visit(baseFunction));
|
||||
}
|
||||
std::map<CallableDeclaration const*, int> nodes;
|
||||
std::map<int, CallableDeclaration 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(CallableDeclaration 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);
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
} overrideGraph(_baseCallables);
|
||||
|
||||
// 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
|
||||
{
|
||||
CutVertexFinder(OverrideGraph const& _graph): m_graph(_graph)
|
||||
{
|
||||
run();
|
||||
}
|
||||
std::set<CallableDeclaration 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<CallableDeclaration 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<CallableDeclaration const*> toTraverse = function->annotation().baseFunctions;
|
||||
while (!toTraverse.empty())
|
||||
{
|
||||
auto const *base = *toTraverse.begin();
|
||||
toTraverse.erase(toTraverse.begin());
|
||||
_baseCallables.erase(base);
|
||||
for (CallableDeclaration const* f: base->annotation().baseFunctions)
|
||||
toTraverse.insert(f);
|
||||
}
|
||||
// Remove unimplemented base functions at the cut vertices itself as well.
|
||||
if (auto opt = dynamic_cast<ImplementationOptional const*>(function))
|
||||
if (!opt->isImplemented())
|
||||
_baseCallables.erase(function);
|
||||
}
|
||||
|
||||
// If more than one function is left, they have to be overridden.
|
||||
if (_baseCallables.size() <= 1)
|
||||
return;
|
||||
|
||||
SecondarySourceLocation ssl;
|
||||
for (auto const* baseFunction: _baseCallables)
|
||||
{
|
||||
string contractName = dynamic_cast<ContractDefinition const&>(*baseFunction->scope()).name();
|
||||
ssl.append("Definition in \"" + contractName + "\": ", baseFunction->location());
|
||||
}
|
||||
|
||||
string callableName;
|
||||
if (dynamic_cast<FunctionDefinition const*>(*_baseCallables.begin()))
|
||||
callableName = "function";
|
||||
else if (dynamic_cast<ModifierDefinition const*>(*_baseCallables.begin()))
|
||||
callableName = "modifier";
|
||||
else
|
||||
solAssert(false, "Invalid type for ambiguous override.");
|
||||
|
||||
m_errorReporter.typeError(
|
||||
_location,
|
||||
ssl,
|
||||
"Derived contract must override " + callableName + " \"" +
|
||||
(*_baseCallables.begin())->name() +
|
||||
"\". Two or more base classes define " + callableName + " with same name and parameter types."
|
||||
);
|
||||
}
|
||||
|
||||
set<ContractDefinition const*, ContractLevelChecker::LessFunction> ContractLevelChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const
|
||||
{
|
||||
set<ContractDefinition const*, LessFunction> resolved;
|
||||
|
@ -22,7 +22,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
namespace langutil
|
||||
@ -115,6 +117,10 @@ private:
|
||||
/// Checks for functions in different base contracts which conflict with each
|
||||
/// other and thus need to be overridden explicitly.
|
||||
void checkAmbiguousOverrides(ContractDefinition const& _contract) const;
|
||||
void checkAmbiguousOverridesInternal(std::set<
|
||||
CallableDeclaration const*,
|
||||
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)>
|
||||
> _baseCallables, langutil::SourceLocation const& _location) const;
|
||||
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
|
||||
std::set<ContractDefinition const*, LessFunction> resolveOverrideList(OverrideSpecifier const& _overrides) const;
|
||||
|
||||
|
@ -312,6 +312,16 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
|
||||
return contractDef->contractKind();
|
||||
}
|
||||
|
||||
CallableDeclarationAnnotation& CallableDeclaration::annotation() const
|
||||
{
|
||||
solAssert(
|
||||
m_annotation,
|
||||
"CallableDeclarationAnnotation is an abstract base, need to call annotation on the concrete class first."
|
||||
);
|
||||
return dynamic_cast<CallableDeclarationAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
|
||||
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
|
@ -624,6 +624,8 @@ public:
|
||||
bool markedVirtual() const { return m_isVirtual; }
|
||||
virtual bool virtualSemantics() const { return markedVirtual(); }
|
||||
|
||||
CallableDeclarationAnnotation& annotation() const override;
|
||||
|
||||
protected:
|
||||
ASTPointer<ParameterList> m_parameters;
|
||||
ASTPointer<OverrideSpecifier> m_overrides;
|
||||
|
@ -104,19 +104,23 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
|
||||
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
|
||||
};
|
||||
|
||||
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
struct CallableDeclarationAnnotation: ASTAnnotation
|
||||
{
|
||||
/// The set of functions/modifiers/events this callable overrides.
|
||||
std::set<CallableDeclaration const*> baseFunctions;
|
||||
};
|
||||
|
||||
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
||||
{
|
||||
/// The set of functions this function overrides.
|
||||
std::set<FunctionDefinition const*> baseFunctions;
|
||||
/// Pointer to the contract this function is defined in
|
||||
ContractDefinition const* contract = nullptr;
|
||||
};
|
||||
|
||||
struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
||||
{
|
||||
};
|
||||
|
||||
struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
||||
{
|
||||
};
|
||||
|
||||
|
@ -392,7 +392,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
|
||||
|
||||
bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "ModifierDefinition", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
|
||||
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||
@ -400,7 +400,10 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
||||
make_pair("virtual", _node.markedVirtual()),
|
||||
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
|
||||
make_pair("body", toJson(_node.body()))
|
||||
});
|
||||
};
|
||||
if (!_node.annotation().baseFunctions.empty())
|
||||
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
|
||||
setJsonNode(_node, "ModifierDefinition", std::move(attributes));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -16,5 +16,5 @@ abstract contract B is I {
|
||||
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.
|
||||
// TypeError: (342-364): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
// TypeError: (342-364): Derived contract must override function "g". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -14,4 +14,4 @@ abstract contract B is I {
|
||||
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.
|
||||
// TypeError: (254-276): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -13,5 +13,5 @@ abstract contract B is I {
|
||||
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.
|
||||
// TypeError: (292-314): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
// TypeError: (292-314): Derived contract must override function "g". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -6,4 +6,4 @@ contract B {
|
||||
}
|
||||
contract C is A, B {}
|
||||
// ----
|
||||
// TypeError: (126-147): Derived contract must override function "f". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (126-147): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -9,5 +9,5 @@ abstract contract B {
|
||||
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.
|
||||
// TypeError: (176-198): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
// TypeError: (176-198): Derived contract must override function "g". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -15,3 +15,6 @@ contract B is IJ
|
||||
{
|
||||
}
|
||||
contract C is A, B {}
|
||||
// ----
|
||||
// TypeError: (14-33): Trying to override non-virtual modifier. Did you forget to add "virtual"?
|
||||
// TypeError: (50-69): Trying to override non-virtual modifier. Did you forget to add "virtual"?
|
||||
|
@ -9,4 +9,4 @@ abstract contract X is A, B {
|
||||
function test() internal override returns (uint256) {}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (205-292): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (205-292): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -10,4 +10,4 @@ contract C is A, B
|
||||
{
|
||||
}
|
||||
// ----
|
||||
// TypeError: (94-116): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (94-116): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -9,4 +9,4 @@ abstract contract X is A, B {
|
||||
function test() internal override virtual returns (uint256);
|
||||
}
|
||||
// ----
|
||||
// TypeError: (203-296): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (203-296): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -8,4 +8,4 @@ contract X is A, B {
|
||||
uint public override foo;
|
||||
}
|
||||
// ----
|
||||
// TypeError: (162-211): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (162-211): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -11,4 +11,4 @@ contract X is B, C {
|
||||
uint public override foo;
|
||||
}
|
||||
// ----
|
||||
// TypeError: (271-320): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (271-320): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -12,4 +12,4 @@ contract X is B, C {
|
||||
}
|
||||
// ----
|
||||
// DeclarationError: (245-269): Identifier already declared.
|
||||
// TypeError: (223-272): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (223-272): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -8,4 +8,4 @@ contract X is A, B {
|
||||
uint public override(A, B) foo;
|
||||
}
|
||||
// ----
|
||||
// TypeError: (162-217): Derived contract must override function "foo". Function with the same name and parameter types defined in two or more base classes.
|
||||
// TypeError: (162-217): Derived contract must override function "foo". Two or more base classes define function with same name and parameter types.
|
||||
|
@ -3,4 +3,4 @@ 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.
|
||||
// TypeError: (116-138): Derived contract must override function "f". Two or more base classes define function with same name and parameter types.
|
||||
|
Loading…
Reference in New Issue
Block a user