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}