Coverage Report

Created: 2026-10-26 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}