• 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 {
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}