From 47eb2cf1a755b84a24a3ffb2e877a442c22c9319 Mon Sep 17 00:00:00 2001 From: xudan16 Date: Tue, 17 Jun 2025 20:55:22 +0800 Subject: [PATCH 1/3] add interop deprecated builtin api check Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICFW2U Signed-off-by: xudan16 --- .../arkanalyzer/config/arkanalyzer.json | 8 +- ets2panda/linter/arkanalyzer/src/Config.ts | 1 + ets2panda/linter/arkanalyzer/src/Scene.ts | 33 +- .../callgraph/pointerAnalysis/PagBuilder.ts | 10 +- .../pointerAnalysis/PointerAnalysisConfig.ts | 2 +- .../linter/arkanalyzer/src/core/base/Expr.ts | 13 +- .../linter/arkanalyzer/src/core/base/Local.ts | 4 +- .../src/core/common/ArkIRTransformer.ts | 40 + .../src/core/common/ArkValueTransformer.ts | 86 +- .../arkanalyzer/src/core/common/Builtin.ts | 4 +- .../src/core/common/DummyMainCreater.ts | 2 +- .../src/core/common/IRInference.ts | 106 +- .../arkanalyzer/src/core/common/ModelUtils.ts | 2 +- .../arkanalyzer/src/core/common/SdkUtils.ts | 138 ++- .../src/core/common/TypeInference.ts | 34 +- .../linter/arkanalyzer/src/core/graph/Scc.ts | 2 +- .../src/core/graph/builder/CfgBuilder.ts | 26 +- .../arkanalyzer/src/core/model/ArkImport.ts | 2 +- .../arkanalyzer/src/core/model/ArkMethod.ts | 9 +- .../src/core/model/builder/ArkClassBuilder.ts | 15 +- .../arkanalyzer/src/save/source/SourceBody.ts | 5 +- .../arkanalyzer/src/save/source/SourceStmt.ts | 33 +- .../src/save/source/SourceTransformer.ts | 31 +- .../StaticSingleAssignmentFormer.ts | 2 +- ets2panda/linter/homecheck/ruleSet.json | 3 +- .../migration/InteropBackwardDFACheck.ts | 11 +- .../InteropDeprecatedBuiltInAPICheck.ts | 928 ++++++++++++++++++ .../src/utils/common/CheckerIndex.ts | 2 + 28 files changed, 1364 insertions(+), 188 deletions(-) create mode 100644 ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts diff --git a/ets2panda/linter/arkanalyzer/config/arkanalyzer.json b/ets2panda/linter/arkanalyzer/config/arkanalyzer.json index fe0c60bdc4..17df9e7480 100644 --- a/ets2panda/linter/arkanalyzer/config/arkanalyzer.json +++ b/ets2panda/linter/arkanalyzer/config/arkanalyzer.json @@ -7,15 +7,19 @@ ".d.ts" ], "enableLeadingComments": false, + "enableBuiltIn": true, "ignoreFileNames": [ "oh_modules", "node_modules", "build-tools", - "hvigorfile.ts" + "hvigorfile.ts", + "hvigorfile.js", + "hvigor-wrapper.js" ], "sdkGlobalFolders": [ "component", - "@internal" + "@internal", + "ohos-typescript" ], "tsconfig": "tsconfig.json" } \ No newline at end of file diff --git a/ets2panda/linter/arkanalyzer/src/Config.ts b/ets2panda/linter/arkanalyzer/src/Config.ts index 1a222ff665..45645d8783 100644 --- a/ets2panda/linter/arkanalyzer/src/Config.ts +++ b/ets2panda/linter/arkanalyzer/src/Config.ts @@ -43,6 +43,7 @@ export interface SceneOptions { ignoreFileNames?: string[]; enableLeadingComments?: boolean; enableTrailingComments?: boolean; + enableBuiltIn?: boolean; tsconfig?: string; isScanAbc?: boolean; sdkGlobalFolders?: string[]; diff --git a/ets2panda/linter/arkanalyzer/src/Scene.ts b/ets2panda/linter/arkanalyzer/src/Scene.ts index 84ef44b99a..cc41afcc09 100644 --- a/ets2panda/linter/arkanalyzer/src/Scene.ts +++ b/ets2panda/linter/arkanalyzer/src/Scene.ts @@ -48,11 +48,11 @@ const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'Scene'); enum SceneBuildStage { BUILD_INIT, + SDK_INFERRED, CLASS_DONE, METHOD_DONE, CLASS_COLLECTED, METHOD_COLLECTED, - SDK_INFERRED, TYPE_INFERRED, } @@ -207,6 +207,9 @@ export class Scene { } // handle sdks + if (this.options.enableBuiltIn && !sceneConfig.getSdksObj().find(sdk => sdk.name === SdkUtils.BUILT_IN_NAME)) { + sceneConfig.getSdksObj().unshift(SdkUtils.BUILT_IN_SDK); + } sceneConfig.getSdksObj()?.forEach(sdk => { if (!sdk.moduleName) { this.buildSdk(sdk.name, sdk.path); @@ -220,7 +223,16 @@ export class Scene { } } }); - + if (this.buildStage < SceneBuildStage.SDK_INFERRED) { + this.sdkArkFilesMap.forEach(file => { + IRInference.inferFile(file); + SdkUtils.mergeGlobalAPI(file, this.sdkGlobalMap); + }); + this.sdkArkFilesMap.forEach(file => { + SdkUtils.postInferredSdk(file, this.sdkGlobalMap); + }); + this.buildStage = SceneBuildStage.SDK_INFERRED; + } this.fileLanguages = sceneConfig.getFileLanguages(); } @@ -235,6 +247,7 @@ export class Scene { return; } const buildProfileJson = parseJsonText(configurationsText); + SdkUtils.setEsVersion(buildProfileJson); const modules = buildProfileJson.modules; if (modules instanceof Array) { modules.forEach(module => { @@ -588,7 +601,8 @@ export class Scene { } private buildSdk(sdkName: string, sdkPath: string): void { - const allFiles = getAllFiles(sdkPath, this.options.supportFileExts!, this.options.ignoreFileNames); + const allFiles = sdkName === SdkUtils.BUILT_IN_NAME ? SdkUtils.fetchBuiltInFiles() : + getAllFiles(sdkPath, this.options.supportFileExts!, this.options.ignoreFileNames); allFiles.forEach(file => { logger.trace('=== parse sdk file:', file); try { @@ -602,7 +616,7 @@ export class Scene { const fileSig = arkFile.getFileSignature().toMapKey(); this.sdkArkFilesMap.set(fileSig, arkFile); SdkUtils.buildSdkImportMap(arkFile); - SdkUtils.buildGlobalMap(arkFile, this.sdkGlobalMap); + SdkUtils.loadGlobalAPI(arkFile, this.sdkGlobalMap); } catch (error) { logger.error('Error parsing file:', file, error); this.unhandledSdkFilePaths.push(file); @@ -1039,16 +1053,7 @@ export class Scene { ``` */ public inferTypes(): void { - if (this.buildStage < SceneBuildStage.SDK_INFERRED) { - this.sdkArkFilesMap.forEach(file => { - try { - IRInference.inferFile(file); - } catch (error) { - logger.error('Error inferring types of sdk file:', file.getFileSignature(), error); - } - }); - this.buildStage = SceneBuildStage.SDK_INFERRED; - } + this.filesMap.forEach(file => { try { IRInference.inferFile(file); diff --git a/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PagBuilder.ts b/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PagBuilder.ts index 5249cd8d2c..815eae1afb 100644 --- a/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PagBuilder.ts +++ b/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PagBuilder.ts @@ -957,7 +957,7 @@ export class PagBuilder { .getCfg() ?.getStmts() .filter(s => s instanceof ArkAssignStmt && s.getRightOp() instanceof ArkThisRef); - let thisPtr = (thisAssignStmt?.at(0) as ArkAssignStmt).getRightOp() as ArkThisRef; + let thisPtr = (thisAssignStmt?.[0] as ArkAssignStmt).getRightOp() as ArkThisRef; if (!thisPtr) { throw new Error('Can not get this ptr'); } @@ -1043,8 +1043,8 @@ export class PagBuilder { // add args to parameters edges for (let i = offset; i <= args.length; i++) { - let arg = args.at(i); - let param = params.at(i - offset); + let arg = args[i]; + let param = params[i - offset]; if (!arg || !param) { return srcNodes; } @@ -1075,7 +1075,7 @@ export class PagBuilder { // container value is the base value of callstmt, its points-to is PagNewContainerExprNode let srcNodes: NodeID[] = []; let containerValue = (callStmt.getInvokeExpr() as ArkInstanceInvokeExpr).getBase(); - let param = params.at(0); + let param = params[0]; if (!containerValue || !param) { return srcNodes; } @@ -1212,7 +1212,7 @@ export class PagBuilder { // add args to parameters edges for (let i = 0; i < argNum; i++) { - let arg = cs.args?.at(i); + let arg = cs.args?.[i]; let paramValue; if (arg instanceof Local && arg.getType() instanceof FunctionType) { diff --git a/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PointerAnalysisConfig.ts b/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PointerAnalysisConfig.ts index 0dad1053cf..c42428087e 100644 --- a/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PointerAnalysisConfig.ts +++ b/ets2panda/linter/arkanalyzer/src/callgraph/pointerAnalysis/PointerAnalysisConfig.ts @@ -69,7 +69,7 @@ export class PointerAnalysisConfig { * Class PointerAnalysisConfig has been exported by ArkAnalyzer, the dispose method should be called by users themselves before free this class. */ public static dispose(): void { - // @ts-ignore + // @ts-expect-error: only be used to free the memory this.instance = null; } diff --git a/ets2panda/linter/arkanalyzer/src/core/base/Expr.ts b/ets2panda/linter/arkanalyzer/src/core/base/Expr.ts index 310e822863..521e967079 100644 --- a/ets2panda/linter/arkanalyzer/src/core/base/Expr.ts +++ b/ets2panda/linter/arkanalyzer/src/core/base/Expr.ts @@ -19,11 +19,13 @@ import { MethodSignature } from '../model/ArkSignature'; import { Local } from './Local'; import { AliasType, + AnyType, ArrayType, BigIntType, BooleanType, ClassType, FunctionType, + GenericType, NullType, NumberType, StringType, @@ -134,7 +136,8 @@ export abstract class AbstractInvokeExpr extends AbstractExpr { public getType(): Type { const type = this.methodSignature.getType(); - if (this.realGenericTypes) { + if (TypeInference.checkType(type, t => t instanceof GenericType || t instanceof AnyType) && + this.realGenericTypes) { return TypeInference.replaceTypeWithReal(type, this.realGenericTypes); } return type; @@ -347,6 +350,14 @@ export class ArkNewExpr extends AbstractExpr { if (TypeInference.isUnclearType(type)) { type = TypeInference.inferUnclearRefName(className, arkMethod.getDeclaringArkClass()); } + if (type instanceof AliasType) { + const originalType = TypeInference.replaceAliasType(type); + if (originalType instanceof FunctionType) { + type = originalType.getMethodSignature().getMethodSubSignature().getReturnType(); + } else { + type = originalType; + } + } if (type && type instanceof ClassType) { const instanceType = this.constructorSignature(type, arkMethod) ?? type; this.classType.setClassSignature(instanceType.getClassSignature()); diff --git a/ets2panda/linter/arkanalyzer/src/core/base/Local.ts b/ets2panda/linter/arkanalyzer/src/core/base/Local.ts index 10bb189627..18e6391330 100644 --- a/ets2panda/linter/arkanalyzer/src/core/base/Local.ts +++ b/ets2panda/linter/arkanalyzer/src/core/base/Local.ts @@ -20,7 +20,7 @@ import { TypeInference } from '../common/TypeInference'; import { ArkExport, ExportType } from '../model/ArkExport'; import { ClassSignature, LocalSignature, MethodSignature } from '../model/ArkSignature'; import { ArkSignatureBuilder } from '../model/builder/ArkSignatureBuilder'; -import { UNKNOWN_METHOD_NAME } from '../common/Const'; +import { NAME_PREFIX, UNKNOWN_METHOD_NAME } from '../common/Const'; import { ModifierType } from '../model/ArkBaseModel'; import { ArkMethod } from '../model/ArkMethod'; import { ModelUtils } from '../common/ModelUtils'; @@ -53,7 +53,7 @@ export class Local implements Value, ArkExport { if (this.name === THIS_NAME && this.type instanceof UnknownType) { const declaringArkClass = arkMethod.getDeclaringArkClass(); this.type = new ClassType(declaringArkClass.getSignature(), declaringArkClass.getRealTypes()); - } else if (TypeInference.isUnclearType(this.type)) { + } else if (!this.name.startsWith(NAME_PREFIX) && TypeInference.isUnclearType(this.type)) { const type = TypeInference.inferBaseType(this.name, arkMethod.getDeclaringArkClass()) ?? ModelUtils.findDeclaredLocal(this, arkMethod)?.getType(); if (type) { diff --git a/ets2panda/linter/arkanalyzer/src/core/common/ArkIRTransformer.ts b/ets2panda/linter/arkanalyzer/src/core/common/ArkIRTransformer.ts index c0a61f1f5d..5ed77fbe20 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/ArkIRTransformer.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/ArkIRTransformer.ts @@ -155,6 +155,8 @@ export class ArkIRTransformer { stmts = this.expressionInExportToStmts(node.expression); } else if (ts.isClassDeclaration(node)) { stmts = this.classDeclarationToStmts(node); + } else if (ts.isParameter(node)) { + stmts = this.parameterPropertyToStmts(node); } this.mapStmtsToTsStmt(stmts, node); @@ -189,6 +191,44 @@ export class ArkIRTransformer { return []; } + // This is only used to add class property assign stmts into constructor when it is with parameter property. + private parameterPropertyToStmts(paramNode: ts.ParameterDeclaration): Stmt[] { + if (paramNode.modifiers === undefined || !ts.isIdentifier(paramNode.name)) { + return []; + } + const fieldName = paramNode.name.text; + const arkClass = this.declaringMethod.getDeclaringArkClass(); + const fieldSignature = arkClass.getFieldWithName(fieldName)?.getSignature(); + const paramLocal = Array.from(this.getLocals()).find(local => local.getName() === fieldName); + if (fieldSignature === undefined || paramLocal === undefined) { + return []; + } + const leftOp = new ArkInstanceFieldRef(this.getThisLocal(), fieldSignature); + const fieldAssignStmt = new ArkAssignStmt(leftOp, paramLocal); + fieldAssignStmt.setOperandOriginalPositions([FullPosition.DEFAULT, FullPosition.DEFAULT, FullPosition.DEFAULT]); + + // If the parameter has initializer, the related stmts should be added into class instance init method. + const instInitMethodCfg = arkClass.getInstanceInitMethod().getBody()?.getCfg(); + const instInitStmts = instInitMethodCfg?.getStartingBlock()?.getStmts(); + if (paramNode.initializer && instInitStmts && instInitMethodCfg) { + const { + value: instanceInitValue, + valueOriginalPositions: instanceInitPositions, + stmts: instanceInitStmts, + } = this.tsNodeToValueAndStmts(paramNode.initializer); + const instanceAssignStmt = new ArkAssignStmt(leftOp, instanceInitValue); + instanceAssignStmt.setOperandOriginalPositions([FullPosition.DEFAULT, FullPosition.DEFAULT, ...instanceInitPositions]); + const newInstanceInitStmts = [...instanceInitStmts, instanceAssignStmt]; + + // All these stmts will be added into instance init method, while that method has completed the building. So all new stmts should set cfg here. + newInstanceInitStmts.forEach(stmt => stmt.setCfg(instInitMethodCfg)); + + // The last stmt of instance init method is return stmt, so all the initializer stmts should be added before return stmt. + instInitStmts.splice(instInitStmts.length - 1, 0, ...newInstanceInitStmts); + } + return [fieldAssignStmt]; + } + private returnStatementToStmts(returnStatement: ts.ReturnStatement): Stmt[] { const stmts: Stmt[] = []; if (returnStatement.expression) { diff --git a/ets2panda/linter/arkanalyzer/src/core/common/ArkValueTransformer.ts b/ets2panda/linter/arkanalyzer/src/core/common/ArkValueTransformer.ts index 75d8689428..a26b46ee66 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/ArkValueTransformer.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/ArkValueTransformer.ts @@ -299,6 +299,7 @@ export class ArkValueTransformer { const constructorInvokeStmt = new ArkInvokeStmt(constructorInvokeExpr); constructorInvokeStmt.setOperandOriginalPositions(constructorInvokeExprPositions); stmts.push(constructorInvokeStmt); + return { value: newExprLocal, valueOriginalPositions: newExprLocalPositions, @@ -832,6 +833,11 @@ export class ArkValueTransformer { } else { invokeValue = new ArkStaticInvokeExpr(methodSignature, args, realGenericTypes); } + } else if (callerValue instanceof ArkArrayRef && ts.isElementAccessExpression(functionNameNode)) { + const methodSignature = ArkSignatureBuilder.buildMethodSignatureFromMethodName(functionNameNode.argumentExpression.getText()); + invokeValue = new ArkInstanceInvokeExpr(callerValue.getBase(), methodSignature, args, realGenericTypes); + invokeValuePositions.push(...callerPositions.slice(1)); + stmts.pop(); } else { ({ value: callerValue, @@ -1107,15 +1113,24 @@ export class ArkValueTransformer { private prefixUnaryExpressionToValueAndStmts(prefixUnaryExpression: ts.PrefixUnaryExpression): ValueAndStmts { const stmts: Stmt[] = []; - let { value: operandValue, valueOriginalPositions: operandPositions, stmts: operandStmts } = this.tsNodeToValueAndStmts(prefixUnaryExpression.operand); + let { + value: originOperandValue, + valueOriginalPositions: originOperandPositions, + stmts: operandStmts, + } = this.tsNodeToValueAndStmts(prefixUnaryExpression.operand); operandStmts.forEach(stmt => stmts.push(stmt)); - if (IRUtils.moreThanOneAddress(operandValue)) { + let operandValue: Value; + let operandPositions: FullPosition[]; + if (IRUtils.moreThanOneAddress(originOperandValue)) { ({ value: operandValue, valueOriginalPositions: operandPositions, stmts: operandStmts, - } = this.arkIRTransformer.generateAssignStmtForValue(operandValue, operandPositions)); + } = this.arkIRTransformer.generateAssignStmtForValue(originOperandValue, originOperandPositions)); operandStmts.forEach(stmt => stmts.push(stmt)); + } else { + operandValue = originOperandValue; + operandPositions = originOperandPositions; } const operatorToken = prefixUnaryExpression.operator; @@ -1127,17 +1142,14 @@ export class ArkValueTransformer { const assignStmt = new ArkAssignStmt(operandValue, binopExpr); assignStmt.setOperandOriginalPositions([...operandPositions, ...exprPositions]); stmts.push(assignStmt); - return { - value: operandValue, - valueOriginalPositions: operandPositions, - stmts: stmts, - }; + if (operandValue !== originOperandValue) { + const lastAssignStmt = new ArkAssignStmt(originOperandValue, operandValue); + lastAssignStmt.setOperandOriginalPositions([...originOperandPositions, ...operandPositions]); + stmts.push(lastAssignStmt); + } + return { value: originOperandValue, valueOriginalPositions: originOperandPositions, stmts: stmts }; } else if (operatorToken === ts.SyntaxKind.PlusToken) { - return { - value: operandValue, - valueOriginalPositions: operandPositions, - stmts: stmts, - }; + return { value: operandValue, valueOriginalPositions: operandPositions, stmts: stmts }; } else { let unopExpr: Value; const operator = ArkIRTransformer.tokenToUnaryOperator(operatorToken); @@ -1148,28 +1160,32 @@ export class ArkValueTransformer { unopExpr = ValueUtil.getUndefinedConst(); exprPositions = [FullPosition.DEFAULT]; } - return { - value: unopExpr, - valueOriginalPositions: exprPositions, - stmts: stmts, - }; + return { value: unopExpr, valueOriginalPositions: exprPositions, stmts: stmts }; } } private postfixUnaryExpressionToValueAndStmts(postfixUnaryExpression: ts.PostfixUnaryExpression): ValueAndStmts { const stmts: Stmt[] = []; - let { value: operandValue, valueOriginalPositions: operandPositions, stmts: exprStmts } = this.tsNodeToValueAndStmts(postfixUnaryExpression.operand); + let { + value: originOperandValue, + valueOriginalPositions: originOperandPositions, + stmts: exprStmts, + } = this.tsNodeToValueAndStmts(postfixUnaryExpression.operand); exprStmts.forEach(stmt => stmts.push(stmt)); - if (IRUtils.moreThanOneAddress(operandValue)) { + let operandValue: Value; + let operandPositions: FullPosition[]; + if (IRUtils.moreThanOneAddress(originOperandValue)) { ({ value: operandValue, valueOriginalPositions: operandPositions, stmts: exprStmts, - } = this.arkIRTransformer.generateAssignStmtForValue(operandValue, operandPositions)); + } = this.arkIRTransformer.generateAssignStmtForValue(originOperandValue, originOperandPositions)); exprStmts.forEach(stmt => stmts.push(stmt)); + } else { + operandValue = originOperandValue; + operandPositions = originOperandPositions; } - let value: Value; let exprPositions = [FullPosition.buildFromNode(postfixUnaryExpression, this.sourceFile)]; const operatorToken = postfixUnaryExpression.operator; if (operatorToken === ts.SyntaxKind.PlusPlusToken || operatorToken === ts.SyntaxKind.MinusMinusToken) { @@ -1179,15 +1195,21 @@ export class ArkValueTransformer { const assignStmt = new ArkAssignStmt(operandValue, binopExpr); assignStmt.setOperandOriginalPositions([...operandPositions, ...exprPositions]); stmts.push(assignStmt); - value = operandValue; - } else { - value = ValueUtil.getUndefinedConst(); - exprPositions = [FullPosition.DEFAULT]; + if (operandValue !== originOperandValue) { + const lastAssignStmt = new ArkAssignStmt(originOperandValue, operandValue); + lastAssignStmt.setOperandOriginalPositions([...originOperandPositions, ...operandPositions]); + stmts.push(lastAssignStmt); + } + return { + value: originOperandValue, + valueOriginalPositions: originOperandPositions, + stmts: stmts, + }; } return { - value: value, - valueOriginalPositions: exprPositions, + value: ValueUtil.getUndefinedConst(), + valueOriginalPositions: [FullPosition.DEFAULT], stmts: stmts, }; } @@ -1921,7 +1943,8 @@ export class ArkValueTransformer { } private resolveTypeReferenceNode(typeReferenceNode: ts.TypeReferenceNode): Type { - const typeReferenceFullName = typeReferenceNode.typeName.getText(this.sourceFile); + const typeReferenceFullName = ts.isIdentifier(typeReferenceNode.typeName) ? typeReferenceNode.typeName.text : + typeReferenceNode.typeName.getText(this.sourceFile); if (typeReferenceFullName === Builtin.OBJECT) { return Builtin.OBJECT_CLASS_TYPE; } @@ -1935,12 +1958,11 @@ export class ArkValueTransformer { } if (!aliasTypeAndStmt) { - const typeName = typeReferenceNode.typeName.getText(this.sourceFile); - const local = this.locals.get(typeName); + const local = this.locals.get(typeReferenceFullName); if (local !== undefined) { return local.getType(); } - return new UnclearReferenceType(typeName, genericTypes); + return new UnclearReferenceType(typeReferenceFullName, genericTypes); } else { if (genericTypes.length > 0) { const oldAlias = aliasTypeAndStmt[0]; diff --git a/ets2panda/linter/arkanalyzer/src/core/common/Builtin.ts b/ets2panda/linter/arkanalyzer/src/core/common/Builtin.ts index 5c74ecf4d2..faf7eb2152 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/Builtin.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/Builtin.ts @@ -42,8 +42,8 @@ export class Builtin { public static BUILT_IN_CLASS_SIGNATURE_MAP = this.buildBuiltInClassSignatureMap(); // constants for iterator - public static ITERATOR_FUNCTION = 'iterator'; - public static ITERATOR = 'Iterator'; + public static ITERATOR_FUNCTION = 'Symbol.iterator'; + public static ITERATOR = 'IterableIterator'; public static ITERATOR_NEXT = 'next'; public static ITERATOR_RESULT = 'IteratorResult'; public static ITERATOR_RESULT_DONE = 'done'; diff --git a/ets2panda/linter/arkanalyzer/src/core/common/DummyMainCreater.ts b/ets2panda/linter/arkanalyzer/src/core/common/DummyMainCreater.ts index 3818ac8051..4dc9c68041 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/DummyMainCreater.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/DummyMainCreater.ts @@ -175,7 +175,7 @@ export class DummyMainCreater { let superCls = method.getDeclaringArkClass().getSuperClass(); let methodInSuperCls = superCls?.getMethodWithName(method.getName()); if (methodInSuperCls) { - paramType = methodInSuperCls.getParameters().at(paramIdx)?.getType(); + paramType = methodInSuperCls.getParameters()[paramIdx]?.getType(); method = methodInSuperCls; } } diff --git a/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts b/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts index a9642db052..c670ead482 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts @@ -49,7 +49,7 @@ import { AliasClassSignature, BaseSignature, ClassSignature, - FieldSignature, + FieldSignature, FileSignature, MethodSignature, MethodSubSignature } from '../model/ArkSignature'; @@ -70,7 +70,7 @@ import { Constant } from '../base/Constant'; import { ANONYMOUS_CLASS_PREFIX, CALL_SIGNATURE_NAME, - DEFAULT_ARK_CLASS_NAME, + DEFAULT_ARK_CLASS_NAME, LEXICAL_ENV_NAME_PREFIX, NAME_DELIMITER, NAME_PREFIX, UNKNOWN_CLASS_NAME @@ -136,6 +136,10 @@ export class IRInference { } public static inferStaticInvokeExpr(expr: ArkStaticInvokeExpr, arkMethod: ArkMethod): AbstractInvokeExpr { + const fileSignature = expr.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature(); + if (fileSignature !== FileSignature.DEFAULT && fileSignature !== Builtin.BUILT_IN_CLASSES_FILE_SIGNATURE) { + return expr; + } const arkClass = arkMethod.getDeclaringArkClass(); const methodName = expr.getMethodSignature().getMethodSubSignature().getMethodName(); expr.getArgs().forEach(arg => TypeInference.inferValueType(arg, arkMethod)); @@ -252,10 +256,12 @@ export class IRInference { if (type instanceof FunctionType) { const methodSignature = type.getMethodSignature(); // because of last stmt is ArkReturnVoidStmt, the ArkInvokeStmt at -2 before ArkReturnVoidStmt. - const endIndex = -2; - const endStmt = arkMethod.getDeclaringArkFile().getScene().getMethod(methodSignature)?.getCfg()?.getStmts().at(endIndex); - if (endStmt instanceof ArkInvokeStmt) { - methodSignature.getMethodSubSignature().setReturnType(endStmt.getInvokeExpr().getType()); + const stmts = arkMethod.getDeclaringArkFile().getScene().getMethod(methodSignature)?.getCfg()?.getStmts(); + if (stmts) { + const endStmt = stmts[stmts.length - 2]; + if (endStmt instanceof ArkInvokeStmt) { + methodSignature.getMethodSubSignature().setReturnType(endStmt.getInvokeExpr().getType()); + } } expr.setMethodSignature(methodSignature); return expr; @@ -442,17 +448,13 @@ export class IRInference { } private static inferInvokeExprWithArray(methodName: string, expr: AbstractInvokeExpr, baseType: ArrayType, scene: Scene): AbstractInvokeExpr | null { - if (methodName === Builtin.ITERATOR_FUNCTION) { - const returnType = expr.getMethodSignature().getMethodSubSignature().getReturnType(); - if (returnType instanceof ClassType && returnType.getClassSignature().getDeclaringFileSignature().getProjectName() === Builtin.DUMMY_PROJECT_NAME) { - expr.setRealGenericTypes([baseType.getBaseType()]); - return expr; - } - } else { - const arrayInterface = scene.getSdkGlobal(Builtin.ARRAY); - if (arrayInterface instanceof ArkClass) { - return this.inferInvokeExpr(expr, new ClassType(arrayInterface.getSignature(), [baseType.getBaseType()]), methodName, scene); - } + const arrayInterface = scene.getSdkGlobal(Builtin.ARRAY); + if (arrayInterface instanceof ArkClass) { + return this.inferInvokeExpr(expr, new ClassType(arrayInterface.getSignature(), [baseType.getBaseType()]), methodName, scene); + } else if (methodName === Builtin.ITERATOR_FUNCTION) { + expr.getMethodSignature().getMethodSubSignature().setReturnType(Builtin.ITERATOR_CLASS_TYPE); + expr.setRealGenericTypes([baseType.getBaseType()]); + return expr; } return null; } @@ -495,7 +497,7 @@ export class IRInference { const methodSignature = method.matchMethodSignature(expr.getArgs()); TypeInference.inferSignatureReturnType(methodSignature, method); expr.setMethodSignature(this.replaceMethodSignature(expr.getMethodSignature(), methodSignature)); - expr.setRealGenericTypes(IRInference.getRealTypes(method, declaredClass, baseType)); + expr.setRealGenericTypes(IRInference.getRealTypes(expr, declaredClass, baseType, method)); if (method.isStatic() && expr instanceof ArkInstanceInvokeExpr) { return new ArkStaticInvokeExpr(methodSignature, expr.getArgs(), expr.getRealGenericTypes()); } @@ -524,25 +526,33 @@ export class IRInference { const subSignature = new MethodSubSignature(methodName, [], new ClassType(baseType.getClassSignature())); expr.setMethodSignature(new MethodSignature(baseType.getClassSignature(), subSignature)); return expr; - } else if (methodName === Builtin.ITERATOR_NEXT) { - //sdk隐式构造 - const returnType = expr.getMethodSignature().getMethodSubSignature().getReturnType(); - if (returnType instanceof ClassType && returnType.getClassSignature().getDeclaringFileSignature().getProjectName() === Builtin.DUMMY_PROJECT_NAME) { - returnType.setRealGenericTypes(baseType.getRealGenericTypes()); - return expr; - } + } else if (methodName === Builtin.ITERATOR_NEXT && + baseType.getClassSignature().getDeclaringFileSignature().getProjectName() === Builtin.DUMMY_PROJECT_NAME) { + expr.getMethodSignature().getMethodSubSignature().setReturnType(Builtin.ITERATOR_RESULT_CLASS_TYPE); + expr.setRealGenericTypes(baseType.getRealGenericTypes()); + return expr; } return null; } - private static getRealTypes(method: ArkMethod, declaredClass: ArkClass | null, baseType: ClassType): Type[] | undefined { + private static getRealTypes(expr: AbstractInvokeExpr, declaredClass: ArkClass | null, baseType: ClassType, method: ArkMethod): Type[] | undefined { let realTypes; - if (method.getDeclaringArkClass() === declaredClass) { - realTypes = baseType.getRealGenericTypes(); - } else if (declaredClass?.getRealTypes()) { - realTypes = declaredClass?.getRealTypes(); + const tmp: Type[] = []; + if (method.getGenericTypes()) { + expr.getMethodSignature().getMethodSubSignature().getParameters() + .filter(p => !p.getName().startsWith(LEXICAL_ENV_NAME_PREFIX)) + .forEach((p, i) => { + if (TypeInference.checkType(p.getType(), t => t instanceof GenericType)) { + tmp.push(expr.getArg(i).getType()); + } + }); + } + if (tmp.length > 0) { + realTypes = tmp; } else if (declaredClass?.hasComponentDecorator()) { realTypes = [new ClassType(declaredClass?.getSignature())]; + } else { + realTypes = baseType.getRealGenericTypes() ?? declaredClass?.getRealTypes(); } return realTypes; } @@ -615,23 +625,13 @@ export class IRInference { } const fieldName = ref.getFieldName().replace(/[\"|\']/g, ''); const propertyAndType = TypeInference.inferFieldType(baseType, fieldName, arkClass); - let propertyType = propertyAndType?.[1]; - if (!propertyType || propertyType instanceof UnknownType) { - const newType = TypeInference.inferBaseType(fieldName, arkClass); - if (newType) { - propertyType = newType; - } - } else if (TypeInference.isUnclearType(propertyType)) { - const newType = TypeInference.inferUnclearedType(propertyType, arkClass); - if (newType) { - propertyType = newType; - } - } + let propertyType = IRInference.repairType(propertyAndType?.[1], fieldName, arkClass); let staticFlag: boolean; let signature: BaseSignature; if (baseType instanceof ClassType) { const property = propertyAndType?.[0]; - if (property instanceof ArkField && property.getCategory() !== FieldCategory.ENUM_MEMBER) { + if (property instanceof ArkField && property.getCategory() !== FieldCategory.ENUM_MEMBER && + !(property.getType() instanceof GenericType)) { return property.getSignature(); } staticFlag = @@ -647,6 +647,21 @@ export class IRInference { return new FieldSignature(fieldName, signature, propertyType ?? ref.getType(), staticFlag); } + private static repairType(propertyType: Type | undefined, fieldName: string, arkClass: ArkClass): Type | undefined { + if (!propertyType || propertyType instanceof UnknownType) { + const newType = TypeInference.inferBaseType(fieldName, arkClass); + if (newType) { + propertyType = newType; + } + } else if (TypeInference.isUnclearType(propertyType)) { + const newType = TypeInference.inferUnclearedType(propertyType, arkClass); + if (newType) { + propertyType = newType; + } + } + return propertyType; + } + public static inferAnonymousClass(anon: ArkClass | null, declaredSignature: ClassSignature, set: Set = new Set()): void { if (!anon) { return; @@ -695,7 +710,8 @@ export class IRInference { } const type = property.getSignature().getType(); - const lastStmt = anonField.getInitializer().at(-1); + const fieldInitializer = anonField.getInitializer(); + const lastStmt = fieldInitializer[fieldInitializer.length - 1]; if (lastStmt instanceof ArkAssignStmt) { const rightType = lastStmt.getRightOp().getType(); if (type instanceof ClassType) { @@ -812,7 +828,7 @@ export class IRInference { public static inferParameterRef(ref: ArkParameterRef, arkMethod: ArkMethod): AbstractRef { const paramType = ref.getType(); if (paramType instanceof UnknownType || paramType instanceof UnclearReferenceType) { - const signature = arkMethod.getDeclareSignatures()?.at(0) ?? arkMethod.getSignature(); + const signature = arkMethod.getDeclareSignatures()?.[0] ?? arkMethod.getSignature(); const type1 = signature.getMethodSubSignature().getParameters()[ref.getIndex()]?.getType(); if (!TypeInference.isUnclearType(type1)) { ref.setType(type1); diff --git a/ets2panda/linter/arkanalyzer/src/core/common/ModelUtils.ts b/ets2panda/linter/arkanalyzer/src/core/common/ModelUtils.ts index 41eb1a39f4..ae079bea04 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/ModelUtils.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/ModelUtils.ts @@ -51,7 +51,7 @@ export class ModelUtils { /* * Set static field to be null, then all related objects could be freed by GC. - * Static field implicitArkUIBuilderMethods is only internally used by ArkAnalyzer build method body, the dispose method should be called after build all body. + * Static field implicitArkUIBuilderMethods is only used during method body building, the dispose method should be called after build all body. */ public static dispose(): void { this.implicitArkUIBuilderMethods.clear(); diff --git a/ets2panda/linter/arkanalyzer/src/core/common/SdkUtils.ts b/ets2panda/linter/arkanalyzer/src/core/common/SdkUtils.ts index 644983c544..aa3b7fd421 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/SdkUtils.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/SdkUtils.ts @@ -20,21 +20,72 @@ import { GLOBAL_THIS_NAME, THIS_NAME } from './TSConst'; import { TEMP_LOCAL_PREFIX } from './Const'; import { ArkClass, ClassCategory } from '../model/ArkClass'; import { LocalSignature } from '../model/ArkSignature'; -import { ModelUtils } from './ModelUtils'; import { Local } from '../base/Local'; import { ArkMethod } from '../model/ArkMethod'; import path from 'path'; import { ClassType } from '../base/Type'; import { AbstractFieldRef } from '../base/Ref'; import { ArkNamespace } from '../model/ArkNamespace'; -import { TypeInference } from './TypeInference'; import Logger, { LOG_MODULE_TYPE } from '../../utils/logger'; +import { Sdk } from '../../Config'; +import ts from 'ohos-typescript'; +import fs from 'fs'; const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'SdkUtils'); export class SdkUtils { + private static esVersion: string = 'ES2017'; + private static esVersionMap: Map = new Map([ + ['ES2017', 'lib.es2020.d.ts'], + ['ES2021', 'lib.es2021.d.ts'] + ]); private static sdkImportMap: Map = new Map(); + public static BUILT_IN_NAME = 'built-in'; + private static BUILT_IN_PATH = 'node_modules/ohos-typescript/lib'; + public static BUILT_IN_SDK: Sdk = { + moduleName: '', + name: `${SdkUtils.BUILT_IN_NAME}`, + path: '' + }; + + public static setEsVersion(buildProfile: any): void { + const accessChain = 'buildOption.arkOptions.tscConfig.targetESVersion'; + const version = accessChain.split('.').reduce((acc, key) => acc?.[key], buildProfile); + if (version && this.esVersionMap.has(version)) { + this.esVersion = version; + } + } + + public static fetchBuiltInFiles(): string[] { + let builtInPath = this.BUILT_IN_PATH; + try { + // If arkanalyzer is used as dependency by other project, the base directory should be the module path. + const moduleRoot = path.dirname(path.dirname(require.resolve('ohos-typescript'))); + builtInPath = path.join(moduleRoot, 'lib'); + logger.debug(`arkanalyzer is used as dependency, so using builtin sdk file in ${builtInPath}.`); + } catch { + logger.debug(`use builtin sdk file in ${builtInPath}.`); + } + const filePath = path.resolve(builtInPath, this.esVersionMap.get(this.esVersion) ?? ''); + this.BUILT_IN_SDK.path = path.resolve(builtInPath); + if (!fs.existsSync(filePath)) { + logger.error(`built in directory ${filePath} is not exist, please check!`); + return []; + } + const result = new Set(); + this.dfsFiles(filePath, result); + return Array.from(result); + } + + private static dfsFiles(filePath: string, files: Set): void { + const sourceFile = ts.createSourceFile(filePath, fs.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest); + const references = sourceFile.libReferenceDirectives; + references.forEach(ref => { + this.dfsFiles(path.join(path.dirname(filePath), `lib.${ref.fileName}.d.ts`), files); + }); + files.add(filePath); + } /* * Set static field to be null, then all related objects could be freed by GC. @@ -55,7 +106,7 @@ export class SdkUtils { return this.sdkImportMap.get(from); } - public static buildGlobalMap(file: ArkFile, globalMap: Map): void { + public static loadGlobalAPI(file: ArkFile, globalMap: Map): void { const isGlobalPath = file .getScene() .getOptions() @@ -63,20 +114,59 @@ export class SdkUtils { if (!isGlobalPath) { return; } - ModelUtils.getAllClassesInFile(file).forEach(cls => { + file.getClasses().forEach(cls => { if (!cls.isAnonymousClass() && !cls.isDefaultArkClass()) { - SdkUtils.loadClass(globalMap, cls); + this.loadAPI(cls, globalMap); } if (cls.isDefaultArkClass()) { cls.getMethods() .filter(mtd => !mtd.isDefaultArkMethod() && !mtd.isAnonymousMethod()) - .forEach(mtd => globalMap.set(mtd.getName(), mtd)); + .forEach(mtd => this.loadAPI(mtd, globalMap)); } }); - const defaultArkMethod = file.getDefaultClass().getDefaultArkMethod(); - if (defaultArkMethod) { - TypeInference.inferTypeInMethod(defaultArkMethod); + file.getDefaultClass().getDefaultArkMethod() + ?.getBody() + ?.getAliasTypeMap() + ?.forEach(a => this.loadAPI(a[0], globalMap, true)); + file.getNamespaces().forEach(ns => this.loadAPI(ns, globalMap)); + } + + public static mergeGlobalAPI(file: ArkFile, globalMap: Map): void { + const isGlobalPath = file + .getScene() + .getOptions() + .sdkGlobalFolders?.find(x => file.getFilePath().includes(path.sep + x + path.sep)); + if (!isGlobalPath) { + return; } + file.getClasses().forEach(cls => { + if (!cls.isAnonymousClass() && !cls.isDefaultArkClass()) { + this.loadClass(globalMap, cls); + } + }); + } + + public static loadAPI(api: ArkExport, globalMap: Map, override: boolean = false): void { + const old = globalMap.get(api.getName()); + if (!old) { + globalMap.set(api.getName(), api); + } else if (override) { + logger.trace(`${old.getSignature()} is override`); + globalMap.set(api.getName(), api); + } else { + logger.trace(`duplicated api: ${api.getSignature()}`); + } + } + + public static postInferredSdk(file: ArkFile, globalMap: Map): void { + const isGlobalPath = file + .getScene() + .getOptions() + .sdkGlobalFolders?.find(x => file.getFilePath().includes(path.sep + x + path.sep)); + if (!isGlobalPath) { + return; + } + const defaultArkMethod = file.getDefaultClass().getDefaultArkMethod(); defaultArkMethod ?.getBody() ?.getLocals() @@ -86,28 +176,22 @@ export class SdkUtils { this.loadGlobalLocal(local, defaultArkMethod, globalMap); } }); - defaultArkMethod - ?.getBody() - ?.getAliasTypeMap() - ?.forEach(a => globalMap.set(a[0].getName(), a[0])); - ModelUtils.getAllNamespacesInFile(file).forEach(ns => globalMap.set(ns.getName(), ns)); } private static loadClass(globalMap: Map, cls: ArkClass): void { const old = globalMap.get(cls.getName()); - if (old instanceof ArkClass && old.getDeclaringArkFile().getProjectName() === cls.getDeclaringArkFile().getProjectName()) { - if (old.getCategory() === ClassCategory.CLASS) { + if (cls === old) { + return; + } else if (old instanceof ArkClass && old.getDeclaringArkFile().getProjectName() === cls.getDeclaringArkFile().getProjectName()) { + if (old.getCategory() === ClassCategory.CLASS || old.getCategory() === ClassCategory.INTERFACE) { this.copyMembers(cls, old); } else { this.copyMembers(old, cls); globalMap.delete(cls.getName()); - globalMap.set(cls.getName(), cls); + this.loadAPI(cls, globalMap, true); } } else { - if (old) { - logger.error(`${old.getSignature()} is override`); - } - globalMap.set(cls.getName(), cls); + this.loadAPI(cls, globalMap, true); } } @@ -125,13 +209,15 @@ export class SdkUtils { } } const old = globalMap.get(name); - if (!old) { - globalMap.set(name, local); - } else if (old instanceof ArkClass && local.getType() instanceof ClassType) { - const localConstructor = scene.getClass((local.getType() as ClassType).getClassSignature()); - if (localConstructor) { + if (old instanceof ArkClass && local.getType() instanceof ClassType) { + const localConstructor = globalMap.get((local.getType() as ClassType).getClassSignature().getClassName()); + if (localConstructor instanceof ArkClass) { this.copyMembers(localConstructor, old); + } else { + this.loadAPI(local, globalMap, true); } + } else { + this.loadAPI(local, globalMap, true); } } diff --git a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts index 07fc7faf50..a2a2b25067 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts @@ -415,10 +415,10 @@ export class TypeInference { ) { return true; } else if (type instanceof UnionType || type instanceof IntersectionType || type instanceof TupleType) { - return !!type.getTypes().find(t => this.hasUnclearReferenceType(t)); + return !!type.getTypes().find(t => this.checkType(t, e => e instanceof UnclearReferenceType)); } else if (type instanceof ArrayType) { const baseType = type.getBaseType(); - return this.hasUnclearReferenceType(baseType) || baseType instanceof GenericType; + return this.checkType(baseType, t => t instanceof UnclearReferenceType) || baseType instanceof GenericType; } else if (type instanceof AliasType) { return this.isUnclearType(type.getOriginalType()); } else if (type instanceof KeyofTypeExpr) { @@ -429,25 +429,29 @@ export class TypeInference { return false; } - // This is the temporal function to check unclearReferenceType recursively and can be removed after typeInfer supports multiple candidate types. - private static hasUnclearReferenceType(type: Type, visited: Set = new Set()): boolean { + // This is the temporal function to check Type recursively and can be removed after typeInfer supports multiple candidate types. + public static checkType(type: Type, + check: (t: Type) => boolean, + visited: Set = new Set()): boolean { if (visited.has(type)) { return false; } else { visited.add(type); } - if (type instanceof UnclearReferenceType) { + if (check(type)) { return true; + } else if (type instanceof ClassType) { + return !!type.getRealGenericTypes()?.find(t => this.checkType(t, check, visited)); } else if (type instanceof UnionType || type instanceof IntersectionType || type instanceof TupleType) { - return !!type.getTypes().find(t => this.hasUnclearReferenceType(t, visited)); + return !!type.getTypes().find(t => this.checkType(t, check, visited)); } else if (type instanceof ArrayType) { - return this.hasUnclearReferenceType(type.getBaseType(), visited); + return this.checkType(type.getBaseType(), check, visited); } else if (type instanceof AliasType) { - return this.hasUnclearReferenceType(type.getOriginalType(), visited); + return this.checkType(type.getOriginalType(), check, visited); } else if (type instanceof KeyofTypeExpr) { - return this.hasUnclearReferenceType(type.getOpType(), visited); + return this.checkType(type.getOpType(), check, visited); } else if (type instanceof TypeQueryExpr) { - return this.hasUnclearReferenceType(type.getType(), visited); + return this.checkType(type.getType(), check, visited); } return false; } @@ -714,7 +718,8 @@ export class TypeInference { if (property instanceof ArkField) { if (arkClass.getCategory() === ClassCategory.ENUM) { let constant; - const lastStmt = property.getInitializer().at(-1); + const propertyInitializer = property.getInitializer(); + const lastStmt = propertyInitializer[propertyInitializer.length - 1]; if (lastStmt instanceof ArkAssignStmt && lastStmt.getRightOp() instanceof Constant) { constant = lastStmt.getRightOp() as Constant; } @@ -779,6 +784,11 @@ export class TypeInference { return null; } + public static getTypeByGlobalName(globalName: string, arkMethod: ArkMethod): Type | null { + const arkExport: ArkExport | null = arkMethod.getDeclaringArkFile().getScene().getSdkGlobal(globalName); + return this.parseArkExport2Type(arkExport); + } + public static inferRealGenericTypes(realTypes: Type[] | undefined, arkClass: ArkClass): void { if (!realTypes) { return; @@ -895,7 +905,7 @@ export class TypeInference { } let returnType: Type | undefined = arkMethod.getSignature().getType(); if (returnType instanceof ClassType && returnType.getClassSignature().getClassName() === PROMISE) { - returnType = returnType.getRealGenericTypes()?.at(0); + returnType = returnType.getRealGenericTypes()?.[0]; } if (returnType) { IRInference.inferRightWithSdkType(returnType, stmt.getOp().getType(), arkMethod.getDeclaringArkClass()); diff --git a/ets2panda/linter/arkanalyzer/src/core/graph/Scc.ts b/ets2panda/linter/arkanalyzer/src/core/graph/Scc.ts index aeb568012d..ccafdd7067 100644 --- a/ets2panda/linter/arkanalyzer/src/core/graph/Scc.ts +++ b/ets2panda/linter/arkanalyzer/src/core/graph/Scc.ts @@ -173,7 +173,7 @@ export class SCCDetection> { if (this.getRep(v) === v) { this.setInSCC(v); while (this._S.length > 0) { - let w = this._S.at(this._S.length - 1)!; + let w = this._S[this._S.length - 1]!; if (this._D.get(w)! <= this._D.get(v)!) { break; } else { diff --git a/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts b/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts index 13ff5584cc..478d0a7121 100644 --- a/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts +++ b/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts @@ -32,6 +32,7 @@ import { ConditionBuilder } from './ConditionBuilder'; import { TrapBuilder } from './TrapBuilder'; import { CONSTRUCTOR_NAME, PROMISE } from '../../common/TSConst'; import { ModifierType } from '../../model/ArkBaseModel'; +import { ParameterDeclaration } from 'ohos-typescript'; class StatementBuilder { type: string; @@ -524,7 +525,7 @@ export class CfgBuilder { this.scopes.push(scope); for (let i = 0; i < nodes.length; i++) { let c = nodes[i]; - if (ts.isVariableStatement(c) || ts.isExpressionStatement(c) || ts.isThrowStatement(c) || ts.isTypeAliasDeclaration(c)) { + if (ts.isVariableStatement(c) || ts.isExpressionStatement(c) || ts.isThrowStatement(c) || ts.isTypeAliasDeclaration(c) || ts.isParameter(c)) { let s = new StatementBuilder('statement', c.getText(this.sourceFile), c, scope.id); this.judgeLastType(s, lastStatement); lastStatement = s; @@ -677,7 +678,7 @@ export class CfgBuilder { for (let i = stmt.nexts.length - 1; i >= 0; i--) { stmtQueue.push(stmt.nexts[i]); } - if (stmt.afterSwitch && stmt.afterSwitch.lasts.size == 0) { + if (stmt.afterSwitch && stmt.afterSwitch.lasts.size === 0) { stmtQueue.push(stmt.afterSwitch); } } else if (stmt instanceof TryStatementBuilder) { @@ -959,6 +960,16 @@ export class CfgBuilder { this.exit.lasts = new Set([s]); } + private getParamPropertyNodes(constructorParams: ts.NodeArray): ts.Node[] { + let stmts: ts.Node[] = []; + constructorParams.forEach(parameter => { + if (parameter.modifiers !== undefined) { + stmts.push(parameter); + } + }); + return stmts; + } + buildCfgBuilder(): void { let stmts: ts.Node[] = []; if (ts.isSourceFile(this.astRoot)) { @@ -972,11 +983,7 @@ export class CfgBuilder { ts.isFunctionExpression(this.astRoot) || ts.isClassStaticBlockDeclaration(this.astRoot) ) { - if (this.astRoot.body) { - stmts = [...this.astRoot.body.statements]; - } else { - this.emptyBody = true; - } + this.astRoot.body ? stmts = [...this.astRoot.body.statements] : this.emptyBody = true; } else if (ts.isArrowFunction(this.astRoot)) { if (ts.isBlock(this.astRoot.body)) { stmts = [...this.astRoot.body.statements]; @@ -991,6 +998,10 @@ export class CfgBuilder { } else if (ts.isModuleDeclaration(this.astRoot) && ts.isModuleBlock(this.astRoot.body!)) { stmts = [...this.astRoot.body.statements]; } + // For constructor, add parameter property node to stmts which can be used when build body + if (ts.isConstructorDeclaration(this.astRoot)) { + stmts = [...this.getParamPropertyNodes(this.astRoot.parameters), ...stmts]; + } if (!ModelUtils.isArkUIBuilderMethod(this.declaringMethod)) { this.walkAST(this.entry, this.exit, stmts); } else { @@ -999,6 +1010,7 @@ export class CfgBuilder { if (ts.isArrowFunction(this.astRoot) && !ts.isBlock(this.astRoot.body)) { this.buildStatementBuilder4ArrowFunction(this.astRoot.body); } + this.addReturnInEmptyMethod(); this.deleteExit(); this.CfgBuilder2Array(this.entry); diff --git a/ets2panda/linter/arkanalyzer/src/core/model/ArkImport.ts b/ets2panda/linter/arkanalyzer/src/core/model/ArkImport.ts index 05c5c24a80..8ff3ff50ac 100644 --- a/ets2panda/linter/arkanalyzer/src/core/model/ArkImport.ts +++ b/ets2panda/linter/arkanalyzer/src/core/model/ArkImport.ts @@ -72,7 +72,7 @@ export class ImportInfo extends ArkBaseModel implements FromInfo { * @returns The export information. If there is no export information, the return will be a **null**. */ public getLazyExportInfo(): ExportInfo | null { - if (this.lazyExportInfo === undefined && this.declaringArkFile.getScene().getStage() >= 2) { + if (this.lazyExportInfo === undefined) { this.lazyExportInfo = findExportInfo(this); } return this.lazyExportInfo || null; diff --git a/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts b/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts index 12682223ba..789e940e89 100644 --- a/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts +++ b/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts @@ -644,16 +644,21 @@ export class ArkMethod extends ArkBaseModel implements ArkExport { if (!args[i]) { return isArrowFunc ? true : parameters[i].isOptional(); } - const isMatched = this.matchParam(parameters[i].getType(), args[i]); + const paramType = parameters[i].getType(); + const isMatched = this.matchParam(paramType, args[i]); if (!isMatched) { return false; + } else if (paramType instanceof EnumValueType || paramType instanceof LiteralType) { + return true; } } return true; } private matchParam(paramType: Type, arg: Value): boolean { - arg = ArkMethod.parseArg(arg); + if (paramType instanceof EnumValueType || paramType instanceof LiteralType) { + arg = ArkMethod.parseArg(arg); + } const argType = arg.getType(); if (paramType instanceof AliasType && !(argType instanceof AliasType)) { paramType = TypeInference.replaceAliasType(paramType); diff --git a/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkClassBuilder.ts b/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkClassBuilder.ts index 48a7217d1a..dcef17ac8d 100644 --- a/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkClassBuilder.ts +++ b/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkClassBuilder.ts @@ -371,12 +371,12 @@ function buildArkClassMembers(clsNode: ClassLikeNode, cls: ArkClass, sourceFile: let staticBlockId = 0; clsNode.members.forEach(member => { if ( - ts.isMethodDeclaration(member) || - ts.isConstructorDeclaration(member) || - ts.isMethodSignature(member) || - ts.isConstructSignatureDeclaration(member) || - ts.isAccessor(member) || - ts.isCallSignatureDeclaration(member) + ts.isMethodDeclaration(member) || + ts.isConstructorDeclaration(member) || + ts.isMethodSignature(member) || + ts.isConstructSignatureDeclaration(member) || + ts.isAccessor(member) || + ts.isCallSignatureDeclaration(member) ) { // these node types have been handled at the beginning of this function by calling buildMethodsForClass return; @@ -466,6 +466,9 @@ function buildParameterProperty2ArkField(params: ts.NodeArray 0 && !isAttr) { @@ -511,7 +532,7 @@ export class SourceWhileStmt extends SourceStmt { return false; } - if (iterator.getMethodSignature().getMethodSubSignature().getMethodName() !== 'iterator') { + if (iterator.getMethodSignature().getMethodSubSignature().getMethodName() !== 'Symbol.iterator') { return false; } diff --git a/ets2panda/linter/arkanalyzer/src/save/source/SourceTransformer.ts b/ets2panda/linter/arkanalyzer/src/save/source/SourceTransformer.ts index 58c999d85b..d199e7fc17 100644 --- a/ets2panda/linter/arkanalyzer/src/save/source/SourceTransformer.ts +++ b/ets2panda/linter/arkanalyzer/src/save/source/SourceTransformer.ts @@ -60,7 +60,12 @@ import { SourceClass } from './SourceClass'; import { Value } from '../../core/base/Value'; import { AbstractRef, ArkArrayRef, ArkInstanceFieldRef, ArkStaticFieldRef, ArkThisRef } from '../../core/base/Ref'; import { ArkFile } from '../../core/model/ArkFile'; -import { COMPONENT_CREATE_FUNCTION, COMPONENT_CUSTOMVIEW, COMPONENT_IF, COMPONENT_POP_FUNCTION } from '../../core/common/EtsConst'; +import { + COMPONENT_CREATE_FUNCTION, + COMPONENT_CUSTOMVIEW, + COMPONENT_IF, + COMPONENT_POP_FUNCTION +} from '../../core/common/EtsConst'; import { INSTANCE_INIT_METHOD_NAME } from '../../core/common/Const'; import { ArkAssignStmt } from '../../core/base/Stmt'; import { ArkNamespace } from '../../core/model/ArkNamespace'; @@ -84,7 +89,7 @@ export interface TransformerContext { getPrinter(): ArkCodeBuffer; - transTemp2Code(temp: Local): string; + transTemp2Code(temp: Local, isLeftOp: boolean): string; isInBuilderMethod(): boolean; } @@ -107,7 +112,7 @@ export class SourceTransformer { return clsPrinter.dump().trimStart(); } - public instanceInvokeExprToString(invokeExpr: ArkInstanceInvokeExpr): string { + public instanceInvokeExprToString(invokeExpr: ArkInstanceInvokeExpr, isAttr: boolean): string { let methodName = invokeExpr.getMethodSignature().getMethodSubSignature().getMethodName(); if (methodName === INSTANCE_INIT_METHOD_NAME) { return ''; @@ -116,9 +121,8 @@ export class SourceTransformer { invokeExpr.getArgs().forEach(v => { args.push(this.valueToString(v)); }); - let genericCode = this.genericTypesToString(invokeExpr.getRealGenericTypes()); - - if (PrinterUtils.isComponentAttributeInvoke(invokeExpr) && this.context.isInBuilderMethod()) { + let genericCode = isAttr ? '' : this.genericTypesToString(invokeExpr.getRealGenericTypes()); + if (isAttr && this.context.isInBuilderMethod()) { return `.${methodName}${genericCode}(${args.join(', ')})`; } @@ -227,7 +231,8 @@ export class SourceTransformer { private exprToString(expr: AbstractExpr): string { if (expr instanceof ArkInstanceInvokeExpr) { - return `${this.instanceInvokeExprToString(expr)}`; + const isAttr = PrinterUtils.isComponentAttributeInvoke(expr); + return `${this.instanceInvokeExprToString(expr, isAttr)}`; } if (expr instanceof ArkStaticInvokeExpr) { @@ -251,7 +256,7 @@ export class SourceTransformer { let op2: Value = expr.getOp2(); let operator: string = expr.getOperator(); - return `${this.valueToString(op1, operator)} ${operator} ${this.valueToString(op2, operator)}`; + return `${this.valueToString(op1, false, operator)} ${operator} ${this.valueToString(op2, false, operator)}`; } if (expr instanceof ArkTypeOfExpr) { @@ -310,7 +315,7 @@ export class SourceTransformer { return `${value}`; } - public valueToString(value: Value, operator?: string): string { + public valueToString(value: Value, isLeftOp: boolean = false, operator?: string): string { if (value instanceof AbstractExpr) { return this.exprToString(value); } @@ -324,14 +329,14 @@ export class SourceTransformer { } if (value instanceof Local) { - return this.localToString(value, operator); + return this.localToString(value, isLeftOp, operator); } logger.info(`valueToString ${value.constructor} not support.`); return `${value}`; } - private localToString(value: Local, operator?: string): string { + private localToString(value: Local, isLeftOp: boolean = false, operator?: string): string { if (PrinterUtils.isAnonymousMethod(value.getName())) { let methodSignature = (value.getType() as FunctionType).getMethodSignature(); let anonymousMethod = this.context.getMethod(methodSignature); @@ -351,12 +356,12 @@ export class SourceTransformer { if (PrinterUtils.isTemp(value.getName())) { let stmt = value.getDeclaringStmt(); if (stmt instanceof ArkAssignStmt && stmt.getRightOp() instanceof ArkNormalBinopExpr) { - return `(${this.context.transTemp2Code(value)})`; + return `(${this.context.transTemp2Code(value, isLeftOp)})`; } } } - return this.context.transTemp2Code(value); + return this.context.transTemp2Code(value, isLeftOp); } public literalObjectToString(type: ClassType): string { diff --git a/ets2panda/linter/arkanalyzer/src/transformer/StaticSingleAssignmentFormer.ts b/ets2panda/linter/arkanalyzer/src/transformer/StaticSingleAssignmentFormer.ts index 2c7e78deec..1f1383c18c 100644 --- a/ets2panda/linter/arkanalyzer/src/transformer/StaticSingleAssignmentFormer.ts +++ b/ets2panda/linter/arkanalyzer/src/transformer/StaticSingleAssignmentFormer.ts @@ -73,7 +73,7 @@ export class StaticSingleAssignmentFormer { let phiBlocks = localToPhiBlock.get(local) as Set; let blocks = Array.from(localToBlocks.get(local) as Set); while (blocks.length !== 0) { - let block = blocks.splice(0, 1).at(0) as BasicBlock; + let block = blocks.splice(0, 1)[0] as BasicBlock; let dfs = dominanceFinder.getDominanceFrontiers(block); for (const df of dfs) { this.handleDf(blockToPhiStmts, blockToPhiLocals, phiBlocks, df, local, blockToDefs, blocks); diff --git a/ets2panda/linter/homecheck/ruleSet.json b/ets2panda/linter/homecheck/ruleSet.json index 517d135327..1dcacfee1b 100644 --- a/ets2panda/linter/homecheck/ruleSet.json +++ b/ets2panda/linter/homecheck/ruleSet.json @@ -209,6 +209,7 @@ "@migration/interop-assign": 1, "@migration/interop-boxed-type-check": 1, "@migration/interop-js-modify-property": 1, - "@migration/arkts-interop-s2d-object-literal": 1 + "@migration/arkts-interop-s2d-object-literal": 1, + "@migration/arkts-interop-s2d-dynamic-call-builtin-api-not-in-static": 1 } } \ No newline at end of file diff --git a/ets2panda/linter/homecheck/src/checker/migration/InteropBackwardDFACheck.ts b/ets2panda/linter/homecheck/src/checker/migration/InteropBackwardDFACheck.ts index 85a045b7f0..7f231f51fa 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/InteropBackwardDFACheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/InteropBackwardDFACheck.ts @@ -176,11 +176,14 @@ export class InteropBackwardDFACheck implements BaseChecker { const invoke = stmt.getInvokeExpr(); let isReflect = false; let paramIdx = -1; - if (invoke && invoke instanceof ArkInstanceInvokeExpr) { - if (invoke.getBase().getName() === 'Reflect') { + if (invoke && invoke instanceof ArkStaticInvokeExpr) { + const classSig = invoke.getMethodSignature().getDeclaringClassSignature(); + if ( + classSig.getDeclaringFileSignature().getProjectName() === 'built-in' && + classSig.getDeclaringNamespaceSignature()?.getNamespaceName() === 'Reflect' + ) { isReflect = true; - paramIdx = - REFLECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1; + paramIdx = REFLECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1; } } if (invoke && invoke instanceof ArkStaticInvokeExpr) { diff --git a/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts b/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts new file mode 100644 index 0000000000..b8af6a32c8 --- /dev/null +++ b/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts @@ -0,0 +1,928 @@ +/* + * Copyright (c) 2025 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 { + ArkMethod, + ArkAssignStmt, + FieldSignature, + Stmt, + Scene, + Value, + DVFGBuilder, + CallGraph, + ArkParameterRef, + ArkInstanceFieldRef, + ArkNamespace, + Local, + ClassType, + ArkField, + ClassSignature, + Type, + BooleanType, + FunctionType, + AnyType, + MethodSignature, + UnknownType, + GenericType, + NumberType, + ArrayType, + MethodSubSignature, + ArkInvokeStmt, + AbstractInvokeExpr, + ArkInstanceInvokeExpr, + ArkPtrInvokeExpr, + ImportInfo, + UnionType, + FileSignature, + ArkStaticInvokeExpr, + UndefinedType, + VoidType, +} from 'arkanalyzer/lib'; +import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; +import { BaseChecker, BaseMetaData } from '../BaseChecker'; +import { Rule, Defects, MatcherCallback } from '../../Index'; +import { IssueReport } from '../../model/Defects'; +import { DVFG, DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; +import { CALL_DEPTH_LIMIT, getGlobalsDefineInDefaultMethod, GlobalCallGraphHelper } from './Utils'; +import { WarnInfo } from '../../utils/common/Utils'; +import { ArkClass } from 'arkanalyzer/lib/core/model/ArkClass'; +import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; +import { MethodParameter } from 'arkanalyzer/lib/core/model/builder/ArkMethodBuilder'; + +const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'DeprecatedBuiltInAPICheck'); +const gMetaData: BaseMetaData = { + severity: 1, + ruleDocPath: '', + description: '', +}; + +enum APIBaseCategory { + Array = 'Array', + Set = 'Set', + Map = 'Map', +} + +class DeprecatedAPIInfo { + constructor( + public base: APIBaseCategory, + public name: string, + public isStatic: boolean, + public returnType?: Type, // return Type为undefined时表示不关心API的返回值 + public params?: Type[], // return Type为undefined时表示不关心API的形参,应该是整个API都被废弃,而非某一重载形态 + public targetParamIndex?: number // 若isStatic为tue,说明是静态接口,需要提供查找接口的第几个param是否来源arkts1.2,编号从0开始,不提供则查找base的来源 + ) {} +} + +class APIBasicType { + static readonly genericTypeT = new GenericType('T'); + static readonly genericTypeU = new GenericType('U'); + static readonly genericTypeV = new GenericType('V'); + static readonly genericTypeK = new GenericType('K'); + static readonly genericTypeS = new GenericType('S'); + static readonly arrayT = new ArrayType(this.genericTypeT, 1); + static readonly arrayU = new ArrayType(this.genericTypeU, 1); + static readonly unknownType = UnknownType.getInstance(); + static readonly numberType = NumberType.getInstance(); + static readonly booleanType = BooleanType.getInstance(); + static readonly anyType = AnyType.getInstance(); + static readonly undefinedType = UndefinedType.getInstance(); + static readonly voidType = VoidType.getInstance(); +} + +class APIComplicatedType { + static readonly anonyMethodName = 'anonymous'; + static readonly predicateFunctionType = this.createPredicateFunctionType(); + static readonly predicateFunction1Type = this.createPredicateFunction1Type(); + static readonly concatItemType = this.createConcatItemType(); + static readonly mapfnFunctionType = this.createMapfnFunctionType(); + static readonly ArrayLikeType = new ClassType(new ClassSignature('ArrayLike', FileSignature.DEFAULT)); + static readonly IterableType = new ClassType(new ClassSignature('Iterable', FileSignature.DEFAULT)); + static readonly SetType = new ClassType(new ClassSignature('Set', FileSignature.DEFAULT)); + static readonly MapType = new ClassType(new ClassSignature('Map', FileSignature.DEFAULT)); + static readonly setCallbackFnFunctionType = this.createSetCallbackFnFunctionType(); + static readonly mapCallbackFnFunctionType = this.createMapCallbackFnFunctionType(); + + // 对于参数类型一致,但是参数名称不同、返回值类型不同的FunctionType,视为同一个,不重新创建。因为FunctionType类型匹配的时候仅匹配参数类型 + // 不同的API,仅形参为lambda函数且该lambda函数的返回值不同,例如unknown和value is S类型谓词形式,视为同一个API + private static createPredicateFunctionType(): FunctionType { + const predicateValueParam = new MethodParameter(); + predicateValueParam.setName('value'); + predicateValueParam.setType(APIBasicType.genericTypeT); + const predicateIndexParam = new MethodParameter(); + predicateIndexParam.setName('index'); + predicateIndexParam.setType(APIBasicType.numberType); + const predicateArrayParam = new MethodParameter(); + predicateArrayParam.setName('array'); + predicateArrayParam.setType(APIBasicType.arrayT); + const predicateMethodSubSignature = new MethodSubSignature( + this.anonyMethodName, + [predicateValueParam, predicateIndexParam, predicateArrayParam], + APIBasicType.unknownType + ); + return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); + } + + private static createPredicateFunction1Type(): FunctionType { + const predicateThisParam = new MethodParameter(); + predicateThisParam.setName('this'); + predicateThisParam.setType(APIBasicType.voidType); + const predicateValueParam = new MethodParameter(); + predicateValueParam.setName('value'); + predicateValueParam.setType(APIBasicType.genericTypeT); + const predicateIndexParam = new MethodParameter(); + predicateIndexParam.setName('index'); + predicateIndexParam.setType(APIBasicType.numberType); + const predicateObjParam = new MethodParameter(); + predicateObjParam.setName('obj'); + predicateObjParam.setType(APIBasicType.arrayT); + const predicateMethodSubSignature = new MethodSubSignature( + this.anonyMethodName, + [predicateThisParam, predicateValueParam, predicateIndexParam, predicateObjParam], + APIBasicType.booleanType + ); + return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); + } + + private static createConcatItemType(): UnionType { + return new UnionType([APIBasicType.genericTypeT, new ClassType(new ClassSignature('ConcatArray', FileSignature.DEFAULT))]); + } + + private static createMapfnFunctionType(): FunctionType { + const mapfnParamV = new MethodParameter(); + mapfnParamV.setName('v'); + mapfnParamV.setType(APIBasicType.genericTypeT); + const mapfnParamK = new MethodParameter(); + mapfnParamK.setName('k'); + mapfnParamK.setType(APIBasicType.numberType); + const mapfnMethodSubSignature = new MethodSubSignature(this.anonyMethodName, [mapfnParamV, mapfnParamK], APIBasicType.genericTypeU); + return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, mapfnMethodSubSignature)); + } + + private static createSetCallbackFnFunctionType(): FunctionType { + const callbackFnValueParam = new MethodParameter(); + callbackFnValueParam.setName('value'); + callbackFnValueParam.setType(APIBasicType.genericTypeT); + const callbackFnValue2Param = new MethodParameter(); + callbackFnValue2Param.setName('value2'); + callbackFnValue2Param.setType(APIBasicType.genericTypeT); + const callbackFnSetParam = new MethodParameter(); + callbackFnSetParam.setName('set'); + callbackFnSetParam.setType(this.SetType); + const predicateMethodSubSignature = new MethodSubSignature( + this.anonyMethodName, + [callbackFnValueParam, callbackFnValue2Param, callbackFnSetParam], + APIBasicType.voidType + ); + return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); + } + + private static createMapCallbackFnFunctionType(): FunctionType { + const callbackFnValueParam = new MethodParameter(); + callbackFnValueParam.setName('value'); + callbackFnValueParam.setType(APIBasicType.genericTypeV); + const callbackFnKeyParam = new MethodParameter(); + callbackFnKeyParam.setName('key'); + callbackFnKeyParam.setType(APIBasicType.genericTypeK); + const callbackFnMapParam = new MethodParameter(); + callbackFnMapParam.setName('map'); + callbackFnMapParam.setType(this.MapType); + const predicateMethodSubSignature = new MethodSubSignature( + this.anonyMethodName, + [callbackFnValueParam, callbackFnKeyParam, callbackFnMapParam], + APIBasicType.voidType + ); + return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); + } +} + +class DeprecatedAPIList { + static readonly DeprecatedAPIs: DeprecatedAPIInfo[] = [ + this.createArrayEveryAPI1(), + this.createArrayFilterAPI1(), + this.createArrayFindAPI1(), + this.createArrayFindAPI2(), + this.createArrayFindIndexAPI(), + this.createArrayForEachAPI(), + this.createArrayMapAPI(), + this.createArraySomeAPI(), + this.createArrayConcatAPI(), + this.createArrayFlatAPI(), + this.createArrayFlatMapAPI(), + this.createArrayFromArrayLikeAPI(), + this.createArrayFromIterableAndArrayLikeAPI(), + this.createSetForEachAPI(), + this.createMapForEachAPI(), + this.createArraySymbolIteratorAPI(), + this.createSetSymbolIteratorAPI(), + this.createMapSymbolIteratorAPI() + ]; + + private static createArrayEveryAPI1(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'every', false, APIBasicType.booleanType, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayFilterAPI1(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'filter', false, APIBasicType.arrayT, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayFindAPI1(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'find', false, new UnionType([APIBasicType.genericTypeT, APIBasicType.undefinedType]), [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayFindAPI2(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'find', false, new UnionType([APIBasicType.genericTypeS, APIBasicType.undefinedType]), [ + APIComplicatedType.predicateFunction1Type, + APIBasicType.anyType, + ]); + } + + private static createArrayFindIndexAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'findIndex', false, APIBasicType.numberType, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayForEachAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'forEach', false, APIBasicType.voidType, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayMapAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'map', false, APIBasicType.arrayU, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArraySomeAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'some', false, APIBasicType.booleanType, [ + APIComplicatedType.predicateFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArrayConcatAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'concat', false, APIBasicType.arrayT, [new ArrayType(APIComplicatedType.concatItemType, 1)]); + } + + private static createArrayFlatAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'flat', false); + } + + private static createArrayFlatMapAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'flatMap', false); + } + + private static createArrayFromArrayLikeAPI(): DeprecatedAPIInfo { + const fromParams = [APIComplicatedType.ArrayLikeType, APIComplicatedType.mapfnFunctionType, APIBasicType.anyType]; + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'from', true, APIBasicType.arrayU, fromParams, 0); + } + + private static createArrayFromIterableAndArrayLikeAPI(): DeprecatedAPIInfo { + const fromParams = [ + new UnionType([APIComplicatedType.ArrayLikeType, APIComplicatedType.IterableType]), + APIComplicatedType.mapfnFunctionType, + APIBasicType.anyType, + ]; + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'from', true, APIBasicType.arrayU, fromParams, 0); + } + + private static createSetForEachAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Set, 'forEach', false, APIBasicType.voidType, [ + APIComplicatedType.setCallbackFnFunctionType, + APIBasicType.anyType, + ]); + } + + private static createMapForEachAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Map, 'forEach', false, APIBasicType.voidType, [ + APIComplicatedType.mapCallbackFnFunctionType, + APIBasicType.anyType, + ]); + } + + private static createArraySymbolIteratorAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Array, 'Symbol.iterator', false); + } + + private static createSetSymbolIteratorAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Set, 'Symbol.iterator', false); + } + + private static createMapSymbolIteratorAPI(): DeprecatedAPIInfo { + return new DeprecatedAPIInfo(APIBaseCategory.Map, 'Symbol.iterator', false); + } +} + +export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { + readonly metaData: BaseMetaData = gMetaData; + public rule: Rule; + public defects: Defects[] = []; + public issues: IssueReport[] = []; + private cg: CallGraph; + private dvfg: DVFG; + private dvfgBuilder: DVFGBuilder; + private scene: Scene; + private visited: Set = new Set(); + + public registerMatchers(): MatcherCallback[] { + const matchBuildCb: MatcherCallback = { + matcher: undefined, + callback: this.check, + }; + return [matchBuildCb]; + } + + public check = (scene: Scene): void => { + this.scene = scene; + this.cg = GlobalCallGraphHelper.getCGInstance(scene); + + this.dvfg = new DVFG(this.cg); + this.dvfgBuilder = new DVFGBuilder(this.dvfg, scene); + + for (let arkFile of scene.getFiles()) { + // 此规则仅对arkts1.1进行检查 + if (!(arkFile.getLanguage() === Language.ARKTS1_1)) { + continue; + } + const defaultMethod = arkFile.getDefaultClass().getDefaultArkMethod(); + let globalVarMap: Map = new Map(); + if (defaultMethod) { + this.dvfgBuilder.buildForSingleMethod(defaultMethod); + globalVarMap = getGlobalsDefineInDefaultMethod(defaultMethod); + } + for (let arkClass of arkFile.getClasses()) { + this.processArkClass(arkClass, globalVarMap); + } + for (let namespace of arkFile.getAllNamespacesUnderThisFile()) { + this.processNameSpace(namespace, globalVarMap); + } + } + }; + + public processArkClass(arkClass: ArkClass, globalVarMap: Map): void { + for (let field of arkClass.getFields()) { + this.processClassField(field, globalVarMap); + } + for (let mtd of arkClass.getMethods()) { + this.processArkMethod(mtd, globalVarMap); + } + } + + public processNameSpace(namespace: ArkNamespace, globalVarMap: Map): void { + for (let ns of namespace.getNamespaces()) { + this.processNameSpace(ns, globalVarMap); + } + for (let arkClass of namespace.getClasses()) { + this.processArkClass(arkClass, globalVarMap); + } + } + + public processClassField(field: ArkField, globalVarMap: Map): void { + const stmts = field.getInitializer(); + for (const stmt of stmts) { + const invokeExpr = this.getInvokeExpr(stmt); + if (invokeExpr === null) { + continue; + } + } + } + + public processArkMethod(target: ArkMethod, globalVarMap: Map): void { + const stmts = target.getBody()?.getCfg().getStmts() ?? []; + for (const stmt of stmts) { + // 查找到DeprecatedAPIs中的builtIn的API调用语句为sink点,从该点开始进行逆向数据流分析,分析base是否为arkts1.2中声明的对象实例或其引用 + const targetLocal = this.getTargetLocalOfDeprecatedAPICall(stmt); + if (targetLocal === null) { + continue; + } + + // 从base的最近一次赋值语句开始,使用逆向数据流进行查找 + let checkStmt = this.getLastAssignStmt(targetLocal, stmt); + if (checkStmt === null) { + checkStmt = this.checkTargetLocalAsGlobal(target, stmt, targetLocal, globalVarMap); + } + if (checkStmt === null) { + continue; + } + + if (!this.visited.has(target)) { + this.dvfgBuilder.buildForSingleMethod(target); + this.visited.add(target); + } + + let checkAll = { value: true }; + let visited: Set = new Set(); + if (this.checkFromStmt(checkStmt, globalVarMap, checkAll, visited)) { + this.addIssueReport(stmt, targetLocal); + } + } + } + + private checkTargetLocalAsGlobal(targetMethod: ArkMethod, stmt: Stmt, targetLocal: Local, globalVarMap: Map): Stmt | null { + const globalDefs = globalVarMap.get(targetLocal.getName()); + if (globalDefs === undefined) { + const importInfos = targetMethod.getDeclaringArkClass().getDeclaringArkFile().getImportInfos(); + const importValue = this.isLocalFromImport(targetLocal, importInfos); + if (importValue && importValue.getDeclaringStmt() !== null) { + return importValue.getDeclaringStmt()!; + } + return null; + } + let lastLegalStmtLine = -1; + let lastIllegalStmtLine = -1; + for (const defStmt of globalDefs) { + if (!this.visited.has(targetMethod)) { + this.dvfgBuilder.buildForSingleMethod(targetMethod); + this.visited.add(targetMethod); + } + + let checkAll = { value: true }; + let visited: Set = new Set(); + + const currStmtLine = defStmt.getOriginPositionInfo().getLineNo(); + if (this.checkFromStmt(defStmt, globalVarMap, checkAll, visited)) { + if (currStmtLine > lastIllegalStmtLine) { + lastIllegalStmtLine = currStmtLine; + } + } else { + if (currStmtLine > lastLegalStmtLine) { + lastLegalStmtLine = currStmtLine; + } + } + } + if (lastIllegalStmtLine > lastLegalStmtLine) { + this.addIssueReport(stmt, targetLocal); + } + return null; + } + + private getLastAssignStmt(local: Local, currStmt: Stmt): Stmt | null { + // 获取local变量在currStmt使用语句之前的最近一次赋值/声明语句,未找到表示直接来自于全局变量或import信息,则返回null + const usedStmts = local.getUsedStmts(); + let currLine = currStmt.getOriginPositionInfo().getLineNo(); + let lastAssignStmt = local.getDeclaringStmt(); + for (const stmt of usedStmts) { + if (stmt === currStmt || !(stmt instanceof ArkAssignStmt) || stmt.getLeftOp() !== local) { + continue; + } + const line = stmt.getOriginPositionInfo().getLineNo(); + if (line < currLine) { + lastAssignStmt = stmt; + currLine = line; + } + } + return lastAssignStmt; + } + + private isLocalDefinedInStaticArkTS(local: Local): boolean { + return local.getDeclaringStmt()?.getCfg().getDeclaringMethod().getLanguage() === Language.ARKTS1_2; + } + + // 判断语句是否为废弃API接口的调用语句,若废弃接口仅为一系列重载中的某一种,需要判断这一具体重载形态,若是返回对应需要查找的Local对象,否则返回null + private getTargetLocalOfDeprecatedAPICall(stmt: Stmt): Local | null { + const invokeExpr = this.getInvokeExpr(stmt); + if (invokeExpr === null) { + return null; + } + if (invokeExpr instanceof ArkInstanceInvokeExpr) { + const base = invokeExpr.getBase(); + if (this.isInstanceCallMethodInDeprecatedAPIs(base, stmt, invokeExpr.getMethodSignature(), invokeExpr.getArgs())) { + return base; + } + // instance invoke未匹配到,继续匹配静态调用。Array.from的API调用ArkAnalyzer也表示为ArkInstanceInvokeExpr,因为API定义里没有明确的static标识。 + return this.getTargetValueInStaticInvokeWithDeprecatedAPIs(invokeExpr); + } else if (invokeExpr instanceof ArkPtrInvokeExpr) { + // TODO:可能存在ptr invoke的场景吗? + return null; + } else if (invokeExpr instanceof ArkStaticInvokeExpr) { + return null; + } + return null; + } + + private getInvokeExpr(stmt: Stmt): AbstractInvokeExpr | null { + if (!(stmt instanceof ArkAssignStmt) && !(stmt instanceof ArkInvokeStmt)) { + return null; + } + + if (stmt instanceof ArkInvokeStmt) { + return stmt.getInvokeExpr(); + } + + const rightOp = stmt.getRightOp(); + if (rightOp instanceof AbstractInvokeExpr) { + return rightOp; + } + return null; + } + + private compareParamTypes(apiParams: Type[], callApiParams: MethodParameter[]): boolean { + if (apiParams.length !== callApiParams.length) { + return false; + } + for (let i = 0; i < apiParams.length; i++) { + if (!this.isTypeMatch(apiParams[i], callApiParams[i].getType())) { + return false; + } + } + return true; + } + + private getTargetValueInStaticInvokeWithDeprecatedAPIs(staticInvokeExpr: ArkStaticInvokeExpr): Local | null { + const callApiMethod = staticInvokeExpr.getMethodSignature(); + const callApiClass = callApiMethod.getDeclaringClassSignature(); + for (const api of DeprecatedAPIList.DeprecatedAPIs) { + if (!api.isStatic) { + continue; + } + if (api.name !== callApiMethod.getMethodSubSignature().getMethodName()) { + continue; + } + // Array.from 形式的调用,from方法实际的class为ArrayConstructor + if (api.base !== callApiClass.getClassName() && `${api.base}Constructor` !== callApiClass.getClassName()) { + continue; + } + // 在本条规则检查范围内的static API的调用一定是带参数的,并且其中某个参数即为需要进行进一步查找的value + if (api.params === undefined) { + continue; + } + if (this.compareParamTypes(api.params, callApiMethod.getMethodSubSignature().getParameters())) { + const args = staticInvokeExpr.getArgs(); + // 形参匹配的情况下,进一步比较传入的实参,因为当前废弃接口大多数为去掉any类型的第二个可选参数 + // TODO:这里需要考虑如何做的更通用 + if (args.length !== api.params.length) { + continue; + } + const index = api.targetParamIndex; + // 成功匹配到指定的API后,如果未提供下一步需要查找的目标param的index,则返回null。理论上不应该走到这个分支。 + if (index === undefined) { + logger.error(`Missing targetParamIndex, api: ${api.name}, category ${api.base}`); + return null; + } + + if (args.length <= index) { + logger.error(`Invalid targetParamIndex ${index}, totally invoke args size ${args.length}, api: ${api.name}, category ${api.base}`); + return null; + } + const target = args[index]; + if (target instanceof Local) { + return target; + } + logger.error(`Need to handle non-local target ${target.getType().getTypeString()}`); + return null; + } + } + return null; + } + + private isMatchSymbolIterator(apiName: string, callApiName: string, stmt: Stmt): boolean { + // 对于map[Symbol.iterator]这样的API,这里会存在%0 = Symbol.iterator的操作 + if (apiName !== 'Symbol.iterator' || !callApiName.startsWith('%')) { + return false; + } + const tempLocalDeclaring = stmt.getCfg().getDeclaringMethod().getBody()?.getLocals().get(callApiName)?.getDeclaringStmt(); + if (tempLocalDeclaring && tempLocalDeclaring instanceof ArkAssignStmt) { + const rightOp = tempLocalDeclaring.getRightOp(); + if (!(rightOp instanceof ArkInstanceFieldRef)) { + return false; + } + if (rightOp.getFieldName() === 'iterator' && rightOp.getBase().getName() === 'Symbol') { + return true; + } + } + return false; + } + + private isInstanceCallMethodInDeprecatedAPIs(callBase: Local, stmt: Stmt, callMethod: MethodSignature, args: Value[]): boolean { + const callApiName = callMethod.getMethodSubSignature().getMethodName(); + const callApiParams = callMethod.getMethodSubSignature().getParameters(); + for (const api of DeprecatedAPIList.DeprecatedAPIs) { + // 对于map[Symbol.iterator]这样的API调用,callApiName是临时变量,需要进一步匹配 + if (api.name !== callApiName) { + continue; + } + if (api.isStatic) { + continue; + } + if (!this.isBaseTypeMatchAPIBase(api.base, callBase)) { + continue; + } + + // Array concat API ArkAnalyzer当前无法很好处理...items形式的入参,此处作为特例处理 + if (api.name === 'concat') { + return this.isMatchArrayConcatAPI(args); + } + + const apiParams = api.params; + if (apiParams === undefined) { + return true; + } + let allParamTypeMatch = true; + if (apiParams.length !== callApiParams.length) { + allParamTypeMatch = false; + } else { + for (let i = 0; i < apiParams.length; i++) { + if (!this.isTypeMatch(apiParams[i], callApiParams[i].getType())) { + allParamTypeMatch = false; + break; + } + } + } + + if (allParamTypeMatch) { + // 形参匹配的情况下,进一步比较传入的实参,因为当前废弃接口大多数为去掉any类型的第二个可选参数 + // TODO:这里需要考虑如何做的更通用 + if (args.length !== apiParams.length) { + continue; + } + return true; + } + // 形参类型不匹配的情形,可能是由于ArkAnalyzer的类型推导未能找到正确的API,需要根据实参类型进行二次匹配 + if (apiParams.length !== args.length) { + continue; + } + allParamTypeMatch = true; + for (let i = 0; i < apiParams.length; i++) { + // 对于lambda函数作为参数类型,此处不严格校验lambda的参数类型,仅判断是否为FunctionType + if (apiParams[i] instanceof FunctionType && args[i].getType() instanceof FunctionType) { + continue; + } + if (!this.isTypeMatch(apiParams[i], args[i].getType())) { + allParamTypeMatch = false; + break; + } + } + if (allParamTypeMatch) { + return true; + } + } + return false; + } + + // 判断入参是否都为数组,不允许有单个元素 + private isMatchArrayConcatAPI(args: Value[]): boolean { + for (const arg of args) { + if (!(arg.getType() instanceof ArrayType)) { + return true; + } + } + return false; + } + + private isTypeMatch(apiType: Type, callApiType: Type): boolean { + const apiTypeStr = apiType.getTypeString(); + const callApiTypeStr = callApiType.getTypeString(); + if (callApiType instanceof FunctionType && apiType instanceof FunctionType) { + // 若类型为FunctionType,仅需匹配string中的形参部分 + const regex = /\(([^()]*)\)/; + const apiMatch = apiTypeStr.match(regex); + const callApiMatch = callApiTypeStr.match(regex); + if (apiMatch === null || callApiMatch === null) { + return false; + } + return apiMatch[0] === callApiMatch[0]; + } else if (callApiType instanceof ClassType && apiType instanceof ClassType) { + // 若类型为FunctionType,仅需匹配class name,因为apiTypeStr类型推导后有可能为@%unk/%unk: ArrayLike,而callApiTypeStr有明确的declaring file + return callApiType.getClassSignature().getClassName() === apiType.getClassSignature().getClassName(); + } else if (apiType instanceof AnyType) { + return true; + } else { + // 其他场景需严格判断字符串相等 + return apiTypeStr === callApiTypeStr; + } + } + + private isBaseTypeMatchAPIBase(apiBase: APIBaseCategory, callBase: Local): boolean { + if (apiBase === APIBaseCategory.Array && callBase.getType() instanceof ArrayType) { + return true; + } + if (apiBase === APIBaseCategory.Map) { + const callBaseType = callBase.getType(); + return callBaseType instanceof ClassType && callBaseType.getClassSignature().getClassName() === 'Map'; + } + if (apiBase === APIBaseCategory.Set) { + const callBaseType = callBase.getType(); + return callBaseType instanceof ClassType && callBaseType.getClassSignature().getClassName() === 'Set'; + } + return false; + } + + private checkFromStmt(stmt: Stmt, globalVarMap: Map, checkAll: { value: boolean }, visited: Set, depth: number = 0): boolean { + if (depth > CALL_DEPTH_LIMIT) { + checkAll.value = false; + return false; + } + const node = this.dvfg.getOrNewDVFGNode(stmt); + let worklist: DVFGNode[] = [node]; + while (worklist.length > 0) { + const current = worklist.shift()!; + const currentStmt = current.getStmt(); + if (visited.has(currentStmt)) { + continue; + } + visited.add(currentStmt); + + if (this.isLeftOpDefinedInStaticArkTS(currentStmt)) { + return true; + } + + const gv = this.isRightOpGlobalVar(currentStmt); + if (gv) { + const globalDefs = globalVarMap.get(gv.getName()); + if (globalDefs === undefined) { + const importInfos = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getImportInfos(); + const importValue = this.isLocalFromImport(gv, importInfos); + if (importValue && importValue.getDeclaringStmt() !== null) { + worklist.push(this.dvfg.getOrNewDVFGNode(importValue.getDeclaringStmt()!)); + } + } else { + globalDefs.forEach(d => worklist.push(this.dvfg.getOrNewDVFGNode(d))); + } + continue; + } + + const callsite = this.cg.getCallSiteByStmt(currentStmt); + for (const cs of callsite) { + const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); + if (!declaringMtd || !declaringMtd.getCfg()) { + continue; + } + if (!this.visited.has(declaringMtd)) { + this.dvfgBuilder.buildForSingleMethod(declaringMtd); + this.visited.add(declaringMtd); + } + const returnStmts = declaringMtd.getReturnStmt(); + for (const stmt of returnStmts) { + const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); + if (res) { + return true; + } + } + } + const paramRef = this.isFromParameter(currentStmt); + if (paramRef) { + const paramIdx = paramRef.getIndex(); + const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); + this.processCallsites(callsites); + const argDefs = this.collectArgDefs(paramIdx, callsites); + for (const stmt of argDefs) { + const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); + if (res) { + return true; + } + } + } + current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); + } + return false; + } + + private isRightOpGlobalVar(stmt: Stmt): Local | undefined { + if (stmt instanceof ArkAssignStmt) { + const rightOp = stmt.getRightOp(); + if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) { + return rightOp; + } + } + return undefined; + } + + private isLocalFromImport(local: Local, importInfos: ImportInfo[]): Local | undefined { + for (const importInfo of importInfos) { + if (importInfo.getImportClauseName() === local.getName()) { + const exportInfo = importInfo.getLazyExportInfo(); + if (exportInfo === null) { + return undefined; + } + const arkExport = exportInfo.getArkExport(); + if (arkExport === null || arkExport === undefined) { + return undefined; + } + if (!(arkExport instanceof Local)) { + return undefined; + } + return arkExport; + } + } + return undefined; + } + + private processCallsites(callsites: Stmt[]): void { + callsites.forEach(cs => { + const declaringMtd = cs.getCfg().getDeclaringMethod(); + if (!this.visited.has(declaringMtd)) { + this.dvfgBuilder.buildForSingleMethod(declaringMtd); + this.visited.add(declaringMtd); + } + }); + } + + // 判断语句是否为赋值语句,且左值的定义来自于ArkTS1.2 + private isLeftOpDefinedInStaticArkTS(stmt: Stmt): boolean { + if (!(stmt instanceof ArkAssignStmt)) { + return false; + } + const leftOp = stmt.getLeftOp(); + if (!(leftOp instanceof Local)) { + return false; + } + return this.isLocalDefinedInStaticArkTS(leftOp); + } + + private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { + if (!(stmt instanceof ArkAssignStmt)) { + return undefined; + } + const rightOp = stmt.getRightOp(); + if (rightOp instanceof ArkParameterRef) { + return rightOp; + } + return undefined; + } + + private collectArgDefs(argIdx: number, callsites: Stmt[]): Stmt[] { + const getKey = (v: Value): Value | FieldSignature => { + return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; + }; + return callsites.flatMap(callsite => { + const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx)); + let refs = Array.from(this.dvfg.getOrNewDVFGNode(callsite).getIncomingEdge()) + .map(e => (e.getSrcNode() as DVFGNode).getStmt()) + .filter(s => { + return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp()); + }); + // 以上步骤未找到defs语句,说明入参变量来源自import信息 + if (refs.length === 0 && target instanceof Local) { + const importInfos = callsite.getCfg().getDeclaringMethod().getDeclaringArkFile().getImportInfos(); + const importValue = this.isLocalFromImport(target, importInfos); + if (importValue && importValue.getDeclaringStmt() !== null) { + return importValue.getDeclaringStmt()!; + } + } + return refs; + }); + } + + private addIssueReport(stmt: Stmt, operand: Value): void { + const severity = this.rule.alert ?? this.metaData.severity; + const warnInfo = this.getLineAndColumn(stmt, operand); + const problem = 'builtin-api'; + const desc = `Builtin API is not support in ArkTS1.2 (${this.rule.ruleId.replace('@migration/', '')})`; + + let defects = new Defects( + warnInfo.line, + warnInfo.startCol, + warnInfo.endCol, + problem, + desc, + severity, + this.rule.ruleId, + warnInfo.filePath, + this.metaData.ruleDocPath, + true, + false, + false + ); + this.issues.push(new IssueReport(defects, undefined)); + } + + private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo { + const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); + const originPosition = stmt.getOperandOriginalPosition(operand); + if (arkFile && originPosition) { + const originPath = arkFile.getFilePath(); + const line = originPosition.getFirstLine(); + const startCol = originPosition.getFirstCol(); + const endCol = startCol; + return { line, startCol, endCol, filePath: originPath }; + } else { + logger.debug('ArkFile is null.'); + } + return { line: -1, startCol: -1, endCol: -1, filePath: '' }; + } +} diff --git a/ets2panda/linter/homecheck/src/utils/common/CheckerIndex.ts b/ets2panda/linter/homecheck/src/utils/common/CheckerIndex.ts index 99fbd03f1d..9e43517c0f 100644 --- a/ets2panda/linter/homecheck/src/utils/common/CheckerIndex.ts +++ b/ets2panda/linter/homecheck/src/utils/common/CheckerIndex.ts @@ -28,6 +28,7 @@ import { InteropAssignCheck } from '../../checker/migration/InteropAssignCheck'; import { InteropJSModifyPropertyCheck } from '../../checker/migration/InteropJSModifyPropertyCheck'; import { NoTSLikeAsCheck } from '../../checker/migration/NoTSLikeAsCheck'; import { InteropS2DObjectLiteralCheck } from '../../checker/migration/InteropS2DObjectLiteralsCheck'; +import { InteropDeprecatedBuiltInAPICheck } from '../../checker/migration/InteropDeprecatedBuiltInAPICheck'; const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'CheckerIndex'); @@ -49,6 +50,7 @@ export const projectRules = { '@migration/interop-js-modify-property': InteropJSModifyPropertyCheck, '@migration/interop-dynamic-object-literals': InteropObjectLiteralCheck, '@migration/arkts-no-ts-like-as': NoTSLikeAsCheck, + '@migration/arkts-interop-s2d-dynamic-call-builtin-api-not-in-static': InteropDeprecatedBuiltInAPICheck, }; // 新增文件级的checker,需要在此处注册 -- Gitee From 1bd9be95153fd52f39815dbd2296e84d819317a5 Mon Sep 17 00:00:00 2001 From: xudan16 Date: Wed, 18 Jun 2025 19:26:47 +0800 Subject: [PATCH 2/3] fix linter merge problems with homecheck Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICG629 Signed-off-by: xudan16 --- ets2panda/linter/src/cli/LinterCLI.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/ets2panda/linter/src/cli/LinterCLI.ts b/ets2panda/linter/src/cli/LinterCLI.ts index e45d56a7d5..70309ac894 100644 --- a/ets2panda/linter/src/cli/LinterCLI.ts +++ b/ets2panda/linter/src/cli/LinterCLI.ts @@ -110,12 +110,25 @@ function mergeLintProblems( return onlyArkts2SyntaxRules.has(problem.ruleTag); }); } - if (cmdOptions.scanWholeProjectInHomecheck && !cmdOptions.inputFiles.includes(filePath)) { - filteredProblems = problems.filter((problem) => { - return problem.rule.includes('s2d'); - }); - } mergedProblems.get(filePath)!.push(...filteredProblems); + + if (cmdOptions.scanWholeProjectInHomecheck) { + for (const file of mergedProblems.keys()) { + if (cmdOptions.inputFiles.includes(filePath)) { + continue; + } + const totalProblems = mergedProblems.get(file); + if (totalProblems === undefined) { + continue; + } + filteredProblems = totalProblems.filter(problem => problem.rule.includes('s2d')); + if (filteredProblems.length > 0) { + mergedProblems.set(file, filteredProblems); + } else { + mergedProblems.delete(file); + } + } + } } async function generateReportFile(reportData, reportPath?: string): Promise { -- Gitee From 2c6f42a926183ee062748cd36ffb41a7c5f31e31 Mon Sep 17 00:00:00 2001 From: xudan16 Date: Wed, 18 Jun 2025 21:12:23 +0800 Subject: [PATCH 3/3] Add tsc as bundle dependency of arkanalyzer Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICG6KE Signed-off-by: xudan16 --- ets2panda/linter/arkanalyzer/package.json | 5 ++++- ets2panda/linter/build_linter.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ets2panda/linter/arkanalyzer/package.json b/ets2panda/linter/arkanalyzer/package.json index a9d42adc13..36e27e974c 100644 --- a/ets2panda/linter/arkanalyzer/package.json +++ b/ets2panda/linter/arkanalyzer/package.json @@ -21,5 +21,8 @@ "commander": "^9.4.0", "log4js": "^6.4.0", "json5": "2.2.3" - } + }, + "bundleDependencies": [ + "ohos-typescript" + ] } diff --git a/ets2panda/linter/build_linter.py b/ets2panda/linter/build_linter.py index 3634b84a25..7486c36eeb 100755 --- a/ets2panda/linter/build_linter.py +++ b/ets2panda/linter/build_linter.py @@ -197,9 +197,9 @@ def pack_arkanalyzer(options, new_npm): clean_old_packages(aa_path, pack_prefix, pack_suffix) if new_npm: - ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file, '--legacy-peer-deps', '--offline'] + ts_install_cmd = [options.npm, 'install', tsc_file, '--legacy-peer-deps', '--offline'] else: - ts_install_cmd = [options.npm, 'install', '--no-save', tsc_file] + ts_install_cmd = [options.npm, 'install', tsc_file] compile_cmd = [options.npm, 'run', 'compile'] pack_cmd = [options.npm, 'pack'] run_cmd(ts_install_cmd, aa_path) -- Gitee