diff --git a/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts b/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts index fad6267bde2b7b6a0f46ae0b4de5c36d2662bd79..02b2b9170d73ce7c3f2d229094381ffd9c9317b7 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts @@ -59,7 +59,7 @@ 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'; -import { AbstractFieldRef } from 'arkanalyzer'; +import { AbstractFieldRef, ArkReturnStmt } from 'arkanalyzer'; const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'DeprecatedBuiltInAPICheck'); const gMetaData: BaseMetaData = { @@ -427,7 +427,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { let checkAll = { value: true }; let visited: Set = new Set(); if (this.checkFromStmt(checkStmt, globalVarMap, checkAll, visited)) { - this.addIssueReport(stmt, targetLocal); + this.addIssueReport(stmt, targetLocal, checkAll.value); } } } @@ -654,6 +654,10 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { if (api.name !== callApiName) { continue; } + // 对于for...of的语句,ArkAnalyzer会为其生成Symbol.iterator的调用语句,此处从源码中查找关键字以区分是源码中有还是自动生成的 + if (api.name === 'Symbol.iterator' && !stmt.getOriginalText()?.includes('Symbol.iterator')) { + continue; + } if (api.isStatic) { continue; } @@ -662,7 +666,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } // Array concat API ArkAnalyzer当前无法很好处理...items形式的入参,此处作为特例处理 - if (api.name === 'concat') { + if (api.name === 'concat' && api.base === APIBaseCategory.Array) { return this.isMatchArrayConcatAPI(args); } @@ -670,18 +674,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { 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; - } - } - } - + let allParamTypeMatch = this.compareParamTypes(apiParams, callApiParams); if (allParamTypeMatch) { // 形参匹配的情况下,进一步比较传入的实参,因为当前废弃接口大多数为去掉any类型的第二个可选参数 // TODO:这里需要考虑如何做的更通用 @@ -733,7 +726,10 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { if (apiMatch === null || callApiMatch === null) { return false; } - return apiMatch[0] === callApiMatch[0]; + // 移除字符串中的类型的文件签名、类签名、泛型等信息后进行比较 + let apiParamsStr = apiMatch[0].replace(/@[^:]+:/, '').replace(/<[^>]+>/, ''); + let callApiParamsStr = callApiMatch[0].replace(/@[^:]+:/, '').replace(/<[^>]+>/, ''); + return apiParamsStr === callApiParamsStr; } 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(); @@ -763,7 +759,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { 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; + return true; } const node = this.dvfg.getOrNewDVFGNode(stmt); let worklist: DVFGNode[] = [node]; @@ -775,10 +771,11 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } visited.add(currentStmt); - if (this.isLeftOpDefinedInStaticArkTS(currentStmt)) { + if (this.isLeftOpOrReturnOpDefinedInStaticArkTS(currentStmt)) { return true; } + // 当前语句的右值是全局变量,查找全局变量的定义语句 const gv = this.isRightOpGlobalVar(currentStmt); if (gv) { const globalDefs = globalVarMap.get(gv.getName()); @@ -794,6 +791,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { continue; } + // 当前语句的右值是函数返回值,查找调用函数的所有return语句 const callsite = this.cg.getCallSiteByStmt(currentStmt); for (const cs of callsite) { const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); @@ -812,6 +810,8 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } } } + + // 当前语句的右值是函数参数赋值语句,查找所有调用处的入参情况 const paramRef = this.isFromParameter(currentStmt); if (paramRef) { const paramIdx = paramRef.getIndex(); @@ -826,6 +826,7 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } } + // 当前语句的右值是属性赋值语句,查找该属性的初始化语句 if (currentStmt instanceof ArkAssignStmt && currentStmt.getRightOp() instanceof AbstractFieldRef) { const fieldSignature = (currentStmt.getRightOp() as AbstractFieldRef).getFieldSignature(); const classSignature = fieldSignature.getDeclaringSignature(); @@ -836,6 +837,19 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } } } + + // 当前语句是return语句,查找return操作数的相关语句 + if (currentStmt instanceof ArkReturnStmt) { + const returnOp = currentStmt.getOp(); + if (returnOp instanceof Local) { + let checkStmt = + this.getLastAssignStmt(returnOp, stmt) ?? + this.checkTargetLocalAsGlobal(currentStmt.getCfg().getDeclaringMethod(), stmt, returnOp, globalVarMap); + if (checkStmt !== null) { + worklist.push(this.dvfg.getOrNewDVFGNode(checkStmt)); + } + } + } current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); } return false; @@ -882,15 +896,20 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { } // 判断语句是否为赋值语句,且左值的定义来自于ArkTS1.2 - private isLeftOpDefinedInStaticArkTS(stmt: Stmt): boolean { - if (!(stmt instanceof ArkAssignStmt)) { + private isLeftOpOrReturnOpDefinedInStaticArkTS(stmt: Stmt): boolean { + if (!(stmt instanceof ArkAssignStmt) && !(stmt instanceof ArkReturnStmt)) { return false; } - const leftOp = stmt.getLeftOp(); - if (!(leftOp instanceof Local)) { + let operand: Value; + if (stmt instanceof ArkAssignStmt) { + operand = stmt.getLeftOp(); + } else { + operand = stmt.getOp(); + } + if (!(operand instanceof Local)) { return false; } - return this.isLocalDefinedInStaticArkTS(leftOp); + return this.isLocalDefinedInStaticArkTS(operand); } private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { @@ -927,11 +946,14 @@ export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { }); } - private addIssueReport(stmt: Stmt, operand: Value): void { + private addIssueReport(stmt: Stmt, operand: Value, checkAll: boolean = true): 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 desc = `Builtin API is not support in ArkTS1.2 (${this.rule.ruleId.replace('@migration/', '')})`; + if (!checkAll) { + desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${this.rule.ruleId.replace('@migration/', '')})`; + } let defects = new Defects( warnInfo.line, diff --git a/ets2panda/linter/homecheck/src/checker/migration/InteropDynamicObjectLiteralsCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/InteropDynamicObjectLiteralsCheck.ts index 3b59046d94a0f620aa22c6d27ffd8ef02c2addc3..9d93f7f9c648f6d3a3efd794d7f3eca38d49080b 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/InteropDynamicObjectLiteralsCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/InteropDynamicObjectLiteralsCheck.ts @@ -110,12 +110,11 @@ export class InteropObjectLiteralCheck implements BaseChecker { this.visited.add(target); } - let result: Stmt[] = []; let checkAll = { value: true }; let visited: Set = new Set(); - this.checkFromStmt(stmt, scene, result, checkAll, visited); + // 对于待检查的instanceof语句,其检查对象存在用字面量赋值的情况,需要判断对象声明时的类型注解的来源,满足interop场景时需在此处告警 - if (result.length > 0) { + if (this.checkFromStmt(stmt, scene, checkAll, visited)) { const opType = rightOp.getOp().getType(); if (!(opType instanceof ClassType)) { continue; @@ -124,27 +123,17 @@ export class InteropObjectLiteralCheck implements BaseChecker { if (opTypeClass === null || opTypeClass.getCategory() === ClassCategory.OBJECT) { continue; } - if ( - opTypeClass.getLanguage() === Language.TYPESCRIPT || - opTypeClass.getLanguage() === Language.ARKTS1_1 - ) { - this.addIssueReport(stmt, rightOp, result, opTypeClass.getLanguage()); + if (opTypeClass.getLanguage() === Language.TYPESCRIPT || opTypeClass.getLanguage() === Language.ARKTS1_1) { + this.addIssueReport(stmt, rightOp, opTypeClass.getLanguage(), checkAll.value); } } } } - private checkFromStmt( - stmt: Stmt, - scene: Scene, - res: Stmt[], - checkAll: { value: boolean }, - visited: Set, - depth: number = 0 - ): void { + private checkFromStmt(stmt: Stmt, scene: Scene, checkAll: { value: boolean }, visited: Set, depth: number = 0): boolean { if (depth > CALL_DEPTH_LIMIT) { checkAll.value = false; - return; + return true; } const node = this.dvfg.getOrNewDVFGNode(stmt); let worklist: DVFGNode[] = [node]; @@ -156,36 +145,42 @@ export class InteropObjectLiteralCheck implements BaseChecker { } visited.add(currentStmt); if (this.isObjectLiteral(currentStmt, scene)) { - res.push(currentStmt); - continue; + return true; } const callsite = this.cg.getCallSiteByStmt(currentStmt); - callsite.forEach(cs => { + for (const cs of callsite) { const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); if (!declaringMtd || !declaringMtd.getCfg()) { - return; + return false; } if (!this.visited.has(declaringMtd)) { this.dvfgBuilder.buildForSingleMethod(declaringMtd); this.visited.add(declaringMtd); } - declaringMtd - .getReturnStmt() - .forEach(r => this.checkFromStmt(r, scene, res, checkAll, visited, depth + 1)); - }); + const returnStmts = declaringMtd.getReturnStmt(); + for (const stmt of returnStmts) { + const res = this.checkFromStmt(stmt, scene, 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() - ); + const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); this.processCallsites(callsites); - this.collectArgDefs(paramIdx, callsites).forEach(d => - this.checkFromStmt(d, scene, res, checkAll, visited, depth + 1) - ); + const argDefs = this.collectArgDefs(paramIdx, callsites); + for (const def of argDefs) { + const res = this.checkFromStmt(def, scene, checkAll, visited, depth + 1); + if (res) { + return true; + } + } } current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); } + return false; } private processCallsites(callsites: Stmt[]): void { @@ -235,7 +230,7 @@ export class InteropObjectLiteralCheck implements BaseChecker { }); } - private addIssueReport(stmt: Stmt, operand: Value, result: Stmt[], targetLanguage: Language): void { + private addIssueReport(stmt: Stmt, operand: Value, targetLanguage: Language, checkAll: boolean = true): void { const interopRuleId = this.getInteropRule(targetLanguage); if (interopRuleId === null) { return; @@ -244,10 +239,11 @@ export class InteropObjectLiteralCheck implements BaseChecker { const warnInfo = getLineAndColumn(stmt, operand); let targetLan = getLanguageStr(targetLanguage); - const resPos: number[] = []; - result.forEach(stmt => resPos.push(stmt.getOriginPositionInfo().getLineNo())); const problem = 'Interop'; - const desc = `instanceof including object literal with class type from ${targetLan} (${interopRuleId})`; + let desc = `instanceof including object literal with class type from ${targetLan} (${interopRuleId})`; + if (!checkAll) { + desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${interopRuleId})`; + } let defects = new Defects( warnInfo.line, warnInfo.startCol, diff --git a/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts index 3e6f594aa1aff68dc4c3412b80abd932b6702e5f..2cd9eab61a7242f82a46fa1258a490586bb96063 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts @@ -575,7 +575,7 @@ export class NoTSLikeAsCheck implements BaseChecker { let desc = `(${this.rule.ruleId.replace('@migration/', '')})`; if (relatedStmt === undefined) { - desc = `Can not find all assignments of the value in type assertion, please check it manually ` + desc; + desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually ` + desc; } else { const sinkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); const relatedFile = relatedStmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); diff --git a/ets2panda/linter/homecheck/src/checker/migration/ObjectLiteralCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/ObjectLiteralCheck.ts index f6b47c98c9cbda72cdce3a0162655cf3b16bc2f8..f0c756eeed65cc84bb277074184935aa415a0b5a 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/ObjectLiteralCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/ObjectLiteralCheck.ts @@ -46,7 +46,6 @@ const gMetaData: BaseMetaData = { ruleDocPath: '', description: 'Object literal shall generate instance of a specific class', }; -const INSTANCE_OF_DESC = 'The `instanceof` may work on an object literal, which now generates an instance of a specific class'; export class ObjectLiteralCheck implements BaseChecker { readonly metaData: BaseMetaData = gMetaData; @@ -102,7 +101,7 @@ export class ObjectLiteralCheck implements BaseChecker { this.checkFromStmt(stmt, scene, result, topLevelVarMap, checkAll, visited); result.forEach(s => this.addIssueReport(s, (s as ArkAssignStmt).getRightOp())); if (!checkAll.value) { - this.addIssueReport(stmt, rightOp, false); + this.addIssueReport(stmt, rightOp, checkAll.value); } } } @@ -203,20 +202,14 @@ export class ObjectLiteralCheck implements BaseChecker { DVFGHelper.buildSingleDVFG(declaringMtd, scene); this.visited.add(declaringMtd); } - declaringMtd - .getReturnStmt() - .forEach(r => this.checkFromStmt(r, scene, res, topLevelVarMap, checkAll, visited, depth + 1)); + declaringMtd.getReturnStmt().forEach(r => this.checkFromStmt(r, scene, res, topLevelVarMap, checkAll, visited, depth + 1)); }); const paramRef = this.isFromParameter(currentStmt); if (paramRef) { const paramIdx = paramRef.getIndex(); - const callsites = this.cg.getInvokeStmtByMethod( - currentStmt.getCfg().getDeclaringMethod().getSignature() - ); + const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); this.processCallsites(callsites, scene); - this.collectArgDefs(paramIdx, callsites, scene).forEach(d => - this.checkFromStmt(d, scene, res, topLevelVarMap, checkAll, visited, depth + 1) - ); + this.collectArgDefs(paramIdx, callsites, scene).forEach(d => this.checkFromStmt(d, scene, res, topLevelVarMap, checkAll, visited, depth + 1)); } current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); } @@ -361,12 +354,14 @@ export class ObjectLiteralCheck implements BaseChecker { }); } - private addIssueReport(stmt: Stmt, operand: Value, isPrecise: boolean = true): void { + private addIssueReport(stmt: Stmt, operand: Value, checkAll: boolean = true): void { const severity = this.rule.alert ?? this.metaData.severity; const warnInfo = this.getLineAndColumn(stmt, operand); const problem = 'ObjectLiteral'; - const metaDesc = isPrecise ? this.metaData.description : INSTANCE_OF_DESC; - const desc = `${metaDesc} (${this.rule.ruleId.replace('@migration/', '')})`; + let desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`; + if (!checkAll) { + desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${this.rule.ruleId.replace('@migration/', '')})`; + } let defects = new Defects( warnInfo.line, warnInfo.startCol, diff --git a/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts b/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts index e08dbb90f7327a8e624256650a80fbfc70c3d4fb..b8a14fa1bd730f7211f967f3af6e2d63ac2d7df5 100644 --- a/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts +++ b/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts @@ -98,7 +98,7 @@ export class CheckEntry { /** * 按规则维度统计并输出告警信息,按文件维度汇总并返回告警信息。 - * + * * @returns FileReport[] 文件报告数组,每个元素包含文件名、缺陷列表和输出信息 */ public sortIssues(): FileIssues[] { @@ -235,7 +235,7 @@ export async function checkEntryBuilder(checkEntry: CheckEntry): Promise