Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsolutil/TarjanSCC.h
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
#pragma once
20
21
#include <liblangutil/Exceptions.h>
22
23
#include <algorithm>
24
#include <concepts>
25
#include <cstddef>
26
#include <limits>
27
#include <vector>
28
29
namespace solidity::util
30
{
31
32
namespace detail
33
{
34
35
/// Callers must supply a dense `[0, N)` remapping; this struct conflates the node's
36
/// identity with its position in `adjacency` for performance.
37
template<std::unsigned_integral NodeIndex>
38
struct TarjanSCC
39
{
40
  static constexpr std::size_t undefined = std::numeric_limits<std::size_t>::max();
41
42
  struct Frame
43
  {
44
    NodeIndex node;
45
    std::size_t childIdx;
46
  };
47
48
  std::vector<std::vector<NodeIndex>> const& adjacency;
49
  /// numbers the nodes consecutively in the order in which they are discovered
50
  std::vector<std::size_t> discoveryIndex;
51
  /// lowlink[v] corresponds to the smallest index of a node reachable through v's DFS subtree
52
  std::vector<std::size_t> lowlink;
53
  std::vector<bool> onStack;
54
  std::vector<NodeIndex> nodeStack;
55
  std::vector<Frame> workStack;
56
  std::size_t nextIndex = 0;
57
  std::vector<std::vector<NodeIndex>> sccs;
58
59
  explicit TarjanSCC(std::vector<std::vector<NodeIndex>> const& _adjacency):
60
3.68M
    adjacency(_adjacency),
61
3.68M
    discoveryIndex(_adjacency.size(), undefined),
62
3.68M
    lowlink(_adjacency.size(), 0),
63
3.68M
    onStack(_adjacency.size(), false)
64
3.68M
  {}
Unexecuted instantiation: solidity::util::detail::TarjanSCC<unsigned int>::TarjanSCC(std::__1::vector<std::__1::vector<unsigned int, std::__1::allocator<unsigned int> >, std::__1::allocator<std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > > > const&)
solidity::util::detail::TarjanSCC<unsigned long>::TarjanSCC(std::__1::vector<std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >, std::__1::allocator<std::__1::vector<unsigned long, std::__1::allocator<unsigned long> > > > const&)
Line
Count
Source
60
3.68M
    adjacency(_adjacency),
61
3.68M
    discoveryIndex(_adjacency.size(), undefined),
62
3.68M
    lowlink(_adjacency.size(), 0),
63
3.68M
    onStack(_adjacency.size(), false)
64
3.68M
  {}
65
66
  void enter(NodeIndex const _v)
67
28.9M
  {
68
28.9M
    solAssert(_v < adjacency.size());
69
28.9M
    discoveryIndex[_v] = nextIndex;
70
28.9M
    lowlink[_v] = nextIndex;
71
28.9M
    ++nextIndex;
72
28.9M
    nodeStack.push_back(_v);
73
28.9M
    onStack[_v] = true;
74
28.9M
    workStack.push_back({_v, 0});
75
28.9M
  }
Unexecuted instantiation: solidity::util::detail::TarjanSCC<unsigned int>::enter(unsigned int)
solidity::util::detail::TarjanSCC<unsigned long>::enter(unsigned long)
Line
Count
Source
67
28.9M
  {
68
    solAssert(_v < adjacency.size());
69
28.9M
    discoveryIndex[_v] = nextIndex;
70
28.9M
    lowlink[_v] = nextIndex;
71
28.9M
    ++nextIndex;
72
28.9M
    nodeStack.push_back(_v);
73
28.9M
    onStack[_v] = true;
74
28.9M
    workStack.push_back({_v, 0});
75
28.9M
  }
76
77
  void emitSCC(NodeIndex const _root)
78
28.7M
  {
79
28.7M
    solAssert(!nodeStack.empty());
80
28.7M
    std::vector<NodeIndex> scc;
81
28.7M
    NodeIndex w;
82
28.7M
    do
83
28.9M
    {
84
28.9M
      w = nodeStack.back();
85
28.9M
      nodeStack.pop_back();
86
28.9M
      onStack[w] = false;
87
28.9M
      scc.push_back(w);
88
28.9M
    }
89
28.9M
    while (w != _root);
90
28.7M
    sccs.push_back(std::move(scc));
91
28.7M
  }
Unexecuted instantiation: solidity::util::detail::TarjanSCC<unsigned int>::emitSCC(unsigned int)
solidity::util::detail::TarjanSCC<unsigned long>::emitSCC(unsigned long)
Line
Count
Source
78
28.7M
  {
79
28.7M
    solAssert(!nodeStack.empty());
80
28.7M
    std::vector<NodeIndex> scc;
81
28.7M
    NodeIndex w;
82
28.7M
    do
83
28.9M
    {
84
28.9M
      w = nodeStack.back();
85
28.9M
      nodeStack.pop_back();
86
28.9M
      onStack[w] = false;
87
28.9M
      scc.push_back(w);
88
28.9M
    }
89
28.9M
    while (w != _root);
90
28.7M
    sccs.push_back(std::move(scc));
91
28.7M
  }
92
93
  std::vector<std::vector<NodeIndex>> run() &&
94
3.68M
  {
95
32.6M
    for (NodeIndex root = 0; root < discoveryIndex.size(); ++root)
96
28.9M
    {
97
28.9M
      if (discoveryIndex[root] != undefined)
98
19.6M
        continue;
99
100
      // start new DFS tree
101
9.31M
      enter(root);
102
85.6M
      while (!workStack.empty())
103
76.3M
      {
104
76.3M
        NodeIndex const v = workStack.back().node;
105
76.3M
        std::size_t const childIdx = workStack.back().childIdx;
106
76.3M
        if (childIdx < adjacency[v].size())
107
47.4M
        {
108
          // process next outgoing edge
109
47.4M
          NodeIndex const w = adjacency[v][childIdx];
110
          // advance v's currently handled edge before pushing w, so v resumes at the next child when w finishes
111
47.4M
          workStack.back().childIdx = childIdx + 1;
112
47.4M
          if (discoveryIndex[w] == undefined)
113
            // Successor w has not yet been visited; recurse on it
114
19.6M
            enter(w);
115
27.7M
          else if (onStack[w])
116
            // Successor w is in stack S and hence in the current SCC
117
470k
            lowlink[v] = std::min(lowlink[v], discoveryIndex[w]);
118
47.4M
        }
119
28.9M
        else
120
28.9M
        {
121
          // if v is a root node
122
28.9M
          if (lowlink[v] == discoveryIndex[v])
123
            // generate an SCC and pop the node stack
124
28.7M
            emitSCC(v);
125
126
          // pop v and propagate its lowlink to the parent (post-recursion update)
127
28.9M
          workStack.pop_back();
128
28.9M
          if (!workStack.empty())
129
19.6M
          {
130
19.6M
            NodeIndex const parent = workStack.back().node;
131
19.6M
            lowlink[parent] = std::min(lowlink[parent], lowlink[v]);
132
19.6M
          }
133
28.9M
        }
134
76.3M
      }
135
9.31M
    }
136
3.68M
    return std::move(sccs);
137
3.68M
  }
Unexecuted instantiation: solidity::util::detail::TarjanSCC<unsigned int>::run() &&
solidity::util::detail::TarjanSCC<unsigned long>::run() &&
Line
Count
Source
94
3.68M
  {
95
32.6M
    for (NodeIndex root = 0; root < discoveryIndex.size(); ++root)
96
28.9M
    {
97
28.9M
      if (discoveryIndex[root] != undefined)
98
19.6M
        continue;
99
100
      // start new DFS tree
101
9.31M
      enter(root);
102
85.6M
      while (!workStack.empty())
103
76.3M
      {
104
76.3M
        NodeIndex const v = workStack.back().node;
105
76.3M
        std::size_t const childIdx = workStack.back().childIdx;
106
76.3M
        if (childIdx < adjacency[v].size())
107
47.4M
        {
108
          // process next outgoing edge
109
47.4M
          NodeIndex const w = adjacency[v][childIdx];
110
          // advance v's currently handled edge before pushing w, so v resumes at the next child when w finishes
111
47.4M
          workStack.back().childIdx = childIdx + 1;
112
47.4M
          if (discoveryIndex[w] == undefined)
113
            // Successor w has not yet been visited; recurse on it
114
19.6M
            enter(w);
115
27.7M
          else if (onStack[w])
116
            // Successor w is in stack S and hence in the current SCC
117
470k
            lowlink[v] = std::min(lowlink[v], discoveryIndex[w]);
118
47.4M
        }
119
28.9M
        else
120
28.9M
        {
121
          // if v is a root node
122
28.9M
          if (lowlink[v] == discoveryIndex[v])
123
            // generate an SCC and pop the node stack
124
28.7M
            emitSCC(v);
125
126
          // pop v and propagate its lowlink to the parent (post-recursion update)
127
28.9M
          workStack.pop_back();
128
28.9M
          if (!workStack.empty())
129
19.6M
          {
130
19.6M
            NodeIndex const parent = workStack.back().node;
131
19.6M
            lowlink[parent] = std::min(lowlink[parent], lowlink[v]);
132
19.6M
          }
133
28.9M
        }
134
76.3M
      }
135
9.31M
    }
136
3.68M
    return std::move(sccs);
137
3.68M
  }
138
};
139
140
}
141
142
/// Tarjan's strongly-connected-components algorithm.
143
/// Takes an adjacency list where `_adjacency[v]` is the list of successors of node `v`. Node IDs must lie in `[0, _adjacency.size())`.
144
/// Implementation based on the Wikipedia pseudocode.
145
///
146
/// Wikipedia contributors, "Tarjan's strongly connected components algorithm," Wikipedia, The Free Encyclopedia,
147
///   https://en.wikipedia.org/w/index.php?title=Tarjan%27s_strongly_connected_components_algorithm&oldid=1341352351
148
/// Tarjan, R.E., "Depth-First Search and Linear Graph Algorithms", https://doi.org/10.1137/0201010
149
template<std::unsigned_integral NodeID>
150
std::vector<std::vector<NodeID>> computeStronglyConnectedComponents(std::vector<std::vector<NodeID>> const& _adjacency)
151
3.68M
{
152
3.68M
  return detail::TarjanSCC<NodeID>(_adjacency).run();
153
3.68M
}
Unexecuted instantiation: _ZN8solidity4util34computeStronglyConnectedComponentsITkNSt3__117unsigned_integralEjEENS2_6vectorINS3_IT_NS2_9allocatorIS4_EEEENS5_IS7_EEEERKS9_
_ZN8solidity4util34computeStronglyConnectedComponentsITkNSt3__117unsigned_integralEmEENS2_6vectorINS3_IT_NS2_9allocatorIS4_EEEENS5_IS7_EEEERKS9_
Line
Count
Source
151
3.68M
{
152
3.68M
  return detail::TarjanSCC<NodeID>(_adjacency).run();
153
3.68M
}
154
155
}