* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AbstractFieldRef, ArkAssignStmt, ArkFile, ArkIfStmt, ArkInvokeStmt, ArkMethod, ArkNamespace, ArkNewExpr,
ArkNormalBinopExpr, ArkStaticInvokeExpr, ArkUnopExpr, ClassSignature, ClassType, DEFAULT_ARK_CLASS_NAME,
FunctionType, LocalSignature, MethodSignature, NamespaceSignature, Scene, Signature, TEMP_LOCAL_PREFIX, Value,
transfer2UnixPath
} from 'arkanalyzer';
import { ArkClass, ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass';
import { ExportSignature } from 'arkanalyzer/lib/core/model/ArkExport';
import { Local } from 'arkanalyzer/lib/core/base/Local';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import path from 'path';
import { FileUtils, WriteFileMode } from '../utils/common/FileUtils';
const OUTPUT_DIR_PATH = './ModuleChains';
const FILE_NAME_CHAINS_JSON = 'ModuleChains.json';
const FILE_NAME_FILE_ID_MAP = 'FileIdMap.json';
const FILE_NAME_CHAINS_TXT = 'ModuleChains.txt';
const logger = Logger.getLogger(LOG_MODULE_TYPE.TOOL, 'BuildModuleChains');
type ModuleSignature = ExportSignature | string;
const gFinishScanMap = new Map<ModuleSignature, ModuleSignature[]>();
const gNodeMap: Map<string, { nodeInfo: NodeInfo, nextNodes: string[] }> = new Map();
const gModuleIdMap: Map<ModuleSignature, string> = new Map();
const repeatFilePath: string[] = [];
let gOutPutDirPath: string = OUTPUT_DIR_PATH;
let gIsSkipSdk = true;
let gOutStorage = '';
let gOutNum = 0;
interface NodeInfo {
filePath: string;
name: string;
type: number;
line: number;
}
export function buildModuleChains(scene: Scene, arkFiles: ArkFile[], outputDirPath: string): boolean {
let fileNameFlag = '';
if (outputDirPath.length !== 0) {
gOutPutDirPath = outputDirPath;
}
if (arkFiles.length === 0) {
fileNameFlag = 'allFiles';
arkFiles = scene.getFiles();
} else {
fileNameFlag = arkFiles[0].getName();
}
let isOutput = false;
for (const arkFile of arkFiles) {
const busyArray = new Array<ModuleSignature>();
fileProcess(arkFile, busyArray);
}
logger.debug('Scan completed, start to write file...');
isOutput = genResultForJson(scene, fileNameFlag);
clearGlobalMem();
return isOutput;
}
function clearGlobalMem(): void {
gFinishScanMap.clear();
gNodeMap.clear();
gModuleIdMap.clear();
repeatFilePath.length = 0;
gOutStorage = '';
gOutNum = 0;
}
function genResultForJson(scene: Scene, fileName: string): boolean {
for (const [module] of gFinishScanMap) {
if (typeof module === 'string') {
const uniqueId = genUniqueId();
genJsonNode(scene, module, uniqueId);
}
}
return outputNodeList(fileName.replace(/[\\/]/g, '_'));
}
function genUniqueId(): string {
return Math.random().toString(36).substring(2);
}
function genJsonNode(scene: Scene, module: ModuleSignature, uniqueId: string): void {
const nodeInfo = genNodeInfo(scene, module);
if (nodeInfo) {
gNodeMap.set(uniqueId, { nodeInfo: nodeInfo, nextNodes: [] });
gModuleIdMap.set(module, uniqueId);
} else {
logger.warn(`create nodeInfo failed!`);
}
let nextNodeList = gFinishScanMap.get(module);
if (!nextNodeList) {
return;
}
for (const nextNode of nextNodeList) {
let nextUniqueId = gModuleIdMap.get(nextNode);
if (nextUniqueId) {
gNodeMap.get(uniqueId)?.nextNodes.push(nextUniqueId);
} else {
nextUniqueId = genUniqueId();
gNodeMap.get(uniqueId)?.nextNodes.push(nextUniqueId);
genJsonNode(scene, nextNode, nextUniqueId);
}
}
}
function classTypeToString(scene: Scene, classSign: ClassSignature): string {
const type = scene.getClass(classSign)?.getCategory();
switch (type) {
case ClassCategory.CLASS:
return 'class';
case ClassCategory.STRUCT:
return 'struct';
case ClassCategory.INTERFACE:
return 'interface';
case ClassCategory.ENUM:
return 'enum';
case ClassCategory.TYPE_LITERAL:
return 'literal';
case ClassCategory.OBJECT:
return 'object';
default:
return '';
}
}
enum NodeType {
FILE = 0,
NAMESPACE,
CLASS,
STRUCT,
INTERFACE,
ENUM,
TYPE_LITERAL,
OBJECT,
FUNCTION,
VARIABLE
}
function genNodeInfo(scene: Scene, module: ModuleSignature): NodeInfo | null {
let nodeInfo: NodeInfo | null = null;
if (module instanceof ClassSignature) {
const type = scene.getClass(module)?.getCategory();
nodeInfo = {
filePath: module.getDeclaringFileSignature().getFileName(),
name: module.getClassName(),
type: (type !== undefined) ? type + 2 : -1,
line: -1
};
} else if (module instanceof MethodSignature) {
let className = module.getDeclaringClassSignature()?.getClassName();
if (className === DEFAULT_ARK_CLASS_NAME) {
className = module.getDeclaringClassSignature().getDeclaringNamespaceSignature()?.getNamespaceName() ?? '';
}
let methodName = module.getMethodSubSignature().getMethodName();
const methodLine = scene.getMethod(module)?.getLine();
let curLine: number = -1;
if (methodLine) {
curLine = methodLine;
}
methodName = className.length > 0 ? `${className}.${methodName}` : methodName;
nodeInfo = {
filePath: module.getDeclaringClassSignature()?.getDeclaringFileSignature().getFileName(),
name: methodName,
type: NodeType.FUNCTION,
line: curLine
};
} else if (module instanceof NamespaceSignature) {
nodeInfo = {
filePath: module.getDeclaringFileSignature().getFileName(),
name: module.getNamespaceName(),
type: NodeType.NAMESPACE,
line: -1
};
} else if (module instanceof LocalSignature) {
const fileSign = module.getDeclaringMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature();
const methodLine = scene.getMethod(module.getDeclaringMethodSignature())?.getLine();
let curLine: number = -1;
if (methodLine) {
curLine = methodLine;
}
nodeInfo = {
filePath: fileSign.getFileName() ?? '',
name: module.getName(),
type: NodeType.VARIABLE,
line: curLine
};
} else if (typeof module === 'string') {
nodeInfo = {
filePath: module,
name: path.basename(module),
type: NodeType.FILE,
line: -1
};
}
return nodeInfo;
}
function genResultForChains(arkFile: ArkFile): boolean {
for (const [module] of gFinishScanMap) {
if (typeof (module) === 'string' && module.includes(arkFile.getFileSignature().getFileName().replace(/\//g, '\\'))) {
genChain(module);
}
}
if (gOutStorage.length > 0 && outputStorage()) {
logger.info(gOutStorage.length + ' chains have been written to the file.');
return true;
}
return false;
}
function genChain(module: ModuleSignature, headChain: string = ''): void {
const nextNodes = gFinishScanMap.get(module);
if (nextNodes) {
for (const nextNode of nextNodes) {
genChain(nextNode, headChain + module.toString().replace(`/${DEFAULT_ARK_CLASS_NAME}./g`, '') + '\n>>');
}
} else {
gOutStorage += headChain + module.toString().replace(`/${DEFAULT_ARK_CLASS_NAME}./g`, '') + '\n';
gOutNum++;
if (gOutNum >= 1000 && outputStorage()) {
gOutStorage = '';
gOutNum = 0;
}
}
}
function fileProcess(arkFile: ArkFile, busyArray: Array<ModuleSignature>): void {
const filePath = path.join('@' + arkFile.getProjectName(), transfer2UnixPath(arkFile.getName()));
if (!busyArray.includes(filePath) && !repeatFilePath.includes(filePath)) {
repeatFilePath.push(filePath);
busyArray.push(filePath);
const importList = arkFile.getImportInfos();
for (const importModule of importList) {
const moduleSign = importModule.getLazyExportInfo()?.getArkExport()?.getSignature();
if (!moduleSign) {
continue;
}
const importFile = importModule.getLazyExportInfo()?.getDeclaringArkFile();
if (importFile) {
fileProcess(importFile, busyArray);
}
moduleDeeplyProcess(moduleSign, busyArray, arkFile.getScene());
}
if (busyArray.length > 1 && typeof (busyArray[busyArray.length - 1]) === 'string') {
addLastNodeToMap(busyArray);
}
findGlobalDef(arkFile.getDefaultClass().getDefaultArkMethod(), busyArray);
busyArray.pop();
}
}
function findGlobalDef(dfltMethod: ArkMethod | null, busyArray: Array<ModuleSignature>): void {
const stmts = dfltMethod?.getBody()?.getCfg().getStmts();
for (const stmt of stmts ?? []) {
if (stmt instanceof ArkInvokeStmt) {
busyArray.push(stmt.getInvokeExpr().getMethodSignature());
addLastNodeToMap(busyArray);
busyArray.pop();
}
}
}
function moduleDeeplyProcess(moduleSign: Signature, busyArray: Array<ModuleSignature>, scene: Scene): void {
if (moduleSign instanceof ClassSignature) {
classProcess(scene.getClass(moduleSign), busyArray);
} else if (moduleSign instanceof MethodSignature) {
methodProcess(scene.getMethod(moduleSign), busyArray);
} else if (moduleSign instanceof NamespaceSignature) {
namespaceProcess(scene.getNamespace(moduleSign), busyArray);
} else if (moduleSign instanceof LocalSignature) {
busyArray.push(moduleSign);
addLastNodeToMap(busyArray);
busyArray.pop();
}
}
function namespaceProcess(ns: ArkNamespace | null, busyArray: Array<ModuleSignature>): void {
if (!ns || busyArray.includes(ns.getSignature())) {
return;
}
const nsSign = ns.getSignature();
busyArray.push(nsSign);
addLastNodeToMap(busyArray);
if (gFinishScanMap.has(nsSign)) {
busyArray.pop();
return;
}
if (gIsSkipSdk && nsSign.getDeclaringFileSignature().getProjectName() !== ns.getDeclaringArkFile().getScene().getProjectName()) {
busyArray.pop();
return;
}
for (const arkClass of ns.getClasses()) {
classProcess(arkClass, busyArray);
}
for (const innerNs of ns.getNamespaces()) {
namespaceProcess(innerNs, busyArray);
}
busyArray.pop();
}
function classProcess(arkClass: ArkClass | null, busyArray: Array<ModuleSignature>): void {
if (!arkClass || busyArray.includes(arkClass.getSignature())) {
return;
}
const arkClassSign = arkClass.getSignature();
busyArray.push(arkClassSign);
if (!arkClass.isAnonymousClass()) {
addLastNodeToMap(busyArray);
}
if (gFinishScanMap.has(arkClassSign)) {
busyArray.pop();
return;
}
if (gIsSkipSdk && arkClassSign.getDeclaringFileSignature().getProjectName() !== arkClass.getDeclaringArkFile().getScene().getProjectName()) {
busyArray.pop();
return;
}
superClassProcess(arkClass, busyArray);
arkFieldProcess(arkClass, busyArray);
for (const arkMethod of arkClass.getMethods()) {
methodProcess(arkMethod, busyArray);
}
busyArray.pop();
}
function methodProcess(arkMethod: ArkMethod | null | undefined, busyArray: Array<ModuleSignature>): void {
if (!arkMethod || busyArray.includes(arkMethod.getSignature())) {
return;
}
const arkMethodSign = arkMethod.getSignature();
busyArray.push(arkMethodSign);
if (!arkMethod.isAnonymousMethod()) {
addLastNodeToMap(busyArray);
}
if (gFinishScanMap.has(arkMethodSign)) {
busyArray.pop();
return;
}
if (gIsSkipSdk && arkMethod.getDeclaringArkFile().getProjectName() !== arkMethod.getDeclaringArkFile().getScene().getProjectName()) {
busyArray.pop();
return;
}
const stmts = arkMethod.getBody()?.getCfg()?.getStmts() ?? [];
const arkFile = arkMethod.getDeclaringArkFile();
for (const stmt of stmts) {
if (stmt instanceof ArkInvokeStmt && stmt.getInvokeExpr() instanceof ArkStaticInvokeExpr) {
staticExprProcess(stmt.getInvokeExpr() as ArkStaticInvokeExpr, arkFile, busyArray);
} else if (stmt instanceof ArkAssignStmt) {
rightOpProcess(stmt.getRightOp(), arkFile, busyArray);
} else if (stmt instanceof ArkIfStmt) {
ifStmtProcess(stmt, arkFile, busyArray);
}
}
busyArray.pop();
}
function staticExprProcess(invokeExpr: ArkStaticInvokeExpr, arkFile: ArkFile, busyArray: Array<ModuleSignature>): void {
const methodSignature = invokeExpr.getMethodSignature();
const classSignature = methodSignature.getDeclaringClassSignature();
const methodName = methodSignature.getMethodSubSignature().getMethodName();
let className = classSignature.getClassName();
if (className === DEFAULT_ARK_CLASS_NAME) {
className = classSignature.getDeclaringNamespaceSignature()?.getNamespaceName() ?? '';
}
const fileSign = classSignature.getDeclaringFileSignature();
let invokeFilePath = arkFile.getScene().getFile(fileSign)?.getFilePath();
if (invokeFilePath && invokeFilePath === arkFile.getFilePath()) {
methodProcess(arkFile.getScene().getMethod(methodSignature), busyArray);
return;
}
for (const importInfo of arkFile.getImportInfos()) {
const importName = importInfo.getImportClauseName();
const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature();
if (typeSign && (methodName === importName || className === importName)) {
moduleDeeplyProcess(typeSign, busyArray, arkFile.getScene());
}
}
}
function ifStmtProcess(stmt: ArkIfStmt, curFile: ArkFile, busyArray: Array<ModuleSignature>): void {
const op1 = stmt.getConditionExpr().getOp1();
const op2 = stmt.getConditionExpr().getOp2();
if (op1 instanceof Local) {
localProcess(op1, curFile, busyArray);
}
if (op2 instanceof Local) {
localProcess(op2, curFile, busyArray);
}
}
function superClassProcess(arkClass: ArkClass, busyArray: Array<ModuleSignature>): void {
const superName = arkClass.getSuperClass()?.getName();
if (!superName || busyArray.includes(arkClass.getSuperClass()?.getSignature()!)) {
return;
}
for (const importInfo of arkClass.getDeclaringArkFile().getImportInfos()) {
const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature();
if (importInfo.getImportClauseName() === superName && typeSign) {
moduleDeeplyProcess(typeSign, busyArray, arkClass.getDeclaringArkFile().getScene());
}
}
}
function arkFieldProcess(arkClass: ArkClass, busyArray: Array<ModuleSignature>): void {
const arkFields = arkClass.getFields();
for (const arkField of arkFields) {
const fieldStmts = arkField.getInitializer();
for (const stmt of fieldStmts) {
if (stmt instanceof ArkAssignStmt) {
rightOpProcess(stmt.getRightOp(), arkClass.getDeclaringArkFile(), busyArray);
}
}
}
}
function rightOpProcess(rightOp: Value, curFile: ArkFile, busyArray: Array<ModuleSignature>): void {
if (rightOp instanceof ArkNewExpr) {
const type = rightOp.getType();
if (type instanceof ClassType) {
newExprProcess(type, curFile, busyArray);
}
} else if (rightOp instanceof Local) {
localProcess(rightOp, curFile, busyArray);
} else if (rightOp instanceof ArkUnopExpr) {
const ops = rightOp.getUses();
for (const op of ops) {
if (op instanceof Local) {
localProcess(op, curFile, busyArray);
}
}
} else if (rightOp instanceof ArkNormalBinopExpr) {
const op1 = rightOp.getOp1();
const op2 = rightOp.getOp2();
if (op1 instanceof Local) {
localProcess(op1, curFile, busyArray);
}
if (op2 instanceof Local) {
localProcess(op2, curFile, busyArray);
}
} else if (rightOp instanceof AbstractFieldRef) {
moduleDeeplyProcess(rightOp.getFieldSignature().getDeclaringSignature(), busyArray, curFile.getScene());
} else if (rightOp instanceof ArkStaticInvokeExpr) {
staticExprProcess(rightOp, curFile, busyArray);
}
}
function newExprProcess(type: ClassType, arkFile: ArkFile, busyArray: Array<ModuleSignature>): void {
const classSign = type.getClassSignature();
const className = classSign.getClassName();
const curFilePath = arkFile.getFilePath();
const classFilePath = arkFile.getScene().getFile(classSign.getDeclaringFileSignature())?.getFilePath();
if (!curFilePath || !classFilePath) {
logger.debug('Get curFilePath or classFilePath failed.');
return;
}
if (curFilePath === classFilePath) {
classProcess(arkFile.getClass(classSign), busyArray);
} else {
for (const importInfo of arkFile.getImportInfos()) {
const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature();
if (className === importInfo.getImportClauseName() && typeSign) {
moduleDeeplyProcess(typeSign, busyArray, arkFile.getScene());
}
}
}
}
function localProcess(rightOp: Local, curFile: ArkFile, busyArray: Array<ModuleSignature>): void {
const type = rightOp.getType();
if (type instanceof ClassType) {
if (rightOp.getName().includes(TEMP_LOCAL_PREFIX)) {
return;
}
moduleDeeplyProcess(type.getClassSignature(), busyArray, curFile.getScene());
} else if (type instanceof FunctionType) {
moduleDeeplyProcess(type.getMethodSignature(), busyArray, curFile.getScene());
} else {
for (const importInfo of curFile.getImportInfos()) {
const typeSign = importInfo.getLazyExportInfo()?.getArkExport()?.getSignature();
if (importInfo.getImportClauseName() === rightOp.getName() && typeSign) {
moduleDeeplyProcess(typeSign, busyArray, curFile.getScene());
break;
}
}
}
}
function isAnonymous(module: ModuleSignature): boolean {
return module.toString().includes('%A');
}
function addLastNodeToMap(busyArray: Array<ModuleSignature>) {
let index = busyArray.length - 2;
let lastModule = busyArray[index];
while (isAnonymous(lastModule) && index > 0) {
index--;
lastModule = busyArray[index];
}
let curModule = busyArray[busyArray.length - 1];
const storage = gFinishScanMap.get(lastModule);
if (!storage) {
gFinishScanMap.set(lastModule, [curModule]);
} else if (!storage.includes(curModule)) {
storage.push(curModule);
}
}
function outputNodeList(fileName: string): boolean {
try {
FileUtils.writeToFile(path.join(gOutPutDirPath, fileName + '_' + FILE_NAME_CHAINS_JSON), JSON.stringify(mapToJson(gNodeMap)), WriteFileMode.OVERWRITE);
return true;
} catch (error) {
logger.error((error as Error).message);
return false;
}
}
function outputStorage(): boolean {
try {
FileUtils.writeToFile(path.join(gOutPutDirPath, FILE_NAME_CHAINS_TXT), gOutStorage);
return true;
} catch (error) {
logger.error((error as Error).message);
return false;
}
}
function mapToJson(map: Map<string, any>): any {
const obj: { [key: string]: any } = Object.create(null);
for (const [key, value] of map) {
if (value instanceof Map) {
obj[key] = mapToJson(value);
} else {
obj[key] = value;
}
}
return obj;
}