• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseNode } from './BaseExplicitGraph';
17import { NodeID, GraphTraits } from './GraphTraits';
18
19type NodeSet = Set<NodeID>;
20type NodeStack = NodeID[];
21type Node2RepSCCInfoMap = Map<NodeID, NodeSCCInfo>;
22type Node2NodeMap = Map<NodeID, NodeID>;
23
24/**
25 * Basic SCC info for a single node
26 */
27class NodeSCCInfo {
28    private _rep: NodeID;
29    private _subNodes: NodeSet;
30
31    constructor() {
32        this._rep = Number.MAX_SAFE_INTEGER;
33        this._subNodes = new Set();
34    }
35
36    get rep(): NodeID {
37        return this._rep;
38    }
39
40    set rep(n: NodeID) {
41        this._rep = n;
42    }
43
44    addSubNodes(n: NodeID): void {
45        this._subNodes.add(n);
46    }
47
48    get subNodes(): NodeSet {
49        return this._subNodes;
50    }
51}
52
53/**
54 * Detect strongly connected components in a directed graph
55 * A topological graph is an extra product from this algorithm
56 * Advanced Nuutila’s algorithm which come from the following paper:
57 *   Wave Propagation and Deep Propagation for pointer Analysis
58 *   CGO 2009
59 */
60export class SCCDetection<Graph extends GraphTraits<BaseNode>> {
61    // graph G = (V, E)
62    private _G: Graph;
63    // counter
64    private _I: number;
65    // map of V to {1, . . . , |V |} ∪ ⊥, associates the
66    // nodes in V to the order in which they are visited by
67    // Nuutila’s algorithm. Initially, D(v) = ⊥.
68    private _D: Node2NodeMap;
69    // map of V to V , associates each node in a cycle to
70    // the representative of that cycle. Initially R(v) = v.
71    private _R: Node2RepSCCInfoMap;
72    // stack of V, holds the nodes that are in a cycle, but
73    // have not yet been inserted into C. Initially empty
74    private _S: NodeStack;
75    // stack of V , holds the nodes of V that are represenatives
76    // of strongly connected components. T keeps the
77    // nodes in topological order, that is, the top node has no
78    // predecessors. Initially empty
79    private _T: NodeStack;
80    private repNodes: NodeSet;
81    private visitedNodes: Set<NodeID>;
82    private inSCCNodes: Set<NodeID>;
83
84    constructor(GT: Graph) {
85        this._G = GT;
86        this._I = 0;
87        this._D = new Map<NodeID, NodeID>();
88        this._S = new Array<NodeID>();
89        this._T = new Array<NodeID>();
90        this.repNodes = new Set<NodeID>();
91        this._R = new Map<number, NodeSCCInfo>();
92        this.visitedNodes = new Set();
93        this.inSCCNodes = new Set();
94    }
95
96    private isVisited(n: NodeID): boolean {
97        return this.visitedNodes.has(n);
98    }
99
100    private inSCC(n: NodeID): boolean {
101        return this.inSCCNodes.has(n);
102    }
103
104    private setVisited(n: NodeID): void {
105        this.visitedNodes.add(n);
106    }
107
108    private setInSCC(n: NodeID): void {
109        this.inSCCNodes.add(n);
110    }
111
112    private setRep(n: NodeID, r: NodeID): void {
113        let sccIn = this._R.get(n);
114        if (!sccIn) {
115            sccIn = new NodeSCCInfo();
116            this._R.set(n, sccIn);
117        }
118        sccIn.rep = r;
119
120        let rInfo = this._R.get(r);
121        if (!rInfo) {
122            rInfo = new NodeSCCInfo();
123            this._R.set(r, rInfo);
124        }
125        rInfo.addSubNodes(n);
126        if (n !== r) {
127            sccIn.subNodes.clear();
128            this.repNodes.add(r);
129        }
130    }
131
132    private getRep(n: NodeID): NodeID {
133        let info = this._R.get(n);
134        if (!info) {
135            info = new NodeSCCInfo();
136            this._R.set(n, info);
137        }
138        return info.rep;
139    }
140
141    private getNode(id: NodeID): BaseNode {
142        let n = this._G.getNode(id);
143        if (!n) {
144            throw new Error('Node is not found');
145        }
146        return n;
147    }
148
149    private visit(v: NodeID): void {
150        this._I += 1;
151        this._D.set(v, this._I);
152        this.setRep(v, v);
153        this.setVisited(v);
154
155        let node = this.getNode(v);
156        node.getOutgoingEdges().forEach(e => {
157            let w: NodeID = e.getDstID();
158            if (!this.isVisited(w)) {
159                this.visit(w);
160            }
161
162            if (!this.inSCC(w)) {
163                let repV = this.getRep(v);
164                let repW = this.getRep(w);
165                if (!this._D.has(repV) || !this._D.has(repW)) {
166                    throw new Error('Error happening in SCC detection');
167                }
168                let rep = this._D.get(repV)! < this._D.get(repW)! ? repV : repW;
169                this.setRep(v, rep);
170            }
171        });
172
173        if (this.getRep(v) === v) {
174            this.setInSCC(v);
175            while (this._S.length > 0) {
176                let w = this._S[this._S.length - 1]!;
177                if (this._D.get(w)! <= this._D.get(v)!) {
178                    break;
179                } else {
180                    this._S.pop();
181                    this.setInSCC(w);
182                    this.setRep(w, v);
183                }
184            }
185            this._T.push(v);
186        } else {
187            this._S.push(v);
188        }
189    }
190
191    private clear(): void {
192        this._R.clear();
193        this._I = 0;
194        this._D.clear();
195        this.repNodes.clear();
196        this._S.length = 0;
197        this._T.length = 0;
198        this.inSCCNodes.clear();
199        this.visitedNodes.clear();
200    }
201
202    /**
203     * Get the rep node
204     * If not found return itself
205     */
206    public getRepNode(n: NodeID): NodeID {
207        const it = this._R.get(n);
208        if (!it) {
209            throw new Error('scc rep not found');
210        }
211        const rep = it.rep;
212        return rep !== Number.MAX_SAFE_INTEGER ? rep : n;
213    }
214
215    /**
216     * Start to detect and collapse SCC
217     */
218    public find(): void {
219        this.clear();
220        let nodeIt = this._G.nodesItor();
221        for (let node of nodeIt) {
222            const nodeId: NodeID = node.getID();
223            if (!this.isVisited(nodeId) && !this._D.has(nodeId)) {
224                this.visit(nodeId);
225            }
226        }
227    }
228
229    public getTopoAndCollapsedNodeStack(): NodeStack {
230        return this._T;
231    }
232
233    public getNode2SCCInfoMap(): Node2RepSCCInfoMap {
234        return this._R;
235    }
236
237    // whether the node is in a cycle
238    public nodeIsInCycle(n: NodeID): boolean {
239        const rep = this.getRepNode(n);
240        const subNodesCount = this.getSubNodes(rep).size;
241        // multi-node cycle
242        if (subNodesCount > 1) {
243            return true;
244        }
245        // self-cycle: a call a
246        let repNode = this._G.getNode(rep)!;
247        for (const e of repNode?.getOutgoingEdges()) {
248            if (e.getDstID() === rep) {
249                return true;
250            }
251        }
252        return false;
253    }
254
255    public getMySCCNodes(n: NodeID): NodeSet {
256        const rep = this.getRepNode(n);
257        return this.getSubNodes(rep);
258    }
259
260    // get all subnodes in one scc
261    public getSubNodes(n: NodeID): NodeSet {
262        const it = this._R.get(n);
263        if (!it) {
264            throw new Error('sccInfo not found for a node');
265        }
266        let sub = it.subNodes;
267        if (sub.size === 0) {
268            sub.add(n);
269        }
270
271        return sub;
272    }
273
274    // get all representative nodes
275    public getRepNodes(): NodeSet {
276        return this.repNodes;
277    }
278}
279