From 70cd32ed9b49abe05f98db56670d00ec01623ccc Mon Sep 17 00:00:00 2001 From: xudan16 Date: Tue, 24 Jun 2025 10:44:35 +0800 Subject: [PATCH 1/2] homecheck update and bug fix Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICHBM2 Signed-off-by: xudan16 --- .../InteropDeprecatedBuiltInAPICheck.ts | 72 ++++++++++++------- .../InteropDynamicObjectLiteralsCheck.ts | 66 ++++++++--------- .../src/checker/migration/NoTSLikeAsCheck.ts | 2 +- .../checker/migration/ObjectLiteralCheck.ts | 23 +++--- .../homecheck/src/utils/common/CheckEntry.ts | 8 ++- 5 files changed, 93 insertions(+), 78 deletions(-) diff --git a/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts b/ets2panda/linter/homecheck/src/checker/migration/InteropDeprecatedBuiltInAPICheck.ts index fad6267bde..02b2b9170d 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 3b59046d94..9d93f7f9c6 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 3e6f594aa1..2cd9eab61a 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 f6b47c98c9..f0c756eeed 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 e08dbb90f7..b8a14fa1bd 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 Date: Tue, 24 Jun 2025 17:09:32 +0800 Subject: [PATCH 2/2] homecheck add cast expr with increment Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICHHAC Signed-off-by: xudan16 --- .../src/checker/migration/NoTSLikeAsCheck.ts | 226 ++++++++++-------- 1 file changed, 126 insertions(+), 100 deletions(-) diff --git a/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts index 2cd9eab61a..3c7fb7450a 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/NoTSLikeAsCheck.ts @@ -15,46 +15,46 @@ import path from 'path'; import { - ArkMethod, + AbstractFieldRef, ArkAssignStmt, - FieldSignature, - Stmt, - Scene, - Value, - DVFGBuilder, - ArkInstanceOfExpr, - CallGraph, - ArkParameterRef, - ArkInstanceFieldRef, - ArkNamespace, - Local, ArkCastExpr, - ClassType, - classSignatureCompare, ArkField, - fileSignatureCompare, - Cfg, - BasicBlock, ArkIfStmt, + ArkInstanceFieldRef, + ArkInstanceOfExpr, + ArkMethod, + ArkNamespace, + ArkNormalBinopExpr, + ArkParameterRef, ArkUnopExpr, - RelationalBinaryOperator, + BasicBlock, + CallGraph, + Cfg, + ClassSignature, + classSignatureCompare, + ClassType, + DVFGBuilder, + FieldSignature, + fileSignatureCompare, LineColPosition, - UnaryOperator, - ArkNormalBinopExpr, + Local, NormalBinaryOperator, - AbstractFieldRef, - ClassSignature, + RelationalBinaryOperator, + Scene, + Stmt, + UnaryOperator, + Value, } 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 { Defects, MatcherCallback, Rule } 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 { CALL_DEPTH_LIMIT, getGlobalsDefineInDefaultMethod, getLineAndColumn, GlobalCallGraphHelper } from './Utils'; import { ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass'; import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; -import { BooleanConstant } from 'arkanalyzer/lib/core/base/Constant'; +import { BooleanConstant, NumberConstant } from 'arkanalyzer/lib/core/base/Constant'; +import { ArkClass, NumberType } from 'arkanalyzer'; const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoTSLikeAsCheck'); const gMetaData: BaseMetaData = { @@ -74,6 +74,7 @@ export class NoTSLikeAsCheck implements BaseChecker { public rule: Rule; public defects: Defects[] = []; public issues: IssueReport[] = []; + private scene: Scene; private cg: CallGraph; private dvfg: DVFG; private dvfgBuilder: DVFGBuilder; @@ -88,6 +89,7 @@ export class NoTSLikeAsCheck implements BaseChecker { } public check = (scene: Scene): void => { + this.scene = scene; this.cg = GlobalCallGraphHelper.getCGInstance(scene); this.dvfg = new DVFG(this.cg); @@ -105,58 +107,38 @@ export class NoTSLikeAsCheck implements BaseChecker { globalVarMap = getGlobalsDefineInDefaultMethod(defaultMethod); } for (let clazz of arkFile.getClasses()) { - for (let field of clazz.getFields()) { - this.processClassField(field, globalVarMap, scene); - } - for (let mtd of clazz.getMethods()) { - this.processArkMethod(mtd, globalVarMap, scene); - } + this.processClass(clazz, globalVarMap); } for (let namespace of arkFile.getAllNamespacesUnderThisFile()) { - this.processNameSpace(namespace, globalVarMap, scene); + this.processNameSpace(namespace, globalVarMap); } } }; - public processNameSpace(namespace: ArkNamespace, globalVarMap: Map, scene: Scene): void { + public processClass(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, scene); + this.processNameSpace(ns, globalVarMap); } for (let clazz of namespace.getClasses()) { - for (let field of clazz.getFields()) { - this.processClassField(field, globalVarMap, scene); - } - for (let mtd of clazz.getMethods()) { - this.processArkMethod(mtd, globalVarMap, scene); - } + this.processClass(clazz, globalVarMap); } } - public processClassField(field: ArkField, globalVarMap: Map, scene: Scene): void { - const stmts = field.getInitializer(); - for (const stmt of stmts) { - const castExpr = this.getCastExpr(stmt); - if (castExpr === null) { - continue; - } - // 判断cast类型断言的类型是否是class,非class的场景不在本规则检查范围内 - if (!(castExpr.getType() instanceof ClassType)) { - continue; - } - let checkAll = { value: true }; - let visited: Set = new Set(); - const result = this.checkFromStmt(stmt, scene, globalVarMap, checkAll, visited); - if (result !== null) { - this.addIssueReport(stmt, castExpr, result); - } else { - if (!checkAll.value) { - this.addIssueReport(stmt, castExpr); - } - } - } + public processClassField(field: ArkField, globalVarMap: Map): void { + const instInit = field.getDeclaringArkClass().getInstanceInitMethod(); + this.processArkMethod(instInit, globalVarMap); } - public processArkMethod(target: ArkMethod, globalVarMap: Map, scene: Scene): void { + public processArkMethod(target: ArkMethod, globalVarMap: Map): void { const stmts = target.getBody()?.getCfg().getStmts() ?? []; for (const stmt of stmts) { // cast表达式所在语句为sink点,从该点开始进行逆向数据流分析 @@ -164,6 +146,13 @@ export class NoTSLikeAsCheck implements BaseChecker { if (castExpr === null) { continue; } + + // 判断是否为cast表达式的自增自减运算,属于告警场景之一 + if (this.isCastExprWithIncrementDecrement(stmt)) { + this.addIssueReport(stmt, castExpr, undefined, true); + continue; + } + // 判断cast类型断言的类型是否是class,非class的场景不在本规则检查范围内 if (!(castExpr.getType() instanceof ClassType)) { continue; @@ -178,7 +167,7 @@ export class NoTSLikeAsCheck implements BaseChecker { let checkAll = { value: true }; let visited: Set = new Set(); - const result = this.checkFromStmt(stmt, scene, globalVarMap, checkAll, visited); + const result = this.checkFromStmt(stmt, globalVarMap, checkAll, visited); if (result !== null) { this.addIssueReport(stmt, castExpr, result); } else { @@ -189,6 +178,62 @@ export class NoTSLikeAsCheck implements BaseChecker { } } + private isCastExprWithIncrementDecrement(stmt: Stmt): boolean { + if (!(stmt instanceof ArkAssignStmt) || !(stmt.getRightOp() instanceof ArkCastExpr)) { + return false; + } + const castLocal = stmt.getLeftOp(); + if (!(castLocal instanceof Local)) { + return false; + } + // 判断是否为自增或自减语句,需要判断used stmt是否至少包含%0 = %0 + 1 和 castExpr = %0两条语句,不新增临时变量 + // 非自增或自减语句,used stmt中仅包含%1 = %0 + 1 + const usedStmts = castLocal.getUsedStmts(); + if (usedStmts.length !== 2) { + return false; + } + let selfAssignFlag = false; + let assignBackFlag = false; + for (const usedStmt of usedStmts) { + if (!(usedStmt instanceof ArkAssignStmt)) { + return false; + } + const leftOp = usedStmt.getLeftOp(); + const rightOp = usedStmt.getRightOp(); + if (leftOp instanceof Local) { + if (leftOp !== castLocal) { + return false; + } + if (!(rightOp instanceof ArkNormalBinopExpr)) { + return false; + } + const op1 = rightOp.getOp1(); + const op2 = rightOp.getOp2(); + const operator = rightOp.getOperator(); + if (op1 !== castLocal) { + return false; + } + if (operator !== NormalBinaryOperator.Addition && operator !== NormalBinaryOperator.Subtraction) { + return false; + } + if (!(op2 instanceof NumberConstant) || !(op2.getType() instanceof NumberType) || op2.getValue() !== '1') { + return false; + } + selfAssignFlag = true; + } + if (leftOp instanceof ArkCastExpr) { + if (leftOp !== stmt.getRightOp()) { + return false; + } + if (rightOp !== castLocal) { + return false; + } + assignBackFlag = true; + } + } + return selfAssignFlag && assignBackFlag; + } + private hasCheckedWithInstanceof(cfg: Cfg, stmt: Stmt): boolean { const castExpr = this.getCastExpr(stmt); if (castExpr === null) { @@ -346,14 +391,7 @@ export class NoTSLikeAsCheck implements BaseChecker { return null; } - private checkFromStmt( - stmt: Stmt, - scene: Scene, - globalVarMap: Map, - checkAll: { value: boolean }, - visited: Set, - depth: number = 0 - ): Stmt | null { + private checkFromStmt(stmt: Stmt, globalVarMap: Map, checkAll: { value: boolean }, visited: Set, depth: number = 0): Stmt | null { if (depth > CALL_DEPTH_LIMIT) { checkAll.value = false; return null; @@ -367,11 +405,11 @@ export class NoTSLikeAsCheck implements BaseChecker { continue; } visited.add(currentStmt); - if (this.isWithInterfaceAnnotation(currentStmt, scene)) { + if (this.isWithInterfaceAnnotation(currentStmt)) { return currentStmt; } - const fieldDeclareStmt = this.isCastOpFieldWithInterfaceType(currentStmt, scene); + const fieldDeclareStmt = this.isCastOpFieldWithInterfaceType(currentStmt); if (fieldDeclareStmt) { return fieldDeclareStmt; } @@ -401,7 +439,7 @@ export class NoTSLikeAsCheck implements BaseChecker { } const returnStmts = declaringMtd.getReturnStmt(); for (const stmt of returnStmts) { - const res = this.checkFromStmt(stmt, scene, globalVarMap, checkAll, visited, depth + 1); + const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); if (res !== null) { return res; } @@ -414,7 +452,7 @@ export class NoTSLikeAsCheck implements BaseChecker { this.processCallsites(callsites); const argDefs = this.collectArgDefs(paramIdx, callsites); for (const stmt of argDefs) { - const res = this.checkFromStmt(stmt, scene, globalVarMap, checkAll, visited, depth + 1); + const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); if (res !== null) { return res; } @@ -425,7 +463,7 @@ export class NoTSLikeAsCheck implements BaseChecker { return null; } - private isCastOpFieldWithInterfaceType(stmt: Stmt, scene: Scene): Stmt | undefined { + private isCastOpFieldWithInterfaceType(stmt: Stmt): Stmt | undefined { const obj = this.getCastOp(stmt); if (obj === null || !(obj instanceof Local)) { return undefined; @@ -440,13 +478,13 @@ export class NoTSLikeAsCheck implements BaseChecker { } const fieldDeclaring = rightOp.getFieldSignature().getDeclaringSignature(); if (fieldDeclaring instanceof ClassSignature) { - const field = scene.getClass(fieldDeclaring)?.getField(rightOp.getFieldSignature()); + const field = this.scene.getClass(fieldDeclaring)?.getField(rightOp.getFieldSignature()); if (!field) { return undefined; } const fieldInitializer = field.getInitializer(); const lastStmt = fieldInitializer[fieldInitializer.length - 1]; - if (this.isWithInterfaceAnnotation(lastStmt, scene)) { + if (this.isWithInterfaceAnnotation(lastStmt)) { return lastStmt; } } @@ -497,7 +535,7 @@ export class NoTSLikeAsCheck implements BaseChecker { } // 判断语句是否为赋值语句,且左值的类型注解为Interface,右值的类型与左值不一样 - private isWithInterfaceAnnotation(stmt: Stmt, scene: Scene): boolean { + private isWithInterfaceAnnotation(stmt: Stmt): boolean { if (!(stmt instanceof ArkAssignStmt)) { return false; } @@ -505,7 +543,7 @@ export class NoTSLikeAsCheck implements BaseChecker { if (!(leftOpType instanceof ClassType)) { return false; } - const leftOpTypeclass = scene.getClass(leftOpType.getClassSignature()); + const leftOpTypeclass = this.scene.getClass(leftOpType.getClassSignature()); if (leftOpTypeclass === null) { return false; } @@ -567,14 +605,16 @@ export class NoTSLikeAsCheck implements BaseChecker { }); } - private addIssueReport(stmt: Stmt, operand: ArkCastExpr, relatedStmt?: Stmt): void { + private addIssueReport(stmt: Stmt, operand: ArkCastExpr, relatedStmt?: Stmt, incrementCase: boolean = false): void { const severity = this.rule.alert ?? this.metaData.severity; - const warnInfo = this.getLineAndColumn(stmt, operand); + const warnInfo = getLineAndColumn(stmt, operand); const problem = 'As'; const descPrefix = 'The value in type assertion is assigned by value with interface annotation'; let desc = `(${this.rule.ruleId.replace('@migration/', '')})`; - if (relatedStmt === undefined) { + if (incrementCase) { + desc = 'Can not use neither increment nor decrement with cast expression ' + desc; + } else if (relatedStmt === undefined) { 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(); @@ -585,6 +625,7 @@ export class NoTSLikeAsCheck implements BaseChecker { desc = `${descPrefix} in file ${path.normalize(relatedFile.getName())}: ${relatedStmt.getOriginPositionInfo().getLineNo()} ` + desc; } } + let defects = new Defects( warnInfo.line, warnInfo.startCol, @@ -601,19 +642,4 @@ export class NoTSLikeAsCheck implements BaseChecker { ); 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: '' }; - } } -- Gitee