• 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 { MethodSignature } from '../../core/model/ArkSignature';
17import { Stmt } from '../../core/base/Stmt';
18import { Scene } from '../../Scene';
19import { ArkMethod } from '../../core/model/ArkMethod';
20import { GraphPrinter } from '../../save/GraphPrinter';
21import { PrinterBuilder } from '../../save/PrinterBuilder';
22import { BaseEdge, BaseNode, BaseExplicitGraph, NodeID } from '../../core/graph/BaseExplicitGraph';
23import { CGStat } from '../common/Statistics';
24import { UNKNOWN_FILE_NAME } from '../../core/common/Const';
25import { CallSite, CallSiteID, DynCallSite, ICallSite } from './CallSite';
26
27export type Method = MethodSignature;
28export type FuncID = number;
29type StmtSet = Set<Stmt>;
30
31export { CallSite, DynCallSite, ICallSite };
32
33export enum CallGraphNodeKind {
34    real, // method from project and has body
35    vitual,
36    intrinsic, // method created by AA, which arkMethod.isGenrated is true
37    constructor, // constructor
38    blank, // method without body
39}
40
41export class CallGraphEdge extends BaseEdge {
42    private directCalls: StmtSet = new Set();
43    private specialCalls: StmtSet = new Set();
44    private indirectCalls: StmtSet = new Set();
45    // private callSiteID: CallSiteID;
46
47    constructor(src: CallGraphNode, dst: CallGraphNode) {
48        super(src, dst, 0);
49    }
50
51    public addDirectCallSite(stmt: Stmt): void {
52        this.directCalls.add(stmt);
53    }
54
55    public addSpecialCallSite(stmt: Stmt): void {
56        this.specialCalls.add(stmt);
57    }
58
59    public addInDirectCallSite(stmt: Stmt): void {
60        this.indirectCalls.add(stmt);
61    }
62
63    public getDotAttr(): string {
64        const indirectCallNums: number = this.indirectCalls.size;
65        const directCallNums: number = this.directCalls.size;
66        const specialCallNums: number = this.specialCalls.size;
67        if ([CallGraphNodeKind.intrinsic, CallGraphNodeKind.constructor].includes(this.getDstNode().getKind())) {
68            return '';
69        }
70
71        if (indirectCallNums !== 0 && directCallNums === 0) {
72            return 'color=red';
73        } else if (specialCallNums !== 0) {
74            return 'color=yellow';
75        } else if (indirectCallNums === 0 && directCallNums !== 0) {
76            return 'color=black';
77        } else {
78            return 'color=black';
79        }
80    }
81}
82
83export class CallGraphNode extends BaseNode {
84    private method: Method;
85    private ifSdkMethod: boolean = false;
86
87    constructor(id: number, m: Method, k: CallGraphNodeKind = CallGraphNodeKind.real) {
88        super(id, k);
89        this.method = m;
90    }
91
92    public getMethod(): Method {
93        return this.method;
94    }
95
96    public setSdkMethod(v: boolean): void {
97        this.ifSdkMethod = v;
98    }
99
100    public isSdkMethod(): boolean {
101        return this.ifSdkMethod;
102    }
103
104    public get isBlankMethod(): boolean {
105        return this.kind === CallGraphNodeKind.blank;
106    }
107
108    public getDotAttr(): string {
109        if ([CallGraphNodeKind.intrinsic, CallGraphNodeKind.constructor].includes(this.getKind())) {
110            return '';
111        }
112        return 'shape=box';
113    }
114
115    public getDotLabel(): string {
116        let label: string = 'ID: ' + this.getID() + '\n';
117        label = label + this.getMethod().toString();
118        return label;
119    }
120}
121
122export class CallGraph extends BaseExplicitGraph {
123    private scene: Scene;
124    private idToCallSiteMap: Map<CallSiteID, CallSite> = new Map();
125    private callSiteToIdMap: Map<CallSite, CallSiteID> = new Map();
126    private stmtToCallSitemap: Map<Stmt, CallSite[]> = new Map();
127    private stmtToDynCallSitemap: Map<Stmt, DynCallSite> = new Map();
128    private methodToCGNodeMap: Map<string, NodeID> = new Map();
129    private callPairToEdgeMap: Map<string, CallGraphEdge> = new Map();
130    private methodToCallSiteMap: Map<FuncID, Set<CallSite>> = new Map();
131    private callSiteNum: number = 0;
132    private entries!: NodeID[];
133    private cgStat: CGStat;
134    private dummyMainMethodID: FuncID | undefined;
135
136    constructor(s: Scene) {
137        super();
138        this.scene = s;
139        this.cgStat = new CGStat();
140    }
141
142    private getCallPairString(srcID: NodeID, dstID: NodeID): string {
143        return `${srcID}-${dstID}`;
144    }
145
146    public getCallEdgeByPair(srcID: NodeID, dstID: NodeID): CallGraphEdge | undefined {
147        let key: string = this.getCallPairString(srcID, dstID);
148        return this.callPairToEdgeMap.get(key);
149    }
150
151    public addCallGraphNode(method: Method, kind: CallGraphNodeKind = CallGraphNodeKind.real): CallGraphNode {
152        let id: NodeID = this.nodeNum;
153        let cgNode = new CallGraphNode(id, method, kind);
154        // check if sdk method
155        cgNode.setSdkMethod(this.scene.hasSdkFile(method.getDeclaringClassSignature().getDeclaringFileSignature()));
156
157        this.addNode(cgNode);
158        this.methodToCGNodeMap.set(method.toString(), cgNode.getID());
159        this.cgStat.addNodeStat(kind);
160        return cgNode;
161    }
162
163    public removeCallGraphNode(nodeID: NodeID): void {
164        // remove edge relate to node first
165        this.removeCallGraphEdge(nodeID);
166        let node = this.getNode(nodeID) as CallGraphNode;
167        // remove node itself
168        this.removeNode(nodeID);
169        this.methodToCGNodeMap.delete(node.getMethod().toString());
170    }
171
172    public getCallGraphNodeByMethod(method: Method): CallGraphNode {
173        if (!method) {
174            throw new Error();
175        }
176        let n = this.methodToCGNodeMap.get(method.toString());
177        if (n === undefined) {
178            // The method can't be found
179            // means the method has no implementation, or base type is unclear to find it
180            // Create a virtual CG Node
181            // TODO: this virtual CG Node need be remove once the base type is clear
182            return this.addCallGraphNode(method, CallGraphNodeKind.vitual);
183        }
184
185        return this.getNode(n) as CallGraphNode;
186    }
187
188    public addDirectOrSpecialCallEdge(caller: Method, callee: Method, callStmt: Stmt, isDirectCall: boolean = true): void {
189        let callerNode = this.getCallGraphNodeByMethod(caller) as CallGraphNode;
190        let calleeNode = this.getCallGraphNodeByMethod(callee) as CallGraphNode;
191        let args = callStmt.getInvokeExpr()?.getArgs();
192
193        let cs: CallSite = new CallSite(callStmt, args, calleeNode.getID(), callerNode.getID());
194        let csID: CallSiteID;
195        if (!this.callSiteToIdMap.has(cs)) {
196            csID = this.callSiteNum++;
197            this.idToCallSiteMap.set(csID, cs);
198            this.callSiteToIdMap.set(cs, csID);
199        } else {
200            csID = this.callSiteToIdMap.get(cs) as CallSiteID;
201        }
202
203        if (this.addStmtToCallSiteMap(callStmt, cs)) {
204            // TODO: check stmt exists
205        }
206
207        // TODO: check if edge exists
208        let callEdge = this.getCallEdgeByPair(callerNode.getID(), calleeNode.getID());
209        if (callEdge === undefined) {
210            callEdge = new CallGraphEdge(callerNode, calleeNode);
211            callEdge.getSrcNode().addOutgoingEdge(callEdge);
212            callEdge.getDstNode().addIncomingEdge(callEdge);
213            this.callPairToEdgeMap.set(this.getCallPairString(callerNode.getID(), calleeNode.getID()), callEdge);
214        }
215        if (isDirectCall) {
216            callEdge.addDirectCallSite(callStmt);
217        } else {
218            callEdge.addSpecialCallSite(callStmt);
219        }
220    }
221
222    public removeCallGraphEdge(nodeID: NodeID): void {
223        let node = this.getNode(nodeID) as CallGraphNode;
224
225        for (const inEdge of node.getIncomingEdge()) {
226            node.removeIncomingEdge(inEdge);
227        }
228
229        for (const outEdge of node.getOutgoingEdges()) {
230            node.removeIncomingEdge(outEdge);
231        }
232    }
233
234    public addDynamicCallInfo(callStmt: Stmt, caller: Method, protentialCallee?: Method): void {
235        let callerNode = this.getCallGraphNodeByMethod(caller) as CallGraphNode;
236        let calleeNode;
237        if (protentialCallee) {
238            calleeNode = this.getCallGraphNodeByMethod(protentialCallee) as CallGraphNode;
239        }
240        let args = callStmt.getInvokeExpr()?.getArgs();
241
242        let cs = new DynCallSite(callStmt, args, calleeNode?.getID(), callerNode.getID());
243        this.stmtToDynCallSitemap.set(callStmt, cs);
244    }
245
246    public addDynamicCallEdge(callerID: NodeID, calleeID: NodeID, callStmt: Stmt): void {
247        let callerNode = this.getNode(callerID) as CallGraphNode;
248        let calleeNode = this.getNode(calleeID) as CallGraphNode;
249
250        let callEdge = this.getCallEdgeByPair(callerNode.getID(), calleeNode.getID());
251        if (callEdge === undefined) {
252            callEdge = new CallGraphEdge(callerNode, calleeNode);
253            callEdge.getSrcNode().addOutgoingEdge(callEdge);
254            callEdge.getDstNode().addIncomingEdge(callEdge);
255            this.callPairToEdgeMap.set(this.getCallPairString(callerNode.getID(), calleeNode.getID()), callEdge);
256        }
257        callEdge.addInDirectCallSite(callStmt);
258    }
259
260    public getDynCallsiteByStmt(stmt: Stmt): DynCallSite | undefined {
261        return this.stmtToDynCallSitemap.get(stmt);
262    }
263
264    public addStmtToCallSiteMap(stmt: Stmt, cs: CallSite): boolean {
265        if (this.stmtToCallSitemap.has(stmt)) {
266            let callSites = this.stmtToCallSitemap.get(stmt) ?? [];
267            this.stmtToCallSitemap.set(stmt, [...callSites, cs]);
268            return false;
269        }
270        this.stmtToCallSitemap.set(stmt, [cs]);
271        return true;
272    }
273
274    public getCallSiteByStmt(stmt: Stmt): CallSite[] {
275        return this.stmtToCallSitemap.get(stmt) ?? [];
276    }
277
278    public addMethodToCallSiteMap(funcID: FuncID, cs: CallSite): void {
279        if (this.methodToCallSiteMap.has(funcID)) {
280            this.methodToCallSiteMap.get(funcID)!.add(cs);
281        } else {
282            this.methodToCallSiteMap.set(funcID, new Set([cs]));
283        }
284    }
285
286    public getCallSitesByMethod(func: FuncID | MethodSignature): Set<CallSite> {
287        let funcID: FuncID;
288        if (func instanceof MethodSignature) {
289            funcID = this.getCallGraphNodeByMethod(func).getID();
290        } else {
291            funcID = func;
292        }
293
294        return this.methodToCallSiteMap.get(funcID) ?? new Set();
295    }
296
297    public getInvokeStmtByMethod(func: FuncID | MethodSignature): Stmt[] {
298        let callSites = this.getCallSitesByMethod(func);
299        let invokeStmts: Stmt[] = [];
300        callSites.forEach(cs => {
301            invokeStmts.push(cs.callStmt);
302        });
303
304        return invokeStmts;
305    }
306
307    public getDynEdges(): Map<Method, Set<Method>> {
308        let callMap: Map<Method, Set<Method>> = new Map();
309        this.callPairToEdgeMap.forEach((edge: CallGraphEdge) => {
310            let srcMethod = (edge.getSrcNode() as CallGraphNode).getMethod();
311            let dstMethod = (edge.getDstNode() as CallGraphNode).getMethod();
312
313            let dstSet: Set<Method>;
314            if (callMap.has(srcMethod)) {
315                dstSet = callMap.get(srcMethod)!;
316            } else {
317                dstSet = new Set();
318            }
319            callMap.set(srcMethod, dstSet.add(dstMethod));
320        });
321
322        return callMap;
323    }
324
325    public getMethodByFuncID(id: FuncID): Method | null {
326        let node = this.getNode(id);
327        if (node !== undefined) {
328            return (node as CallGraphNode).getMethod();
329        }
330        return null;
331    }
332
333    public getArkMethodByFuncID(id: FuncID): ArkMethod | null {
334        let method = this.getMethodByFuncID(id);
335        if (method != null) {
336            // TODO: SDK Method search
337            return this.scene.getMethod(method);
338        }
339
340        return null;
341    }
342
343    public getEntries(): FuncID[] {
344        return this.entries;
345    }
346
347    public setEntries(n: NodeID[]): void {
348        this.entries = n;
349    }
350
351    public dump(name: string, entry?: FuncID): void {
352        let printer = new GraphPrinter<this>(this);
353        if (entry) {
354            printer.setStartID(entry);
355        }
356        PrinterBuilder.dump(printer, name);
357    }
358
359    public detectReachable(fromID: FuncID, dstID: FuncID): boolean {
360        let dWorklist: FuncID[] = [];
361        let travserdFuncs = new Set();
362
363        dWorklist.push(fromID);
364
365        while (dWorklist.length > 0) {
366            let nodeID = dWorklist.shift()!;
367            if (travserdFuncs.has(nodeID)) {
368                continue;
369            }
370            travserdFuncs.add(nodeID);
371
372            let node = this.getNode(nodeID)!;
373            for (let e of node.getOutgoingEdges()) {
374                let dst = e.getDstID();
375                if (dst === dstID) {
376                    return true;
377                }
378                dWorklist.push(dst);
379            }
380        }
381
382        return false;
383    }
384
385    public startStat(): void {
386        this.cgStat.startStat();
387    }
388
389    public endStat(): void {
390        this.cgStat.endStat();
391    }
392
393    public printStat(): void {
394        this.cgStat.printStat();
395    }
396
397    public getStat(): string {
398        return this.cgStat.getStat();
399    }
400
401    public setDummyMainFuncID(dummyMainMethodID: number): void {
402        this.dummyMainMethodID = dummyMainMethodID;
403    }
404
405    public getDummyMainFuncID(): FuncID | undefined {
406        return this.dummyMainMethodID;
407    }
408
409    public isUnknownMethod(funcID: FuncID): boolean {
410        let method = this.getMethodByFuncID(funcID);
411
412        if (method) {
413            if (!(method.getDeclaringClassSignature().getDeclaringFileSignature().getFileName() === UNKNOWN_FILE_NAME)) {
414                return false;
415            }
416        }
417
418        return true;
419    }
420
421    public getGraphName(): string {
422        return 'CG';
423    }
424}