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 { 17 AbstractFieldRef, ArkAssignStmt, ArkFile, ArkIfStmt, ArkInvokeStmt, ArkMethod, ArkNamespace, ArkNewExpr, 18 ArkNormalBinopExpr, ArkStaticInvokeExpr, ArkUnopExpr, ClassSignature, ClassType, DEFAULT_ARK_CLASS_NAME, 19 FunctionType, LocalSignature, MethodSignature, NamespaceSignature, Scene, Signature, TEMP_LOCAL_PREFIX, Value, 20 transfer2UnixPath 21} from 'arkanalyzer'; 22import { ArkClass, ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass'; 23import { ExportSignature } from 'arkanalyzer/lib/core/model/ArkExport'; 24import { Local } from 'arkanalyzer/lib/core/base/Local'; 25import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 26import path from 'path'; 27import { FileUtils, WriteFileMode } from '../utils/common/FileUtils'; 28 29const OUTPUT_DIR_PATH = './ModuleChains'; 30const FILE_NAME_CHAINS_JSON = 'ModuleChains.json'; 31const FILE_NAME_FILE_ID_MAP = 'FileIdMap.json'; 32const FILE_NAME_CHAINS_TXT = 'ModuleChains.txt'; 33const logger = Logger.getLogger(LOG_MODULE_TYPE.TOOL, 'BuildModuleChains'); 34 35type ModuleSignature = ExportSignature | string; 36const gFinishScanMap = new Map<ModuleSignature, ModuleSignature[]>(); 37const gNodeMap: Map<string, { nodeInfo: NodeInfo, nextNodes: string[] }> = new Map(); 38const gModuleIdMap: Map<ModuleSignature, string> = new Map(); 39const repeatFilePath: string[] = []; 40let gOutPutDirPath: string = OUTPUT_DIR_PATH; 41let gIsSkipSdk = true; 42 43let gOutStorage = ''; 44let gOutNum = 0; 45 46interface NodeInfo { 47 filePath: string; 48 name: string; 49 type: number; 50 line: number; 51} 52 53export function buildModuleChains(scene: Scene, arkFiles: ArkFile[], outputDirPath: string): boolean { 54 let fileNameFlag = ''; 55 if (outputDirPath.length !== 0) { 56 gOutPutDirPath = outputDirPath; 57 } 58 if (arkFiles.length === 0) { 59 fileNameFlag = 'allFiles'; 60 arkFiles = scene.getFiles(); 61 } else { 62 fileNameFlag = arkFiles[0].getName(); 63 } 64 let isOutput = false; 65 for (const arkFile of arkFiles) { 66 const busyArray = new Array<ModuleSignature>(); 67 fileProcess(arkFile, busyArray); 68 } 69 logger.debug('Scan completed, start to write file...'); 70 isOutput = genResultForJson(scene, fileNameFlag); 71 clearGlobalMem(); 72 return isOutput; 73} 74 75function clearGlobalMem(): void { 76 gFinishScanMap.clear(); 77 gNodeMap.clear(); 78 gModuleIdMap.clear(); 79 repeatFilePath.length = 0; 80 gOutStorage = ''; 81 gOutNum = 0; 82} 83 84function genResultForJson(scene: Scene, fileName: string): boolean { 85 for (const [module] of gFinishScanMap) { 86 if (typeof module === 'string') { 87 const uniqueId = genUniqueId(); 88 genJsonNode(scene, module, uniqueId); 89 } 90 } 91 return outputNodeList(fileName.replace(/[\\/]/g, '_')); 92} 93 94function genUniqueId(): string { 95 return Math.random().toString(36).substring(2); 96} 97 98function genJsonNode(scene: Scene, module: ModuleSignature, uniqueId: string): void { 99 const nodeInfo = genNodeInfo(scene, module); 100 if (nodeInfo) { 101 gNodeMap.set(uniqueId, { nodeInfo: nodeInfo, nextNodes: [] }); 102 gModuleIdMap.set(module, uniqueId); 103 } else { 104 logger.warn(`create nodeInfo failed!`); 105 } 106 107 let nextNodeList = gFinishScanMap.get(module); 108 if (!nextNodeList) { 109 return; 110 } 111 112 for (const nextNode of nextNodeList) { 113 let nextUniqueId = gModuleIdMap.get(nextNode); 114 if (nextUniqueId) { 115 gNodeMap.get(uniqueId)?.nextNodes.push(nextUniqueId); 116 } else { 117 nextUniqueId = genUniqueId(); 118 gNodeMap.get(uniqueId)?.nextNodes.push(nextUniqueId); 119 genJsonNode(scene, nextNode, nextUniqueId); 120 } 121 } 122} 123 124function classTypeToString(scene: Scene, classSign: ClassSignature): string { 125 const type = scene.getClass(classSign)?.getCategory(); 126 switch (type) { 127 case ClassCategory.CLASS: 128 return 'class'; 129 case ClassCategory.STRUCT: 130 return 'struct'; 131 case ClassCategory.INTERFACE: 132 return 'interface'; 133 case ClassCategory.ENUM: 134 return 'enum'; 135 case ClassCategory.TYPE_LITERAL: 136 return 'literal'; 137 case ClassCategory.OBJECT: 138 return 'object'; 139 default: 140 return ''; 141 } 142} 143 144enum NodeType { 145 FILE = 0, 146 NAMESPACE, 147 CLASS, 148 STRUCT, 149 INTERFACE, 150 ENUM, 151 TYPE_LITERAL, 152 OBJECT, 153 FUNCTION, 154 VARIABLE 155} 156 157function genNodeInfo(scene: Scene, module: ModuleSignature): NodeInfo | null { 158 let nodeInfo: NodeInfo | null = null; 159 if (module instanceof ClassSignature) { 160 const type = scene.getClass(module)?.getCategory(); 161 nodeInfo = { 162 filePath: module.getDeclaringFileSignature().getFileName(), 163 name: module.getClassName(), 164 // 底座ArkClass的枚举值差2 165 type: (type !== undefined) ? type + 2 : -1, 166 line: -1 167 }; 168 } else if (module instanceof MethodSignature) { 169 let className = module.getDeclaringClassSignature()?.getClassName(); 170 if (className === DEFAULT_ARK_CLASS_NAME) { 171 className = module.getDeclaringClassSignature().getDeclaringNamespaceSignature()?.getNamespaceName() ?? ''; 172 } 173 let methodName = module.getMethodSubSignature().getMethodName(); 174 const methodLine = scene.getMethod(module)?.getLine(); 175 let curLine: number = -1; 176 if (methodLine) { 177 curLine = methodLine; 178 } 179 methodName = className.length > 0 ? `${className}.${methodName}` : methodName; 180 nodeInfo = { 181 filePath: module.getDeclaringClassSignature()?.getDeclaringFileSignature().getFileName(), 182 name: methodName, 183 type: NodeType.FUNCTION, 184 line: curLine 185 }; 186 } else if (module instanceof NamespaceSignature) { 187 nodeInfo = { 188 filePath: module.getDeclaringFileSignature().getFileName(), 189 name: module.getNamespaceName(), 190 type: NodeType.NAMESPACE, 191 line: -1 192 }; 193 } else if (module instanceof LocalSignature) { 194 const fileSign = module.getDeclaringMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature(); 195 const methodLine = scene.getMethod(module.getDeclaringMethodSignature())?.getLine(); 196 let curLine: number = -1; 197 if (methodLine) { 198 curLine = methodLine; 199 } 200 nodeInfo = { 201 filePath: fileSign.getFileName() ?? '', 202 name: module.getName(), 203 type: NodeType.VARIABLE, 204 line: curLine 205 }; 206 } else if (typeof module === 'string') { 207 nodeInfo = { 208 filePath: module, 209 name: path.basename(module), 210 type: NodeType.FILE, 211 line: -1 212 }; 213 } 214 return nodeInfo; 215} 216 217function genResultForChains(arkFile: ArkFile): boolean { 218 for (const [module] of gFinishScanMap) { 219 if (typeof (module) === 'string' && module.includes(arkFile.getFileSignature().getFileName().replace(/\//g, '\\'))) { 220 genChain(module); 221 } 222 } 223 if (gOutStorage.length > 0 && outputStorage()) { 224 logger.info(gOutStorage.length + ' chains have been written to the file.'); 225 return true; 226 } 227 return false; 228} 229 230function genChain(module: ModuleSignature, headChain: string = ''): void { 231 const nextNodes = gFinishScanMap.get(module); 232 if (nextNodes) { 233 for (const nextNode of nextNodes) { 234 genChain(nextNode, headChain + module.toString().replace(`/${DEFAULT_ARK_CLASS_NAME}./g`, '') + '\n>>'); 235 } 236 } else { 237 gOutStorage += headChain + module.toString().replace(`/${DEFAULT_ARK_CLASS_NAME}./g`, '') + '\n'; 238 gOutNum++; 239 if (gOutNum >= 1000 && outputStorage()) { 240 gOutStorage = ''; 241 gOutNum = 0; 242 } 243 } 244} 245 246function fileProcess(arkFile: ArkFile, busyArray: Array<ModuleSignature>): void { 247 const filePath = path.join('@' + arkFile.getProjectName(), transfer2UnixPath(arkFile.getName())); 248 if (!busyArray.includes(filePath) && !repeatFilePath.includes(filePath)) { 249 repeatFilePath.push(filePath); 250 busyArray.push(filePath); 251 const importList = arkFile.getImportInfos(); 252 for (const importModule of importList) { 253 const moduleSign = importModule.getLazyExportInfo()?.getArkExport()?.getSignature(); 254 if (!moduleSign) { 255 continue; 256 } 257 // 添加文件间依赖 258 const importFile = importModule.getLazyExportInfo()?.getDeclaringArkFile(); 259 if (importFile) { 260 fileProcess(importFile, busyArray); 261 } 262 moduleDeeplyProcess(moduleSign, busyArray, arkFile.getScene()); 263 } 264 if (busyArray.length > 1 && typeof (busyArray[busyArray.length - 1]) === 'string') { 265 addLastNodeToMap(busyArray); 266 } 267 // 查找全局调用 268 findGlobalDef(arkFile.getDefaultClass().getDefaultArkMethod(), busyArray); 269 busyArray.pop(); 270 } 271} 272 273function findGlobalDef(dfltMethod: ArkMethod | null, busyArray: Array<ModuleSignature>): void { 274 const stmts = dfltMethod?.getBody()?.getCfg().getStmts(); 275 for (const stmt of stmts ?? []) { 276 if (stmt instanceof ArkInvokeStmt) { 277 busyArray.push(stmt.getInvokeExpr().getMethodSignature()); 278 addLastNodeToMap(busyArray); 279 busyArray.pop(); 280 } 281 } 282} 283 284function moduleDeeplyProcess(moduleSign: Signature, busyArray: Array<ModuleSignature>, scene: Scene): void { 285 if (moduleSign instanceof ClassSignature) { 286 classProcess(scene.getClass(moduleSign), busyArray); 287 } else if (moduleSign instanceof MethodSignature) { 288 methodProcess(scene.getMethod(moduleSign), busyArray); 289 } else if (moduleSign instanceof NamespaceSignature) { 290 namespaceProcess(scene.getNamespace(moduleSign), busyArray); 291 } else if (moduleSign instanceof LocalSignature) { 292 busyArray.push(moduleSign); 293 addLastNodeToMap(busyArray); 294 busyArray.pop(); 295 } 296} 297 298function namespaceProcess(ns: ArkNamespace | null, busyArray: Array<ModuleSignature>): void { 299 if (!ns || busyArray.includes(ns.getSignature())) { 300 return; 301 } 302 const nsSign = ns.getSignature(); 303 busyArray.push(nsSign); 304 addLastNodeToMap(busyArray); 305 // 遍历过的节点不再遍历 306 if (gFinishScanMap.has(nsSign)) { 307 busyArray.pop(); 308 return; 309 } 310 // 处理sdk跳过逻辑 311 if (gIsSkipSdk && nsSign.getDeclaringFileSignature().getProjectName() !== ns.getDeclaringArkFile().getScene().getProjectName()) { 312 busyArray.pop(); 313 return; 314 } 315 // 处理当前层ns的类 316 for (const arkClass of ns.getClasses()) { 317 classProcess(arkClass, busyArray); 318 } 319 // 递归处理嵌套ns的类 320 for (const innerNs of ns.getNamespaces()) { 321 namespaceProcess(innerNs, busyArray); 322 } 323 busyArray.pop(); 324} 325 326function classProcess(arkClass: ArkClass | null, busyArray: Array<ModuleSignature>): void { 327 if (!arkClass || busyArray.includes(arkClass.getSignature())) { 328 return; 329 } 330 const arkClassSign = arkClass.getSignature(); 331 busyArray.push(arkClassSign); 332 if (!arkClass.isAnonymousClass()) { 333 addLastNodeToMap(busyArray); 334 } 335 336 // 遍历过的节点不再遍历 337 if (gFinishScanMap.has(arkClassSign)) { 338 busyArray.pop(); 339 return; 340 } 341 // 处理sdk跳过逻辑 342 if (gIsSkipSdk && arkClassSign.getDeclaringFileSignature().getProjectName() !== arkClass.getDeclaringArkFile().getScene().getProjectName()) { 343 busyArray.pop(); 344 return; 345 } 346 // 1、继承类处理 347 superClassProcess(arkClass, busyArray); 348 // 2、成员变量处理 349 arkFieldProcess(arkClass, busyArray); 350 // 3、方法内处理 351 for (const arkMethod of arkClass.getMethods()) { 352 methodProcess(arkMethod, busyArray); 353 } 354 busyArray.pop(); 355} 356 357function methodProcess(arkMethod: ArkMethod | null | undefined, busyArray: Array<ModuleSignature>): void { 358 if (!arkMethod || busyArray.includes(arkMethod.getSignature())) { 359 return; 360 } 361 const arkMethodSign = arkMethod.getSignature(); 362 busyArray.push(arkMethodSign); 363 if (!arkMethod.isAnonymousMethod()) { 364 addLastNodeToMap(busyArray); 365 } 366 // 遍历过的节点不再遍历 367 if (gFinishScanMap.has(arkMethodSign)) { 368 busyArray.pop(); 369 return; 370 } 371 // 处理sdk跳过逻辑 372 if (gIsSkipSdk && arkMethod.getDeclaringArkFile().getProjectName() !== arkMethod.getDeclaringArkFile().getScene().getProjectName()) { 373 busyArray.pop(); 374 return; 375 } 376 const stmts = arkMethod.getBody()?.getCfg()?.getStmts() ?? []; 377 const arkFile = arkMethod.getDeclaringArkFile(); 378 for (const stmt of stmts) { 379 if (stmt instanceof ArkInvokeStmt && stmt.getInvokeExpr() instanceof ArkStaticInvokeExpr) { 380 // 判断static调用是否为import, 仅考虑静态调用,示例调用场景在new stmt中处理 381 staticExprProcess(stmt.getInvokeExpr() as ArkStaticInvokeExpr, arkFile, busyArray); 382 } else if (stmt instanceof ArkAssignStmt) { 383 // 判断右值中1、new class; 2、Local; 3、一元运算;4、二元运算;5、实例调用/实例字段 是否为import导入的模块 384 rightOpProcess(stmt.getRightOp(), arkFile, busyArray); 385 } else if (stmt instanceof ArkIfStmt) { 386 // 判断ifStmt中变量或常量是否为import导入的模块 387 ifStmtProcess(stmt, arkFile, busyArray); 388 } 389 } 390 busyArray.pop(); 391} 392 393function staticExprProcess(invokeExpr: ArkStaticInvokeExpr, arkFile: ArkFile, busyArray: Array<ModuleSignature>): void { 394 const methodSignature = invokeExpr.getMethodSignature(); 395 const classSignature = methodSignature.getDeclaringClassSignature(); 396 const methodName = methodSignature.getMethodSubSignature().getMethodName(); 397 let className = classSignature.getClassName(); 398 if (className === DEFAULT_ARK_CLASS_NAME) { 399 className = classSignature.getDeclaringNamespaceSignature()?.getNamespaceName() ?? ''; 400 } 401 const fileSign = classSignature.getDeclaringFileSignature(); 402 let invokeFilePath = arkFile.getScene().getFile(fileSign)?.getFilePath(); 403 if (invokeFilePath && invokeFilePath === arkFile.getFilePath()) { 404 // 本文件的模块,深搜 405 methodProcess(arkFile.getScene().getMethod(methodSignature), busyArray); 406 return; 407 } 408 // 导入的模块 409 for (const importInfo of arkFile.getImportInfos()) { 410 const importName = importInfo.getImportClauseName(); 411 const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature(); 412 if (typeSign && (methodName === importName || className === importName)) { 413 moduleDeeplyProcess(typeSign, busyArray, arkFile.getScene()); 414 } 415 } 416} 417 418function ifStmtProcess(stmt: ArkIfStmt, curFile: ArkFile, busyArray: Array<ModuleSignature>): void { 419 const op1 = stmt.getConditionExpr().getOp1(); 420 const op2 = stmt.getConditionExpr().getOp2(); 421 if (op1 instanceof Local) { 422 localProcess(op1, curFile, busyArray); 423 } 424 if (op2 instanceof Local) { 425 localProcess(op2, curFile, busyArray); 426 } 427} 428 429function superClassProcess(arkClass: ArkClass, busyArray: Array<ModuleSignature>): void { 430 const superName = arkClass.getSuperClass()?.getName(); 431 if (!superName || busyArray.includes(arkClass.getSuperClass()?.getSignature()!)) { 432 return; 433 } 434 for (const importInfo of arkClass.getDeclaringArkFile().getImportInfos()) { 435 const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature(); 436 if (importInfo.getImportClauseName() === superName && typeSign) { 437 moduleDeeplyProcess(typeSign, busyArray, arkClass.getDeclaringArkFile().getScene()); 438 } 439 } 440} 441 442function arkFieldProcess(arkClass: ArkClass, busyArray: Array<ModuleSignature>): void { 443 const arkFields = arkClass.getFields(); 444 for (const arkField of arkFields) { 445 const fieldStmts = arkField.getInitializer(); 446 for (const stmt of fieldStmts) { 447 if (stmt instanceof ArkAssignStmt) { 448 rightOpProcess(stmt.getRightOp(), arkClass.getDeclaringArkFile(), busyArray); 449 } 450 } 451 } 452} 453 454function rightOpProcess(rightOp: Value, curFile: ArkFile, busyArray: Array<ModuleSignature>): void { 455 if (rightOp instanceof ArkNewExpr) { 456 // 右值为new class场景 457 const type = rightOp.getType(); 458 if (type instanceof ClassType) { 459 newExprProcess(type, curFile, busyArray); 460 } 461 } else if (rightOp instanceof Local) { 462 // 右值为Local场景 463 localProcess(rightOp, curFile, busyArray); 464 } else if (rightOp instanceof ArkUnopExpr) { 465 // 右值为一元运算场景 466 const ops = rightOp.getUses(); 467 for (const op of ops) { 468 if (op instanceof Local) { 469 localProcess(op, curFile, busyArray); 470 } 471 } 472 } else if (rightOp instanceof ArkNormalBinopExpr) { 473 // 右值为二元运算场景 474 const op1 = rightOp.getOp1(); 475 const op2 = rightOp.getOp2(); 476 if (op1 instanceof Local) { 477 localProcess(op1, curFile, busyArray); 478 } 479 if (op2 instanceof Local) { 480 localProcess(op2, curFile, busyArray); 481 } 482 } else if (rightOp instanceof AbstractFieldRef) { 483 moduleDeeplyProcess(rightOp.getFieldSignature().getDeclaringSignature(), busyArray, curFile.getScene()); 484 } else if (rightOp instanceof ArkStaticInvokeExpr) { 485 staticExprProcess(rightOp, curFile, busyArray); 486 } 487} 488 489function newExprProcess(type: ClassType, arkFile: ArkFile, busyArray: Array<ModuleSignature>): void { 490 const classSign = type.getClassSignature(); 491 const className = classSign.getClassName(); 492 const curFilePath = arkFile.getFilePath(); 493 const classFilePath = arkFile.getScene().getFile(classSign.getDeclaringFileSignature())?.getFilePath(); 494 if (!curFilePath || !classFilePath) { 495 logger.debug('Get curFilePath or classFilePath failed.'); 496 return; 497 } 498 if (curFilePath === classFilePath) { 499 // 本文件类 500 classProcess(arkFile.getClass(classSign), busyArray); 501 } else { 502 // 非本文件类 503 for (const importInfo of arkFile.getImportInfos()) { 504 const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature(); 505 if (className === importInfo.getImportClauseName() && typeSign) { 506 moduleDeeplyProcess(typeSign, busyArray, arkFile.getScene()); 507 } 508 } 509 } 510} 511 512function localProcess(rightOp: Local, curFile: ArkFile, busyArray: Array<ModuleSignature>): void { 513 const type = rightOp.getType(); 514 // todo: Local变量为方法或者类地址,let a = class1,目前右值type为unknown,走else分支 515 if (type instanceof ClassType) { 516 if (rightOp.getName().includes(TEMP_LOCAL_PREFIX)) { 517 return; 518 } 519 moduleDeeplyProcess(type.getClassSignature(), busyArray, curFile.getScene()); 520 } else if (type instanceof FunctionType) { 521 moduleDeeplyProcess(type.getMethodSignature(), busyArray, curFile.getScene()); 522 } else { 523 for (const importInfo of curFile.getImportInfos()) { 524 const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature(); 525 if (importInfo.getImportClauseName() === rightOp.getName() && typeSign) { 526 moduleDeeplyProcess(typeSign, busyArray, curFile.getScene()); 527 break; 528 } 529 } 530 } 531} 532 533function isAnonymous(module: ModuleSignature): boolean { 534 return module.toString().includes('%A'); 535} 536 537function addLastNodeToMap(busyArray: Array<ModuleSignature>): void { 538 let index = busyArray.length - 2; 539 let lastModule = busyArray[index]; 540 while (isAnonymous(lastModule) && index > 0) { 541 index--; 542 lastModule = busyArray[index]; 543 } 544 let curModule = busyArray[busyArray.length - 1]; 545 const storage = gFinishScanMap.get(lastModule); 546 if (!storage) { 547 gFinishScanMap.set(lastModule, [curModule]); 548 } else if (!storage.includes(curModule)) { 549 storage.push(curModule); 550 } 551} 552 553function outputNodeList(fileName: string): boolean { 554 // 文件和节点编号映射落盘 555 556 // import链落盘 557 try { 558 FileUtils.writeToFile(path.join(gOutPutDirPath, fileName + '_' + FILE_NAME_CHAINS_JSON), JSON.stringify(mapToJson(gNodeMap)), WriteFileMode.OVERWRITE); 559 return true; 560 } catch (error) { 561 logger.error((error as Error).message); 562 return false; 563 } 564} 565 566function outputStorage(): boolean { 567 try { 568 FileUtils.writeToFile(path.join(gOutPutDirPath, FILE_NAME_CHAINS_TXT), gOutStorage); 569 return true; 570 } catch (error) { 571 logger.error((error as Error).message); 572 return false; 573 } 574} 575 576function mapToJson(map: Map<string, any>): any { 577 const obj: { [key: string]: any } = Object.create(null); 578 for (const [key, value] of map) { 579 if (value instanceof Map) { 580 // 递归转换嵌套的Map 581 obj[key] = mapToJson(value); 582 } else { 583 obj[key] = value; 584 } 585 } 586 return obj; 587}