Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/CallGraphGenerator.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
 * Specific AST walker that generates the call graph.
20
 */
21
22
#include <libyul/optimiser/CallGraphGenerator.h>
23
24
#include <libyul/AST.h>
25
#include <libyul/Exceptions.h>
26
27
#include <libsolutil/CommonData.h>
28
#include <libsolutil/TarjanSCC.h>
29
#include <libsolutil/Visitor.h>
30
31
#include <range/v3/algorithm/binary_search.hpp>
32
#include <range/v3/algorithm/sort.hpp>
33
#include <range/v3/algorithm/unique.hpp>
34
35
#include <cstddef>
36
37
using namespace solidity;
38
using namespace solidity::yul;
39
using namespace solidity::util;
40
41
namespace
42
{
43
44
class FunctionToIndexBiMapping
45
{
46
public:
47
  explicit FunctionToIndexBiMapping(std::map<FunctionHandle, std::vector<FunctionHandle>> const& _functionCalls)
48
3.68M
  {
49
3.68M
    for (auto const& [function, callees]: _functionCalls)
50
11.5M
    {
51
11.5M
      if (m_functionToIndex.try_emplace(function, m_indexToFunction.size()).second)
52
9.73M
        m_indexToFunction.emplace_back(function);
53
11.5M
      for (auto const& callee: callees)
54
47.4M
        if (m_functionToIndex.try_emplace(callee, m_indexToFunction.size()).second)
55
19.1M
          m_indexToFunction.emplace_back(callee);
56
11.5M
    }
57
3.68M
  }
58
59
  FunctionHandle const& indexToFunction(std::size_t const _index) const
60
28.9M
  {
61
28.9M
    return m_indexToFunction.at(_index);
62
28.9M
  }
63
64
  std::size_t numFunctions() const
65
3.68M
  {
66
3.68M
    return m_indexToFunction.size();
67
3.68M
  }
68
69
  std::size_t functionToIndex(FunctionHandle const& _functionHandle) const
70
94.8M
  {
71
94.8M
    return m_functionToIndex.at(_functionHandle);
72
94.8M
  }
73
74
private:
75
  std::vector<FunctionHandle> m_indexToFunction;
76
  std::map<FunctionHandle, std::size_t> m_functionToIndex;
77
};
78
79
}
80
81
CallGraphCycles CallGraph::analyzeCallCycles() const
82
3.68M
{
83
  // A function is recursive iff it is part of a non-trivial strongly-connected component of the call graph (a
84
  // mutual-recursion cycle of any length) or it directly calls itself. The SCCs are computed with Tarjan's algorithm.
85
  // Tarjan's implementation requires dense node indices in [0, N), so assign each function handle
86
  // (both callers and callees) a consecutive index.
87
3.68M
  FunctionToIndexBiMapping const functionIndexBimap(functionCalls);
88
89
  // Build list of edges in the call graph
90
3.68M
  std::vector<std::vector<std::size_t>> indexBasedAdjacencyList(functionIndexBimap.numFunctions());
91
3.68M
  for (auto const& [function, callees]: functionCalls)
92
11.5M
    for (auto const& callee: callees)
93
47.4M
      indexBasedAdjacencyList[functionIndexBimap.functionToIndex(function)].emplace_back(functionIndexBimap.functionToIndex(callee));
94
95
  // Sort and deduplicate each adjacency list so the self-loop check below can use a binary search
96
3.68M
  for (auto& callees: indexBasedAdjacencyList)
97
28.9M
  {
98
28.9M
    ranges::sort(callees);
99
28.9M
    callees.erase(ranges::unique(callees), callees.end());
100
28.9M
  }
101
102
3.68M
  std::vector<std::vector<FunctionHandle>> components;
103
3.68M
  std::set<FunctionHandle> recursiveFunctionHandleSet;
104
3.68M
  for (std::vector<std::size_t> const& scc: util::computeStronglyConnectedComponents<std::size_t>(indexBasedAdjacencyList))
105
28.7M
  {
106
28.7M
    yulAssert(!scc.empty());
107
28.7M
    std::vector<FunctionHandle>& component = components.emplace_back();
108
28.7M
    for (std::size_t const node: scc)
109
28.9M
      component.emplace_back(functionIndexBimap.indexToFunction(node));
110
28.7M
    if (component.size() > 1)
111
      // more than one element in the SCC: everything in it is mutually recursive
112
35.8k
      recursiveFunctionHandleSet.insert(component.begin(), component.end());
113
28.7M
    else if (ranges::binary_search(indexBasedAdjacencyList[scc.front()], scc.front()))
114
      // self-recursion f -> f
115
145k
      recursiveFunctionHandleSet.insert(component.front());
116
28.7M
  }
117
118
3.68M
  yulAssert(!recursiveFunctionHandleSet.contains(YulName{}), "the top-level block cannot be recursive");
119
3.68M
  for (FunctionHandle const& recursiveFunction: recursiveFunctionHandleSet)
120
3.68M
    yulAssert(std::holds_alternative<YulName>(recursiveFunction), "a builtin cannot be recursive");
121
122
3.68M
  return {
123
3.68M
    .stronglyConnectedComponents = std::move(components),
124
3.68M
    .recursiveFunctions = std::move(recursiveFunctionHandleSet)
125
3.68M
  };
126
3.68M
}
127
128
CallGraph CallGraphGenerator::callGraph(Block const& _ast)
129
5.24M
{
130
5.24M
  CallGraphGenerator gen;
131
5.24M
  gen(_ast);
132
5.24M
  return std::move(gen.m_callGraph);
133
5.24M
}
134
135
void CallGraphGenerator::operator()(FunctionCall const& _functionCall)
136
159M
{
137
159M
  auto& functionCalls = m_callGraph.functionCalls[m_currentFunction];
138
159M
  FunctionHandle identifier = std::visit(GenericVisitor{
139
159M
    [](BuiltinName const& _builtin) -> FunctionHandle { return _builtin.handle; },
140
159M
    [](Identifier const& _identifier) -> FunctionHandle { return _identifier.name; },
141
159M
  }, _functionCall.functionName);
142
159M
  if (!util::contains(functionCalls, identifier))
143
64.1M
    functionCalls.emplace_back(identifier);
144
159M
  ASTWalker::operator()(_functionCall);
145
159M
}
146
147
void CallGraphGenerator::operator()(ForLoop const& _forLoop)
148
9.09M
{
149
9.09M
  m_callGraph.functionsWithLoops.insert(m_currentFunction);
150
9.09M
  ASTWalker::operator()(_forLoop);
151
9.09M
}
152
153
void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition)
154
10.5M
{
155
10.5M
  solRequire(
156
10.5M
    !m_callGraph.functionCalls.contains(_functionDefinition.name),
157
10.5M
    InputNotDisambiguatedException,
158
10.5M
    "CallGraphGenerator requires a disambiguated AST: duplicate function name " + _functionDefinition.name.str() + "."
159
10.5M
  );
160
10.5M
  YulName previousFunction = m_currentFunction;
161
10.5M
  m_currentFunction = _functionDefinition.name;
162
10.5M
  m_callGraph.functionCalls[m_currentFunction] = {};
163
10.5M
  ASTWalker::operator()(_functionDefinition);
164
10.5M
  m_currentFunction = previousFunction;
165
10.5M
}
166
167
CallGraphGenerator::CallGraphGenerator()
168
5.24M
{
169
5.24M
  m_callGraph.functionCalls[YulName{}] = {};
170
5.24M
}