/src/solidity/libsolidity/analysis/FunctionCallGraph.cpp
Line | Count | Source |
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 solidity::frontend; |
28 | | using namespace solidity::util; |
29 | | |
30 | | CallGraph FunctionCallGraphBuilder::buildCreationGraph(ContractDefinition const& _contract) |
31 | 19.1k | { |
32 | 19.1k | FunctionCallGraphBuilder builder(_contract); |
33 | 19.1k | solAssert(builder.m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), ""); |
34 | | |
35 | | // Create graph for constructor, state vars, etc |
36 | 19.1k | for (ContractDefinition const* base: _contract.annotation().linearizedBaseContracts | ranges::views::reverse) |
37 | 21.2k | { |
38 | | // The constructor and functions called in state variable initial assignments should have |
39 | | // an edge from Entry |
40 | 21.2k | builder.m_currentNode = CallGraph::SpecialNode::Entry; |
41 | 21.2k | for (auto const* stateVar: base->stateVariables()) |
42 | 10.8k | if (!stateVar->isConstant()) |
43 | 10.3k | stateVar->accept(builder); |
44 | | |
45 | 21.2k | if (base->constructor()) |
46 | 2.60k | { |
47 | 2.60k | builder.functionReferenced(*base->constructor()); |
48 | | |
49 | | // Constructors and functions called in state variable initializers have an edge either from |
50 | | // the previous class in linearized order or from Entry if there's no class before. |
51 | 2.60k | builder.m_currentNode = base->constructor(); |
52 | 2.60k | } |
53 | | |
54 | | // Functions called from the inheritance specifier should have an edge from the constructor |
55 | | // for consistency with functions called from constructor modifiers. |
56 | 21.2k | for (auto const& inheritanceSpecifier: base->baseContracts()) |
57 | 2.18k | inheritanceSpecifier->accept(builder); |
58 | 21.2k | } |
59 | | |
60 | 19.1k | builder.m_currentNode = CallGraph::SpecialNode::Entry; |
61 | 19.1k | builder.processQueue(); |
62 | | |
63 | 19.1k | return std::move(builder.m_graph); |
64 | 19.1k | } |
65 | | |
66 | | CallGraph FunctionCallGraphBuilder::buildDeployedGraph( |
67 | | ContractDefinition const& _contract, |
68 | | CallGraph const& _creationGraph |
69 | | ) |
70 | 19.1k | { |
71 | 19.1k | FunctionCallGraphBuilder builder(_contract); |
72 | 19.1k | solAssert(builder.m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), ""); |
73 | | |
74 | 20.6k | auto getSecondElement = [](auto const& _tuple){ return std::get<1>(_tuple); }; |
75 | | |
76 | | // Create graph for all publicly reachable functions |
77 | 19.1k | for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | ranges::views::transform(getSecondElement)) |
78 | 20.6k | { |
79 | 20.6k | auto const* function = dynamic_cast<FunctionDefinition const*>(&functionType->declaration()); |
80 | 20.6k | auto const* variable = dynamic_cast<VariableDeclaration const*>(&functionType->declaration()); |
81 | | |
82 | 20.6k | if (function) |
83 | 16.9k | builder.functionReferenced(*function); |
84 | 3.68k | else |
85 | | // If it's not a function, it must be a getter of a public variable; we ignore those |
86 | 20.6k | solAssert(variable, ""); |
87 | 20.6k | } |
88 | | |
89 | 19.1k | if (_contract.fallbackFunction()) |
90 | 199 | builder.functionReferenced(*_contract.fallbackFunction()); |
91 | | |
92 | 19.1k | if (_contract.receiveFunction()) |
93 | 86 | builder.functionReferenced(*_contract.receiveFunction()); |
94 | | |
95 | | // All functions present in internal dispatch at creation time could potentially be pointers |
96 | | // assigned to state variables and as such may be reachable after deployment as well. |
97 | 19.1k | builder.m_currentNode = CallGraph::SpecialNode::InternalDispatch; |
98 | 19.1k | std::set<CallGraph::Node, CallGraph::CompareByID> defaultNode; |
99 | 19.1k | for (CallGraph::Node const& dispatchTarget: util::valueOrDefault(_creationGraph.edges, CallGraph::SpecialNode::InternalDispatch, defaultNode)) |
100 | 60 | { |
101 | 60 | solAssert(!std::holds_alternative<CallGraph::SpecialNode>(dispatchTarget), ""); |
102 | 60 | solAssert(std::get<CallableDeclaration const*>(dispatchTarget) != nullptr, ""); |
103 | | |
104 | | // Visit the callable to add not only it but also everything it calls too |
105 | 60 | builder.functionReferenced(*std::get<CallableDeclaration const*>(dispatchTarget), false); |
106 | 60 | } |
107 | | |
108 | 19.1k | builder.m_currentNode = CallGraph::SpecialNode::Entry; |
109 | 19.1k | builder.processQueue(); |
110 | | |
111 | 19.1k | return std::move(builder.m_graph); |
112 | 19.1k | } |
113 | | |
114 | | bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall) |
115 | 52.3k | { |
116 | 52.3k | if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) |
117 | 17.5k | return true; |
118 | | |
119 | 34.7k | auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type); |
120 | 34.7k | solAssert(functionType, ""); |
121 | | |
122 | 34.7k | if (functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly) |
123 | | // If it's not a direct call, we don't really know which function will be called (it may even |
124 | | // change at runtime). All we can do is to add an edge to the dispatch which in turn has |
125 | | // edges to all functions could possibly be called. |
126 | 195 | add(m_currentNode, CallGraph::SpecialNode::InternalDispatch); |
127 | 34.6k | else if (functionType->kind() == FunctionType::Kind::Error) |
128 | 59 | m_graph.usedErrors.insert(&dynamic_cast<ErrorDefinition const&>(functionType->declaration())); |
129 | | |
130 | 34.7k | return true; |
131 | 52.3k | } |
132 | | |
133 | | bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement) |
134 | 1.18k | { |
135 | 1.18k | auto const* functionType = dynamic_cast<FunctionType const*>(_emitStatement.eventCall().expression().annotation().type); |
136 | 1.18k | solAssert(functionType, ""); |
137 | | |
138 | 1.18k | m_graph.emittedEvents.insert(&dynamic_cast<EventDefinition const&>(functionType->declaration())); |
139 | | |
140 | 1.18k | return true; |
141 | 1.18k | } |
142 | | |
143 | | bool FunctionCallGraphBuilder::visit(Identifier const& _identifier) |
144 | 221k | { |
145 | 221k | if (auto const* variable = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) |
146 | 193k | { |
147 | 193k | if (variable->isConstant()) |
148 | 2.27k | { |
149 | 2.27k | solAssert(variable->isStateVariable() || variable->isFileLevelVariable(), ""); |
150 | 2.27k | variable->accept(*this); |
151 | 2.27k | } |
152 | 193k | } |
153 | 28.0k | else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration)) |
154 | 15.7k | { |
155 | 15.7k | solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, ""); |
156 | | |
157 | 15.7k | auto funType = dynamic_cast<FunctionType const*>(_identifier.annotation().type); |
158 | | |
159 | | // For events kind() == Event, so we have an extra check here |
160 | 15.7k | if (funType && funType->kind() == FunctionType::Kind::Internal) |
161 | 14.5k | functionReferenced(callable->resolveVirtual(m_contract), _identifier.annotation().calledDirectly); |
162 | 15.7k | } |
163 | | |
164 | 221k | return true; |
165 | 221k | } |
166 | | |
167 | | bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess) |
168 | 307k | { |
169 | 307k | Type const* exprType = _memberAccess.expression().annotation().type; |
170 | 307k | ASTString const& memberName = _memberAccess.memberName(); |
171 | | |
172 | 307k | if (auto magicType = dynamic_cast<MagicType const*>(exprType)) |
173 | 1.91k | if (magicType->kind() == MagicType::Kind::MetaType && ( |
174 | 159 | memberName == "creationCode" || memberName == "runtimeCode" |
175 | 159 | )) |
176 | 22 | { |
177 | 22 | ContractType const& accessedContractType = dynamic_cast<ContractType const&>(*magicType->typeArgument()); |
178 | 22 | m_graph.bytecodeDependency.emplace(&accessedContractType.contractDefinition(), &_memberAccess); |
179 | 22 | } |
180 | | |
181 | 307k | auto functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type); |
182 | 307k | auto functionDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration); |
183 | 307k | if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal) |
184 | 306k | return true; |
185 | | |
186 | | // Super functions |
187 | 338 | if (*_memberAccess.annotation().requiredLookup == VirtualLookup::Super) |
188 | 65 | { |
189 | 65 | if (auto const* typeType = dynamic_cast<TypeType const*>(exprType)) |
190 | 65 | if (auto const contractType = dynamic_cast<ContractType const*>(typeType->actualType())) |
191 | 65 | { |
192 | 65 | solAssert(contractType->isSuper(), ""); |
193 | 65 | functionDef = &functionDef->resolveVirtual( |
194 | 65 | m_contract, |
195 | 65 | contractType->contractDefinition().superContract(m_contract) |
196 | 65 | ); |
197 | 65 | } |
198 | 65 | } |
199 | 273 | else |
200 | 338 | solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); |
201 | | |
202 | 338 | functionReferenced(*functionDef, _memberAccess.annotation().calledDirectly); |
203 | 338 | return true; |
204 | 307k | } |
205 | | |
206 | | bool FunctionCallGraphBuilder::visit(BinaryOperation const& _binaryOperation) |
207 | 95.7k | { |
208 | 95.7k | if (*_binaryOperation.annotation().userDefinedFunction != nullptr) |
209 | 32 | functionReferenced(**_binaryOperation.annotation().userDefinedFunction, true /* called directly */); |
210 | 95.7k | return true; |
211 | 95.7k | } |
212 | | |
213 | | bool FunctionCallGraphBuilder::visit(UnaryOperation const& _unaryOperation) |
214 | 28.4k | { |
215 | 28.4k | if (*_unaryOperation.annotation().userDefinedFunction != nullptr) |
216 | 19 | functionReferenced(**_unaryOperation.annotation().userDefinedFunction, true /* called directly */); |
217 | 28.4k | return true; |
218 | 28.4k | } |
219 | | |
220 | | bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation) |
221 | 745 | { |
222 | 745 | if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration)) |
223 | 462 | { |
224 | 462 | VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup; |
225 | | |
226 | 462 | if (requiredLookup == VirtualLookup::Virtual) |
227 | 455 | functionReferenced(modifier->resolveVirtual(m_contract)); |
228 | 7 | else |
229 | 7 | { |
230 | 7 | solAssert(requiredLookup == VirtualLookup::Static, ""); |
231 | 7 | functionReferenced(*modifier); |
232 | 7 | } |
233 | 462 | } |
234 | | |
235 | 745 | return true; |
236 | 745 | } |
237 | | |
238 | | bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression) |
239 | 4.45k | { |
240 | 4.45k | if (ContractType const* contractType = dynamic_cast<ContractType const*>(_newExpression.typeName().annotation().type)) |
241 | 345 | m_graph.bytecodeDependency.emplace(&contractType->contractDefinition(), &_newExpression); |
242 | | |
243 | 4.45k | return true; |
244 | 4.45k | } |
245 | | |
246 | | void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable) |
247 | 35.2k | { |
248 | 35.2k | if (!m_graph.edges.count(&_callable)) |
249 | 22.3k | { |
250 | 22.3k | m_visitQueue.push_back(&_callable); |
251 | | |
252 | | // Insert the callable to the graph (with no edges coming out of it) to mark it as visited. |
253 | 22.3k | m_graph.edges.insert({CallGraph::Node(&_callable), {}}); |
254 | 22.3k | } |
255 | 35.2k | } |
256 | | |
257 | | void FunctionCallGraphBuilder::processQueue() |
258 | 38.3k | { |
259 | 38.3k | solAssert(m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), "Visit queue is already being processed."); |
260 | | |
261 | 60.7k | while (!m_visitQueue.empty()) |
262 | 22.3k | { |
263 | 22.3k | m_currentNode = m_visitQueue.front(); |
264 | 22.3k | solAssert(std::holds_alternative<CallableDeclaration const*>(m_currentNode), ""); |
265 | | |
266 | 22.3k | m_visitQueue.pop_front(); |
267 | 22.3k | std::get<CallableDeclaration const*>(m_currentNode)->accept(*this); |
268 | 22.3k | } |
269 | | |
270 | 38.3k | m_currentNode = CallGraph::SpecialNode::Entry; |
271 | 38.3k | } |
272 | | |
273 | | void FunctionCallGraphBuilder::add(CallGraph::Node _caller, CallGraph::Node _callee) |
274 | 35.4k | { |
275 | 35.4k | m_graph.edges[_caller].insert(_callee); |
276 | 35.4k | } |
277 | | |
278 | | void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly) |
279 | 35.2k | { |
280 | 35.2k | if (_calledDirectly) |
281 | 34.9k | { |
282 | 34.9k | solAssert( |
283 | 34.9k | std::holds_alternative<CallGraph::SpecialNode>(m_currentNode) || m_graph.edges.count(m_currentNode) > 0, |
284 | 34.9k | "Adding an edge from a node that has not been visited yet." |
285 | 34.9k | ); |
286 | | |
287 | 34.9k | add(m_currentNode, &_callable); |
288 | 34.9k | } |
289 | 343 | else |
290 | 343 | add(CallGraph::SpecialNode::InternalDispatch, &_callable); |
291 | | |
292 | 35.2k | enqueueCallable(_callable); |
293 | 35.2k | } |
294 | | |
295 | | std::ostream& solidity::frontend::operator<<(std::ostream& _out, CallGraph::Node const& _node) |
296 | 0 | { |
297 | 0 | if (std::holds_alternative<CallGraph::SpecialNode>(_node)) |
298 | 0 | switch (std::get<CallGraph::SpecialNode>(_node)) |
299 | 0 | { |
300 | 0 | case CallGraph::SpecialNode::InternalDispatch: |
301 | 0 | _out << "InternalDispatch"; |
302 | 0 | break; |
303 | 0 | case CallGraph::SpecialNode::Entry: |
304 | 0 | _out << "Entry"; |
305 | 0 | break; |
306 | 0 | default: |
307 | 0 | solAssert(false, "Invalid SpecialNode type"); |
308 | 0 | } |
309 | 0 | else |
310 | 0 | { |
311 | 0 | solAssert(std::holds_alternative<CallableDeclaration const*>(_node), ""); |
312 | |
|
313 | 0 | auto const* callableDeclaration = std::get<CallableDeclaration const*>(_node); |
314 | 0 | solAssert(callableDeclaration, ""); |
315 | |
|
316 | 0 | auto const* function = dynamic_cast<FunctionDefinition const *>(callableDeclaration); |
317 | 0 | auto const* event = dynamic_cast<EventDefinition const *>(callableDeclaration); |
318 | 0 | auto const* modifier = dynamic_cast<ModifierDefinition const *>(callableDeclaration); |
319 | |
|
320 | 0 | auto typeToString = [](auto const& _var) -> std::string { return _var->type()->toString(true); }; |
321 | 0 | std::vector<std::string> parameters = callableDeclaration->parameters() | ranges::views::transform(typeToString) | ranges::to<std::vector<std::string>>(); |
322 | |
|
323 | 0 | std::string scopeName; |
324 | 0 | if (!function || !function->isFree()) |
325 | 0 | { |
326 | 0 | solAssert(callableDeclaration->annotation().scope, ""); |
327 | 0 | auto const* parentContract = dynamic_cast<ContractDefinition const*>(callableDeclaration->annotation().scope); |
328 | 0 | solAssert(parentContract, ""); |
329 | 0 | scopeName = parentContract->name(); |
330 | 0 | } |
331 | |
|
332 | 0 | if (function && function->isFree()) |
333 | 0 | _out << "function " << function->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
334 | 0 | else if (function && function->isConstructor()) |
335 | 0 | _out << "constructor of " << scopeName; |
336 | 0 | else if (function && function->isFallback()) |
337 | 0 | _out << "fallback of " << scopeName; |
338 | 0 | else if (function && function->isReceive()) |
339 | 0 | _out << "receive of " << scopeName; |
340 | 0 | else if (function) |
341 | 0 | _out << "function " << scopeName << "." << function->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
342 | 0 | else if (event) |
343 | 0 | _out << "event " << scopeName << "." << event->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
344 | 0 | else if (modifier) |
345 | 0 | _out << "modifier " << scopeName << "." << modifier->name(); |
346 | 0 | else |
347 | 0 | solAssert(false, "Unexpected AST node type in function call graph"); |
348 | 0 | } |
349 | | |
350 | 0 | return _out; |
351 | 0 | } |