Skip to content

Commit 907bde6

Browse files
authored
Merge pull request #10973 from ethereum/function-call-graph-v2
Function Call Graph v2
2 parents e75e3fc + 051995a commit 907bde6

File tree

12 files changed

+2618
-11
lines changed

12 files changed

+2618
-11
lines changed

libsolidity/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ set(sources
1818
analysis/DocStringAnalyser.h
1919
analysis/DocStringTagParser.cpp
2020
analysis/DocStringTagParser.h
21+
analysis/FunctionCallGraph.cpp
22+
analysis/FunctionCallGraph.h
2123
analysis/ImmutableValidator.cpp
2224
analysis/ImmutableValidator.h
2325
analysis/GlobalContext.cpp
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libsolidity/analysis/FunctionCallGraph.h>
20+
21+
#include <libsolutil/StringUtils.h>
22+
23+
#include <range/v3/range/conversion.hpp>
24+
#include <range/v3/view/reverse.hpp>
25+
#include <range/v3/view/transform.hpp>
26+
27+
using namespace std;
28+
using namespace ranges;
29+
using namespace solidity::frontend;
30+
using namespace solidity::util;
31+
32+
bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const
33+
{
34+
if (_lhs.index() != _rhs.index())
35+
return _lhs.index() < _rhs.index();
36+
37+
if (holds_alternative<SpecialNode>(_lhs))
38+
return get<SpecialNode>(_lhs) < get<SpecialNode>(_rhs);
39+
return get<CallableDeclaration const*>(_lhs)->id() < get<CallableDeclaration const*>(_rhs)->id();
40+
}
41+
42+
bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const
43+
{
44+
solAssert(!holds_alternative<SpecialNode>(_lhs), "");
45+
46+
return get<CallableDeclaration const*>(_lhs)->id() < _rhs;
47+
}
48+
49+
bool FunctionCallGraphBuilder::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const
50+
{
51+
solAssert(!holds_alternative<SpecialNode>(_rhs), "");
52+
53+
return _lhs < get<CallableDeclaration const*>(_rhs)->id();
54+
}
55+
56+
FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildCreationGraph(ContractDefinition const& _contract)
57+
{
58+
FunctionCallGraphBuilder builder(_contract);
59+
solAssert(builder.m_currentNode == Node(SpecialNode::Entry), "");
60+
61+
// Create graph for constructor, state vars, etc
62+
for (ContractDefinition const* base: _contract.annotation().linearizedBaseContracts | views::reverse)
63+
{
64+
// The constructor and functions called in state variable initial assignments should have
65+
// an edge from Entry
66+
builder.m_currentNode = SpecialNode::Entry;
67+
for (auto const* stateVar: base->stateVariables())
68+
stateVar->accept(builder);
69+
70+
if (base->constructor())
71+
{
72+
builder.functionReferenced(*base->constructor());
73+
74+
// Constructors and functions called in state variable initializers have an edge either from
75+
// the previous class in linearized order or from Entry if there's no class before.
76+
builder.m_currentNode = base->constructor();
77+
}
78+
79+
// Functions called from the inheritance specifier should have an edge from the constructor
80+
// for consistency with functions called from constructor modifiers.
81+
for (auto const& inheritanceSpecifier: base->baseContracts())
82+
inheritanceSpecifier->accept(builder);
83+
}
84+
85+
builder.m_currentNode = SpecialNode::Entry;
86+
builder.processQueue();
87+
88+
return move(builder.m_graph);
89+
}
90+
91+
FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildDeployedGraph(
92+
ContractDefinition const& _contract,
93+
FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph
94+
)
95+
{
96+
solAssert(&_creationGraph.contract == &_contract, "");
97+
98+
FunctionCallGraphBuilder builder(_contract);
99+
solAssert(builder.m_currentNode == Node(SpecialNode::Entry), "");
100+
101+
auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); };
102+
103+
// Create graph for all publicly reachable functions
104+
for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | views::transform(getSecondElement))
105+
{
106+
auto const* function = dynamic_cast<FunctionDefinition const*>(&functionType->declaration());
107+
auto const* variable = dynamic_cast<VariableDeclaration const*>(&functionType->declaration());
108+
109+
if (function)
110+
builder.functionReferenced(*function);
111+
else
112+
// If it's not a function, it must be a getter of a public variable; we ignore those
113+
solAssert(variable, "");
114+
}
115+
116+
if (_contract.fallbackFunction())
117+
builder.functionReferenced(*_contract.fallbackFunction());
118+
119+
if (_contract.receiveFunction())
120+
builder.functionReferenced(*_contract.receiveFunction());
121+
122+
// All functions present in internal dispatch at creation time could potentially be pointers
123+
// assigned to state variables and as such may be reachable after deployment as well.
124+
builder.m_currentNode = SpecialNode::InternalDispatch;
125+
for (Node const& dispatchTarget: valueOrDefault(_creationGraph.edges, SpecialNode::InternalDispatch, {}))
126+
{
127+
solAssert(!holds_alternative<SpecialNode>(dispatchTarget), "");
128+
solAssert(get<CallableDeclaration const*>(dispatchTarget) != nullptr, "");
129+
130+
// Visit the callable to add not only it but also everything it calls too
131+
builder.functionReferenced(*get<CallableDeclaration const*>(dispatchTarget), false);
132+
}
133+
134+
builder.m_currentNode = SpecialNode::Entry;
135+
builder.processQueue();
136+
137+
return move(builder.m_graph);
138+
}
139+
140+
bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall)
141+
{
142+
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
143+
return true;
144+
145+
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
146+
solAssert(functionType, "");
147+
148+
if (functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly)
149+
// If it's not a direct call, we don't really know which function will be called (it may even
150+
// change at runtime). All we can do is to add an edge to the dispatch which in turn has
151+
// edges to all functions could possibly be called.
152+
add(m_currentNode, SpecialNode::InternalDispatch);
153+
154+
return true;
155+
}
156+
157+
bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement)
158+
{
159+
auto const* functionType = dynamic_cast<FunctionType const*>(_emitStatement.eventCall().expression().annotation().type);
160+
solAssert(functionType, "");
161+
162+
m_graph.emittedEvents.insert(&dynamic_cast<EventDefinition const&>(functionType->declaration()));
163+
164+
return true;
165+
}
166+
167+
bool FunctionCallGraphBuilder::visit(Identifier const& _identifier)
168+
{
169+
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
170+
{
171+
solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, "");
172+
173+
auto funType = dynamic_cast<FunctionType const*>(_identifier.annotation().type);
174+
175+
// For events kind() == Event, so we have an extra check here
176+
if (funType && funType->kind() == FunctionType::Kind::Internal)
177+
functionReferenced(callable->resolveVirtual(m_graph.contract), _identifier.annotation().calledDirectly);
178+
}
179+
180+
return true;
181+
}
182+
183+
bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
184+
{
185+
auto functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
186+
auto functionDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
187+
if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal)
188+
return true;
189+
190+
// Super functions
191+
if (*_memberAccess.annotation().requiredLookup == VirtualLookup::Super)
192+
{
193+
if (auto const* typeType = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type))
194+
if (auto const contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
195+
{
196+
solAssert(contractType->isSuper(), "");
197+
functionDef = &functionDef->resolveVirtual(
198+
m_graph.contract,
199+
contractType->contractDefinition().superContract(m_graph.contract)
200+
);
201+
}
202+
}
203+
else
204+
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
205+
206+
functionReferenced(*functionDef, _memberAccess.annotation().calledDirectly);
207+
208+
return true;
209+
}
210+
211+
bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation)
212+
{
213+
if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration))
214+
{
215+
VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup;
216+
217+
if (requiredLookup == VirtualLookup::Virtual)
218+
functionReferenced(modifier->resolveVirtual(m_graph.contract));
219+
else
220+
{
221+
solAssert(requiredLookup == VirtualLookup::Static, "");
222+
functionReferenced(*modifier);
223+
}
224+
}
225+
226+
return true;
227+
}
228+
229+
bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression)
230+
{
231+
if (ContractType const* contractType = dynamic_cast<ContractType const*>(_newExpression.typeName().annotation().type))
232+
m_graph.createdContracts.emplace(&contractType->contractDefinition());
233+
234+
return true;
235+
}
236+
237+
void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable)
238+
{
239+
if (!m_graph.edges.count(&_callable))
240+
{
241+
m_visitQueue.push_back(&_callable);
242+
243+
// Insert the callable to the graph (with no edges coming out of it) to mark it as visited.
244+
m_graph.edges.insert({Node(&_callable), {}});
245+
}
246+
}
247+
248+
void FunctionCallGraphBuilder::processQueue()
249+
{
250+
solAssert(m_currentNode == Node(SpecialNode::Entry), "Visit queue is already being processed.");
251+
252+
while (!m_visitQueue.empty())
253+
{
254+
m_currentNode = m_visitQueue.front();
255+
solAssert(holds_alternative<CallableDeclaration const*>(m_currentNode), "");
256+
257+
m_visitQueue.pop_front();
258+
get<CallableDeclaration const*>(m_currentNode)->accept(*this);
259+
}
260+
261+
m_currentNode = SpecialNode::Entry;
262+
}
263+
264+
void FunctionCallGraphBuilder::add(Node _caller, Node _callee)
265+
{
266+
m_graph.edges[_caller].insert(_callee);
267+
}
268+
269+
void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly)
270+
{
271+
if (_calledDirectly)
272+
{
273+
solAssert(
274+
holds_alternative<SpecialNode>(m_currentNode) || m_graph.edges.count(m_currentNode) > 0,
275+
"Adding an edge from a node that has not been visited yet."
276+
);
277+
278+
add(m_currentNode, &_callable);
279+
}
280+
else
281+
add(SpecialNode::InternalDispatch, &_callable);
282+
283+
enqueueCallable(_callable);
284+
}
285+
286+
ostream& solidity::frontend::operator<<(ostream& _out, FunctionCallGraphBuilder::Node const& _node)
287+
{
288+
using SpecialNode = FunctionCallGraphBuilder::SpecialNode;
289+
290+
if (holds_alternative<SpecialNode>(_node))
291+
switch (get<SpecialNode>(_node))
292+
{
293+
case SpecialNode::InternalDispatch:
294+
_out << "InternalDispatch";
295+
break;
296+
case SpecialNode::Entry:
297+
_out << "Entry";
298+
break;
299+
default: solAssert(false, "Invalid SpecialNode type");
300+
}
301+
else
302+
{
303+
solAssert(holds_alternative<CallableDeclaration const*>(_node), "");
304+
305+
auto const* callableDeclaration = get<CallableDeclaration const*>(_node);
306+
solAssert(callableDeclaration, "");
307+
308+
auto const* function = dynamic_cast<FunctionDefinition const *>(callableDeclaration);
309+
auto const* event = dynamic_cast<EventDefinition const *>(callableDeclaration);
310+
auto const* modifier = dynamic_cast<ModifierDefinition const *>(callableDeclaration);
311+
312+
auto typeToString = [](auto const& _var) -> string { return _var->type()->toString(true); };
313+
vector<string> parameters = callableDeclaration->parameters() | views::transform(typeToString) | to<vector<string>>();
314+
315+
string scopeName;
316+
if (!function || !function->isFree())
317+
{
318+
solAssert(callableDeclaration->annotation().scope, "");
319+
auto const* parentContract = dynamic_cast<ContractDefinition const*>(callableDeclaration->annotation().scope);
320+
solAssert(parentContract, "");
321+
scopeName = parentContract->name();
322+
}
323+
324+
if (function && function->isFree())
325+
_out << "function " << function->name() << "(" << joinHumanReadable(parameters, ",") << ")";
326+
else if (function && function->isConstructor())
327+
_out << "constructor of " << scopeName;
328+
else if (function && function->isFallback())
329+
_out << "fallback of " << scopeName;
330+
else if (function && function->isReceive())
331+
_out << "receive of " << scopeName;
332+
else if (function)
333+
_out << "function " << scopeName << "." << function->name() << "(" << joinHumanReadable(parameters, ",") << ")";
334+
else if (event)
335+
_out << "event " << scopeName << "." << event->name() << "(" << joinHumanReadable(parameters, ",") << ")";
336+
else if (modifier)
337+
_out << "modifier " << scopeName << "." << modifier->name();
338+
else
339+
solAssert(false, "Unexpected AST node type in function call graph");
340+
}
341+
342+
return _out;
343+
}

0 commit comments

Comments
 (0)