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 { Scene } from '../../Scene'; 17import { AbstractInvokeExpr } from '../base/Expr'; 18import { ArkInvokeStmt, ArkReturnStmt, ArkReturnVoidStmt, Stmt } from '../base/Stmt'; 19import { ArkMethod } from '../model/ArkMethod'; 20import { DataflowProblem, FlowFunction } from './DataflowProblem'; 21import { PathEdge, PathEdgePoint } from './Edge'; 22import { BasicBlock } from '../graph/BasicBlock'; 23import { CallGraph } from '../../callgraph/model/CallGraph'; 24import { ClassHierarchyAnalysis } from '../../callgraph/algorithm/ClassHierarchyAnalysis'; 25import { addCfg2Stmt } from '../../utils/entryMethodUtils'; 26import { getRecallMethodInParam } from './Util'; 27import { CallGraphBuilder } from '../../callgraph/model/builder/CallGraphBuilder'; 28 29/* 30this program is roughly an implementation of the paper: Practical Extensions to the IFDS Algorithm. 31compare to the original ifds paper : Precise Interprocedural Dataflow Analysis via Graph Reachability, 32it have several improvments: 331. construct supergraph on demand(implement in this program); 342. use endSummary and incoming tables to speed up the program(implement in this program) 353. handle ssa form(not implement) 364. handle data facts which subsume another(not implement) 37*/ 38type CallToReturnCacheEdge<D> = PathEdge<D>; 39 40export abstract class DataflowSolver<D> { 41 protected problem: DataflowProblem<D>; 42 protected workList: Array<PathEdge<D>>; 43 protected pathEdgeSet: Set<PathEdge<D>>; 44 protected zeroFact: D; 45 protected inComing: Map<PathEdgePoint<D>, Set<PathEdgePoint<D>>>; 46 protected endSummary: Map<PathEdgePoint<D>, Set<PathEdgePoint<D>>>; 47 protected summaryEdge: Set<CallToReturnCacheEdge<D>>; // summaryEdge不是加速一个函数内多次调用同一个函数,而是加速多次调用同一个函数f时,f内的函数调用 48 protected scene: Scene; 49 protected CHA!: ClassHierarchyAnalysis; 50 protected stmtNexts: Map<Stmt, Set<Stmt>>; 51 protected laterEdges: Set<PathEdge<D>> = new Set(); 52 53 constructor(problem: DataflowProblem<D>, scene: Scene) { 54 this.problem = problem; 55 this.scene = scene; 56 scene.inferTypes(); 57 this.zeroFact = problem.createZeroValue(); 58 this.workList = new Array<PathEdge<D>>(); 59 this.pathEdgeSet = new Set<PathEdge<D>>(); 60 this.inComing = new Map<PathEdgePoint<D>, Set<PathEdgePoint<D>>>(); 61 this.endSummary = new Map<PathEdgePoint<D>, Set<PathEdgePoint<D>>>(); 62 this.summaryEdge = new Set<CallToReturnCacheEdge<D>>(); 63 this.stmtNexts = new Map(); 64 } 65 66 public solve(): void { 67 this.init(); 68 this.doSolve(); 69 } 70 71 protected computeResult(stmt: Stmt, d: D): boolean { 72 for (let pathEdge of this.pathEdgeSet) { 73 if (pathEdge.edgeEnd.node === stmt && pathEdge.edgeEnd.fact === d) { 74 return true; 75 } 76 } 77 return false; 78 } 79 80 protected getChildren(stmt: Stmt): Stmt[] { 81 return Array.from(this.stmtNexts.get(stmt) || []); 82 } 83 84 protected init(): void { 85 let edgePoint: PathEdgePoint<D> = new PathEdgePoint<D>(this.problem.getEntryPoint(), this.zeroFact); 86 let edge: PathEdge<D> = new PathEdge<D>(edgePoint, edgePoint); 87 this.workList.push(edge); 88 this.pathEdgeSet.add(edge); 89 90 // build CHA 91 let cg = new CallGraph(this.scene); 92 this.CHA = new ClassHierarchyAnalysis(this.scene, cg, new CallGraphBuilder(cg, this.scene)); 93 this.buildStmtMapInClass(); 94 this.setCfg4AllStmt(); 95 return; 96 } 97 98 protected buildStmtMapInClass(): void { 99 const methods = this.scene.getMethods(); 100 methods.push(this.problem.getEntryMethod()); 101 for (const method of methods) { 102 const cfg = method.getCfg(); 103 const blocks: BasicBlock[] = []; 104 if (cfg) { 105 blocks.push(...cfg.getBlocks()); 106 } 107 for (const block of blocks) { 108 this.buildStmtMapInBlock(block); 109 } 110 } 111 } 112 113 protected buildStmtMapInBlock(block: BasicBlock): void { 114 const stmts = block.getStmts(); 115 for (let stmtIndex = 0; stmtIndex < stmts.length; stmtIndex++) { 116 const stmt = stmts[stmtIndex]; 117 if (stmtIndex !== stmts.length - 1) { 118 this.stmtNexts.set(stmt, new Set([stmts[stmtIndex + 1]])); 119 } else { 120 const set: Set<Stmt> = new Set(); 121 for (const successor of block.getSuccessors()) { 122 set.add(successor.getStmts()[0]); 123 } 124 this.stmtNexts.set(stmt, set); 125 } 126 } 127 } 128 129 protected setCfg4AllStmt(): void { 130 for (const cls of this.scene.getClasses()) { 131 for (const mtd of cls.getMethods(true)) { 132 addCfg2Stmt(mtd); 133 } 134 } 135 } 136 137 protected getAllCalleeMethods(callNode: ArkInvokeStmt): Set<ArkMethod> { 138 const callSites = this.CHA.resolveCall( 139 this.CHA.getCallGraph().getCallGraphNodeByMethod(this.problem.getEntryMethod().getSignature()).getID(), 140 callNode 141 ); 142 const methods: Set<ArkMethod> = new Set(); 143 for (const callSite of callSites) { 144 const method = this.scene.getMethod(this.CHA.getCallGraph().getMethodByFuncID(callSite.calleeFuncID)!); 145 if (method) { 146 methods.add(method); 147 } 148 } 149 return methods; 150 } 151 152 protected getReturnSiteOfCall(call: Stmt): Stmt { 153 return [...this.stmtNexts.get(call)!][0]; 154 } 155 156 protected getStartOfCallerMethod(call: Stmt): Stmt { 157 const cfg = call.getCfg()!; 158 const paraNum = cfg.getDeclaringMethod().getParameters().length; 159 return [...cfg.getBlocks()][0].getStmts()[paraNum]; 160 } 161 162 protected pathEdgeSetHasEdge(edge: PathEdge<D>): boolean { 163 for (const path of this.pathEdgeSet) { 164 this.problem.factEqual(path.edgeEnd.fact, edge.edgeEnd.fact); 165 if ( 166 path.edgeEnd.node === edge.edgeEnd.node && 167 this.problem.factEqual(path.edgeEnd.fact, edge.edgeEnd.fact) && 168 path.edgeStart.node === edge.edgeStart.node && 169 this.problem.factEqual(path.edgeStart.fact, edge.edgeStart.fact) 170 ) { 171 return true; 172 } 173 } 174 return false; 175 } 176 177 protected propagate(edge: PathEdge<D>): void { 178 if (!this.pathEdgeSetHasEdge(edge)) { 179 let index = this.workList.length; 180 for (let i = 0; i < this.workList.length; i++) { 181 if (this.laterEdges.has(this.workList[i])) { 182 index = i; 183 break; 184 } 185 } 186 this.workList.splice(index, 0, edge); 187 this.pathEdgeSet.add(edge); 188 } 189 } 190 191 protected processExitNode(edge: PathEdge<D>): void { 192 let startEdgePoint: PathEdgePoint<D> = edge.edgeStart; 193 let exitEdgePoint: PathEdgePoint<D> = edge.edgeEnd; 194 const summary = this.endSummary.get(startEdgePoint); 195 if (summary === undefined) { 196 this.endSummary.set(startEdgePoint, new Set([exitEdgePoint])); 197 } else { 198 summary.add(exitEdgePoint); 199 } 200 const callEdgePoints = this.inComing.get(startEdgePoint); 201 if (callEdgePoints === undefined) { 202 if (startEdgePoint.node.getCfg()!.getDeclaringMethod() === this.problem.getEntryMethod()) { 203 return; 204 } 205 throw new Error('incoming does not have ' + startEdgePoint.node.getCfg()?.getDeclaringMethod().toString()); 206 } 207 for (let callEdgePoint of callEdgePoints) { 208 let returnSite: Stmt = this.getReturnSiteOfCall(callEdgePoint.node); 209 let returnFlowFunc: FlowFunction<D> = this.problem.getExitToReturnFlowFunction(exitEdgePoint.node, returnSite, callEdgePoint.node); 210 this.handleFacts(returnFlowFunc, returnSite, exitEdgePoint, callEdgePoint); 211 } 212 } 213 214 private handleFacts(returnFlowFunc: FlowFunction<D>, returnSite: Stmt, exitEdgePoint: PathEdgePoint<D>, callEdgePoint: PathEdgePoint<D>): void { 215 for (let fact of returnFlowFunc.getDataFacts(exitEdgePoint.fact)) { 216 let returnSitePoint: PathEdgePoint<D> = new PathEdgePoint<D>(returnSite, fact); 217 let cacheEdge: CallToReturnCacheEdge<D> = new PathEdge<D>(callEdgePoint, returnSitePoint); 218 let summaryEdgeHasCacheEdge = false; 219 for (const sEdge of this.summaryEdge) { 220 if (sEdge.edgeStart === callEdgePoint && sEdge.edgeEnd.node === returnSite && sEdge.edgeEnd.fact === fact) { 221 summaryEdgeHasCacheEdge = true; 222 break; 223 } 224 } 225 if (summaryEdgeHasCacheEdge) { 226 continue; 227 } 228 this.summaryEdge.add(cacheEdge); 229 let startOfCaller: Stmt = this.getStartOfCallerMethod(callEdgePoint.node); 230 for (let pathEdge of this.pathEdgeSet) { 231 if (pathEdge.edgeStart.node === startOfCaller && pathEdge.edgeEnd === callEdgePoint) { 232 this.propagate(new PathEdge<D>(pathEdge.edgeStart, returnSitePoint)); 233 } 234 } 235 } 236 } 237 238 protected processNormalNode(edge: PathEdge<D>): void { 239 let start: PathEdgePoint<D> = edge.edgeStart; 240 let end: PathEdgePoint<D> = edge.edgeEnd; 241 let stmts: Stmt[] = [...this.getChildren(end.node)].reverse(); 242 for (let stmt of stmts) { 243 let flowFunction: FlowFunction<D> = this.problem.getNormalFlowFunction(end.node, stmt); 244 let set: Set<D> = flowFunction.getDataFacts(end.fact); 245 for (let fact of set) { 246 let edgePoint: PathEdgePoint<D> = new PathEdgePoint<D>(stmt, fact); 247 const edge = new PathEdge<D>(start, edgePoint); 248 this.propagate(edge); 249 this.laterEdges.add(edge); 250 } 251 } 252 } 253 254 protected processCallNode(edge: PathEdge<D>): void { 255 let start: PathEdgePoint<D> = edge.edgeStart; 256 let callEdgePoint: PathEdgePoint<D> = edge.edgeEnd; 257 const invokeStmt = callEdgePoint.node as ArkInvokeStmt; 258 let callees: Set<ArkMethod>; 259 if (this.scene.getFile(invokeStmt.getInvokeExpr().getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature())) { 260 callees = this.getAllCalleeMethods(callEdgePoint.node as ArkInvokeStmt); 261 } else { 262 callees = new Set([getRecallMethodInParam(invokeStmt)!]); 263 } 264 let returnSite: Stmt = this.getReturnSiteOfCall(callEdgePoint.node); 265 for (let callee of callees) { 266 let callFlowFunc: FlowFunction<D> = this.problem.getCallFlowFunction(invokeStmt, callee); 267 if (!callee.getCfg()) { 268 continue; 269 } 270 let firstStmt: Stmt = [...callee.getCfg()!.getBlocks()][0].getStmts()[callee.getParameters().length]; 271 let facts: Set<D> = callFlowFunc.getDataFacts(callEdgePoint.fact); 272 for (let fact of facts) { 273 this.callNodeFactPropagate(edge, firstStmt, fact, returnSite); 274 } 275 } 276 let callToReturnflowFunc: FlowFunction<D> = this.problem.getCallToReturnFlowFunction(edge.edgeEnd.node, returnSite); 277 let set: Set<D> = callToReturnflowFunc.getDataFacts(callEdgePoint.fact); 278 for (let fact of set) { 279 this.propagate(new PathEdge<D>(start, new PathEdgePoint<D>(returnSite, fact))); 280 } 281 for (let cacheEdge of this.summaryEdge) { 282 if (cacheEdge.edgeStart === edge.edgeEnd && cacheEdge.edgeEnd.node === returnSite) { 283 this.propagate(new PathEdge<D>(start, cacheEdge.edgeEnd)); 284 } 285 } 286 } 287 288 protected callNodeFactPropagate(edge: PathEdge<D>, firstStmt: Stmt, fact: D, returnSite: Stmt): void { 289 let callEdgePoint: PathEdgePoint<D> = edge.edgeEnd; 290 // method start loop path edge 291 let startEdgePoint: PathEdgePoint<D> = new PathEdgePoint(firstStmt, fact); 292 this.propagate(new PathEdge<D>(startEdgePoint, startEdgePoint)); 293 //add callEdgePoint in inComing.get(startEdgePoint) 294 let coming: Set<PathEdgePoint<D>> | undefined; 295 for (const incoming of this.inComing.keys()) { 296 if (incoming.fact === startEdgePoint.fact && incoming.node === startEdgePoint.node) { 297 coming = this.inComing.get(incoming); 298 break; 299 } 300 } 301 if (coming === undefined) { 302 this.inComing.set(startEdgePoint, new Set([callEdgePoint])); 303 } else { 304 coming.add(callEdgePoint); 305 } 306 let exitEdgePoints: Set<PathEdgePoint<D>> = new Set(); 307 for (const end of Array.from(this.endSummary.keys())) { 308 if (end.fact === fact && end.node === firstStmt) { 309 exitEdgePoints = this.endSummary.get(end)!; 310 } 311 } 312 for (let exitEdgePoint of exitEdgePoints) { 313 let returnFlowFunc = this.problem.getExitToReturnFlowFunction(exitEdgePoint.node, returnSite, callEdgePoint.node); 314 for (let returnFact of returnFlowFunc.getDataFacts(exitEdgePoint.fact)) { 315 this.summaryEdge.add(new PathEdge<D>(edge.edgeEnd, new PathEdgePoint<D>(returnSite, returnFact))); 316 } 317 } 318 } 319 320 protected doSolve(): void { 321 while (this.workList.length !== 0) { 322 let pathEdge: PathEdge<D> = this.workList.shift()!; 323 if (this.laterEdges.has(pathEdge)) { 324 this.laterEdges.delete(pathEdge); 325 } 326 let targetStmt: Stmt = pathEdge.edgeEnd.node; 327 if (this.isCallStatement(targetStmt)) { 328 this.processCallNode(pathEdge); 329 } else if (this.isExitStatement(targetStmt)) { 330 this.processExitNode(pathEdge); 331 } else { 332 this.processNormalNode(pathEdge); 333 } 334 } 335 } 336 337 protected isCallStatement(stmt: Stmt): boolean { 338 for (const expr of stmt.getExprs()) { 339 if (expr instanceof AbstractInvokeExpr) { 340 if (this.scene.getFile(expr.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature())) { 341 return true; 342 } 343 if (stmt instanceof ArkInvokeStmt && getRecallMethodInParam(stmt)) { 344 return true; 345 } 346 } 347 } 348 return false; 349 } 350 351 protected isExitStatement(stmt: Stmt): boolean { 352 return stmt instanceof ArkReturnStmt || stmt instanceof ArkReturnVoidStmt; 353 } 354 355 public getPathEdgeSet(): Set<PathEdge<D>> { 356 return this.pathEdgeSet; 357 } 358} 359