1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { 17 ArkMethod, 18 ArkAssignStmt, 19 FieldSignature, 20 Stmt, 21 Scene, 22 Value, 23 DVFGBuilder, 24 CallGraph, 25 ArkParameterRef, 26 ArkInstanceFieldRef, 27 ArkNamespace, 28 Local, 29 ClassType, 30 ClassSignature, 31 Type, 32 BooleanType, 33 FunctionType, 34 AnyType, 35 MethodSignature, 36 UnknownType, 37 GenericType, 38 NumberType, 39 ArrayType, 40 MethodSubSignature, 41 ArkInvokeStmt, 42 AbstractInvokeExpr, 43 ArkInstanceInvokeExpr, 44 ArkPtrInvokeExpr, 45 ImportInfo, 46 UnionType, 47 FileSignature, 48 ArkStaticInvokeExpr, 49 UndefinedType, 50 VoidType, 51} from 'arkanalyzer/lib'; 52import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 53import { BaseChecker, BaseMetaData } from '../BaseChecker'; 54import { Rule, Defects, MatcherCallback } from '../../Index'; 55import { IssueReport } from '../../model/Defects'; 56import { DVFG, DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 57import { CALL_DEPTH_LIMIT, getGlobalsDefineInDefaultMethod, GlobalCallGraphHelper } from './Utils'; 58import { WarnInfo } from '../../utils/common/Utils'; 59import { ArkClass } from 'arkanalyzer/lib/core/model/ArkClass'; 60import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; 61import { MethodParameter } from 'arkanalyzer/lib/core/model/builder/ArkMethodBuilder'; 62import { AbstractFieldRef, ArkReturnStmt } from 'arkanalyzer'; 63 64const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'DeprecatedBuiltInAPICheck'); 65const gMetaData: BaseMetaData = { 66 severity: 1, 67 ruleDocPath: '', 68 description: '', 69}; 70 71enum APIBaseCategory { 72 Array = 'Array', 73 Set = 'Set', 74 Map = 'Map', 75} 76 77class DeprecatedAPIInfo { 78 constructor( 79 public base: APIBaseCategory, 80 public name: string, 81 public isStatic: boolean, 82 public returnType?: Type, // return Type为undefined时表示不关心API的返回值 83 public params?: Type[], // return Type为undefined时表示不关心API的形参,应该是整个API都被废弃,而非某一重载形态 84 public targetParamIndex?: number // 若isStatic为tue,说明是静态接口,需要提供查找接口的第几个param是否来源arkts1.2,编号从0开始,不提供则查找base的来源 85 ) {} 86} 87 88class APIBasicType { 89 static readonly genericTypeT = new GenericType('T'); 90 static readonly genericTypeU = new GenericType('U'); 91 static readonly genericTypeV = new GenericType('V'); 92 static readonly genericTypeK = new GenericType('K'); 93 static readonly genericTypeS = new GenericType('S'); 94 static readonly arrayT = new ArrayType(this.genericTypeT, 1); 95 static readonly arrayU = new ArrayType(this.genericTypeU, 1); 96 static readonly unknownType = UnknownType.getInstance(); 97 static readonly numberType = NumberType.getInstance(); 98 static readonly booleanType = BooleanType.getInstance(); 99 static readonly anyType = AnyType.getInstance(); 100 static readonly undefinedType = UndefinedType.getInstance(); 101 static readonly voidType = VoidType.getInstance(); 102} 103 104class APIComplicatedType { 105 static readonly anonyMethodName = 'anonymous'; 106 static readonly predicateFunctionType = this.createPredicateFunctionType(); 107 static readonly predicateFunction1Type = this.createPredicateFunction1Type(); 108 static readonly concatItemType = this.createConcatItemType(); 109 static readonly mapfnFunctionType = this.createMapfnFunctionType(); 110 static readonly ArrayLikeType = new ClassType(new ClassSignature('ArrayLike', FileSignature.DEFAULT)); 111 static readonly IterableType = new ClassType(new ClassSignature('Iterable', FileSignature.DEFAULT)); 112 static readonly SetType = new ClassType(new ClassSignature('Set', FileSignature.DEFAULT)); 113 static readonly MapType = new ClassType(new ClassSignature('Map', FileSignature.DEFAULT)); 114 static readonly setCallbackFnFunctionType = this.createSetCallbackFnFunctionType(); 115 static readonly mapCallbackFnFunctionType = this.createMapCallbackFnFunctionType(); 116 117 // 对于参数类型一致,但是参数名称不同、返回值类型不同的FunctionType,视为同一个,不重新创建。因为FunctionType类型匹配的时候仅匹配参数类型 118 // 不同的API,仅形参为lambda函数且该lambda函数的返回值不同,例如unknown和value is S类型谓词形式,视为同一个API 119 private static createPredicateFunctionType(): FunctionType { 120 const predicateValueParam = new MethodParameter(); 121 predicateValueParam.setName('value'); 122 predicateValueParam.setType(APIBasicType.genericTypeT); 123 const predicateIndexParam = new MethodParameter(); 124 predicateIndexParam.setName('index'); 125 predicateIndexParam.setType(APIBasicType.numberType); 126 const predicateArrayParam = new MethodParameter(); 127 predicateArrayParam.setName('array'); 128 predicateArrayParam.setType(APIBasicType.arrayT); 129 const predicateMethodSubSignature = new MethodSubSignature( 130 this.anonyMethodName, 131 [predicateValueParam, predicateIndexParam, predicateArrayParam], 132 APIBasicType.unknownType 133 ); 134 return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); 135 } 136 137 private static createPredicateFunction1Type(): FunctionType { 138 const predicateThisParam = new MethodParameter(); 139 predicateThisParam.setName('this'); 140 predicateThisParam.setType(APIBasicType.voidType); 141 const predicateValueParam = new MethodParameter(); 142 predicateValueParam.setName('value'); 143 predicateValueParam.setType(APIBasicType.genericTypeT); 144 const predicateIndexParam = new MethodParameter(); 145 predicateIndexParam.setName('index'); 146 predicateIndexParam.setType(APIBasicType.numberType); 147 const predicateObjParam = new MethodParameter(); 148 predicateObjParam.setName('obj'); 149 predicateObjParam.setType(APIBasicType.arrayT); 150 const predicateMethodSubSignature = new MethodSubSignature( 151 this.anonyMethodName, 152 [predicateThisParam, predicateValueParam, predicateIndexParam, predicateObjParam], 153 APIBasicType.booleanType 154 ); 155 return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); 156 } 157 158 private static createConcatItemType(): UnionType { 159 return new UnionType([APIBasicType.genericTypeT, new ClassType(new ClassSignature('ConcatArray', FileSignature.DEFAULT))]); 160 } 161 162 private static createMapfnFunctionType(): FunctionType { 163 const mapfnParamV = new MethodParameter(); 164 mapfnParamV.setName('v'); 165 mapfnParamV.setType(APIBasicType.genericTypeT); 166 const mapfnParamK = new MethodParameter(); 167 mapfnParamK.setName('k'); 168 mapfnParamK.setType(APIBasicType.numberType); 169 const mapfnMethodSubSignature = new MethodSubSignature(this.anonyMethodName, [mapfnParamV, mapfnParamK], APIBasicType.genericTypeU); 170 return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, mapfnMethodSubSignature)); 171 } 172 173 private static createSetCallbackFnFunctionType(): FunctionType { 174 const callbackFnValueParam = new MethodParameter(); 175 callbackFnValueParam.setName('value'); 176 callbackFnValueParam.setType(APIBasicType.genericTypeT); 177 const callbackFnValue2Param = new MethodParameter(); 178 callbackFnValue2Param.setName('value2'); 179 callbackFnValue2Param.setType(APIBasicType.genericTypeT); 180 const callbackFnSetParam = new MethodParameter(); 181 callbackFnSetParam.setName('set'); 182 callbackFnSetParam.setType(this.SetType); 183 const predicateMethodSubSignature = new MethodSubSignature( 184 this.anonyMethodName, 185 [callbackFnValueParam, callbackFnValue2Param, callbackFnSetParam], 186 APIBasicType.voidType 187 ); 188 return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); 189 } 190 191 private static createMapCallbackFnFunctionType(): FunctionType { 192 const callbackFnValueParam = new MethodParameter(); 193 callbackFnValueParam.setName('value'); 194 callbackFnValueParam.setType(APIBasicType.genericTypeV); 195 const callbackFnKeyParam = new MethodParameter(); 196 callbackFnKeyParam.setName('key'); 197 callbackFnKeyParam.setType(APIBasicType.genericTypeK); 198 const callbackFnMapParam = new MethodParameter(); 199 callbackFnMapParam.setName('map'); 200 callbackFnMapParam.setType(this.MapType); 201 const predicateMethodSubSignature = new MethodSubSignature( 202 this.anonyMethodName, 203 [callbackFnValueParam, callbackFnKeyParam, callbackFnMapParam], 204 APIBasicType.voidType 205 ); 206 return new FunctionType(new MethodSignature(ClassSignature.DEFAULT, predicateMethodSubSignature)); 207 } 208} 209 210class DeprecatedAPIList { 211 static readonly DeprecatedAPIs: DeprecatedAPIInfo[] = [ 212 this.createArrayEveryAPI1(), 213 this.createArrayFilterAPI1(), 214 this.createArrayFindAPI1(), 215 this.createArrayFindAPI2(), 216 this.createArrayFindIndexAPI(), 217 this.createArrayForEachAPI(), 218 this.createArrayMapAPI(), 219 this.createArraySomeAPI(), 220 this.createArrayConcatAPI(), 221 this.createArrayFlatAPI(), 222 this.createArrayFlatMapAPI(), 223 this.createArrayFromArrayLikeAPI(), 224 this.createArrayFromIterableAndArrayLikeAPI(), 225 this.createSetForEachAPI(), 226 this.createMapForEachAPI(), 227 this.createArraySymbolIteratorAPI(), 228 this.createSetSymbolIteratorAPI(), 229 this.createMapSymbolIteratorAPI(), 230 ]; 231 232 private static createArrayEveryAPI1(): DeprecatedAPIInfo { 233 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'every', false, APIBasicType.booleanType, [ 234 APIComplicatedType.predicateFunctionType, 235 APIBasicType.anyType, 236 ]); 237 } 238 239 private static createArrayFilterAPI1(): DeprecatedAPIInfo { 240 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'filter', false, APIBasicType.arrayT, [ 241 APIComplicatedType.predicateFunctionType, 242 APIBasicType.anyType, 243 ]); 244 } 245 246 private static createArrayFindAPI1(): DeprecatedAPIInfo { 247 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'find', false, new UnionType([APIBasicType.genericTypeT, APIBasicType.undefinedType]), [ 248 APIComplicatedType.predicateFunctionType, 249 APIBasicType.anyType, 250 ]); 251 } 252 253 private static createArrayFindAPI2(): DeprecatedAPIInfo { 254 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'find', false, new UnionType([APIBasicType.genericTypeS, APIBasicType.undefinedType]), [ 255 APIComplicatedType.predicateFunction1Type, 256 APIBasicType.anyType, 257 ]); 258 } 259 260 private static createArrayFindIndexAPI(): DeprecatedAPIInfo { 261 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'findIndex', false, APIBasicType.numberType, [ 262 APIComplicatedType.predicateFunctionType, 263 APIBasicType.anyType, 264 ]); 265 } 266 267 private static createArrayForEachAPI(): DeprecatedAPIInfo { 268 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'forEach', false, APIBasicType.voidType, [ 269 APIComplicatedType.predicateFunctionType, 270 APIBasicType.anyType, 271 ]); 272 } 273 274 private static createArrayMapAPI(): DeprecatedAPIInfo { 275 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'map', false, APIBasicType.arrayU, [ 276 APIComplicatedType.predicateFunctionType, 277 APIBasicType.anyType, 278 ]); 279 } 280 281 private static createArraySomeAPI(): DeprecatedAPIInfo { 282 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'some', false, APIBasicType.booleanType, [ 283 APIComplicatedType.predicateFunctionType, 284 APIBasicType.anyType, 285 ]); 286 } 287 288 private static createArrayConcatAPI(): DeprecatedAPIInfo { 289 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'concat', false, APIBasicType.arrayT, [new ArrayType(APIComplicatedType.concatItemType, 1)]); 290 } 291 292 private static createArrayFlatAPI(): DeprecatedAPIInfo { 293 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'flat', false); 294 } 295 296 private static createArrayFlatMapAPI(): DeprecatedAPIInfo { 297 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'flatMap', false); 298 } 299 300 private static createArrayFromArrayLikeAPI(): DeprecatedAPIInfo { 301 const fromParams = [APIComplicatedType.ArrayLikeType, APIComplicatedType.mapfnFunctionType, APIBasicType.anyType]; 302 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'from', true, APIBasicType.arrayU, fromParams, 0); 303 } 304 305 private static createArrayFromIterableAndArrayLikeAPI(): DeprecatedAPIInfo { 306 const fromParams = [ 307 new UnionType([APIComplicatedType.ArrayLikeType, APIComplicatedType.IterableType]), 308 APIComplicatedType.mapfnFunctionType, 309 APIBasicType.anyType, 310 ]; 311 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'from', true, APIBasicType.arrayU, fromParams, 0); 312 } 313 314 private static createSetForEachAPI(): DeprecatedAPIInfo { 315 return new DeprecatedAPIInfo(APIBaseCategory.Set, 'forEach', false, APIBasicType.voidType, [ 316 APIComplicatedType.setCallbackFnFunctionType, 317 APIBasicType.anyType, 318 ]); 319 } 320 321 private static createMapForEachAPI(): DeprecatedAPIInfo { 322 return new DeprecatedAPIInfo(APIBaseCategory.Map, 'forEach', false, APIBasicType.voidType, [ 323 APIComplicatedType.mapCallbackFnFunctionType, 324 APIBasicType.anyType, 325 ]); 326 } 327 328 private static createArraySymbolIteratorAPI(): DeprecatedAPIInfo { 329 return new DeprecatedAPIInfo(APIBaseCategory.Array, 'Symbol.iterator', false); 330 } 331 332 private static createSetSymbolIteratorAPI(): DeprecatedAPIInfo { 333 return new DeprecatedAPIInfo(APIBaseCategory.Set, 'Symbol.iterator', false); 334 } 335 336 private static createMapSymbolIteratorAPI(): DeprecatedAPIInfo { 337 return new DeprecatedAPIInfo(APIBaseCategory.Map, 'Symbol.iterator', false); 338 } 339} 340 341export class InteropDeprecatedBuiltInAPICheck implements BaseChecker { 342 readonly metaData: BaseMetaData = gMetaData; 343 public rule: Rule; 344 public defects: Defects[] = []; 345 public issues: IssueReport[] = []; 346 private cg: CallGraph; 347 private dvfg: DVFG; 348 private dvfgBuilder: DVFGBuilder; 349 private scene: Scene; 350 private visited: Set<ArkMethod> = new Set(); 351 352 public registerMatchers(): MatcherCallback[] { 353 const matchBuildCb: MatcherCallback = { 354 matcher: undefined, 355 callback: this.check, 356 }; 357 return [matchBuildCb]; 358 } 359 360 public check = (scene: Scene): void => { 361 this.scene = scene; 362 this.cg = GlobalCallGraphHelper.getCGInstance(scene); 363 364 this.dvfg = new DVFG(this.cg); 365 this.dvfgBuilder = new DVFGBuilder(this.dvfg, scene); 366 367 for (let arkFile of scene.getFiles()) { 368 // 此规则仅对arkts1.1进行检查 369 if (!(arkFile.getLanguage() === Language.ARKTS1_1)) { 370 continue; 371 } 372 const defaultMethod = arkFile.getDefaultClass().getDefaultArkMethod(); 373 let globalVarMap: Map<string, Stmt[]> = new Map(); 374 if (defaultMethod) { 375 this.dvfgBuilder.buildForSingleMethod(defaultMethod); 376 globalVarMap = getGlobalsDefineInDefaultMethod(defaultMethod); 377 } 378 for (let arkClass of arkFile.getClasses()) { 379 this.processArkClass(arkClass, globalVarMap); 380 } 381 for (let namespace of arkFile.getAllNamespacesUnderThisFile()) { 382 this.processNameSpace(namespace, globalVarMap); 383 } 384 } 385 }; 386 387 public processArkClass(arkClass: ArkClass, globalVarMap: Map<string, Stmt[]>): void { 388 this.processArkMethod(arkClass.getInstanceInitMethod(), globalVarMap); 389 this.processArkMethod(arkClass.getStaticInitMethod(), globalVarMap); 390 for (let mtd of arkClass.getMethods()) { 391 this.processArkMethod(mtd, globalVarMap); 392 } 393 } 394 395 public processNameSpace(namespace: ArkNamespace, globalVarMap: Map<string, Stmt[]>): void { 396 for (let ns of namespace.getNamespaces()) { 397 this.processNameSpace(ns, globalVarMap); 398 } 399 for (let arkClass of namespace.getClasses()) { 400 this.processArkClass(arkClass, globalVarMap); 401 } 402 } 403 404 public processArkMethod(target: ArkMethod, globalVarMap: Map<string, Stmt[]>): void { 405 const stmts = target.getBody()?.getCfg().getStmts() ?? []; 406 for (const stmt of stmts) { 407 // 查找到DeprecatedAPIs中的builtIn的API调用语句为sink点,从该点开始进行逆向数据流分析,分析base是否为arkts1.2中声明的对象实例或其引用 408 const targetLocal = this.getTargetLocalOfDeprecatedAPICall(stmt); 409 if (targetLocal === null) { 410 continue; 411 } 412 413 // 从base的最近一次赋值语句开始,使用逆向数据流进行查找 414 let checkStmt = this.getLastAssignStmt(targetLocal, stmt); 415 if (checkStmt === null) { 416 checkStmt = this.checkTargetLocalAsGlobal(target, stmt, targetLocal, globalVarMap); 417 } 418 if (checkStmt === null) { 419 continue; 420 } 421 422 if (!this.visited.has(target)) { 423 this.dvfgBuilder.buildForSingleMethod(target); 424 this.visited.add(target); 425 } 426 427 let checkAll = { value: true }; 428 let visited: Set<Stmt> = new Set(); 429 if (this.checkFromStmt(checkStmt, globalVarMap, checkAll, visited)) { 430 this.addIssueReport(stmt, targetLocal, checkAll.value); 431 } 432 } 433 } 434 435 private checkTargetLocalAsGlobal(targetMethod: ArkMethod, stmt: Stmt, targetLocal: Local, globalVarMap: Map<string, Stmt[]>): Stmt | null { 436 const globalDefs = globalVarMap.get(targetLocal.getName()); 437 if (globalDefs === undefined) { 438 const importInfos = targetMethod.getDeclaringArkClass().getDeclaringArkFile().getImportInfos(); 439 const importValue = this.isLocalFromImport(targetLocal, importInfos); 440 if (importValue && importValue.getDeclaringStmt() !== null) { 441 return importValue.getDeclaringStmt()!; 442 } 443 return null; 444 } 445 let lastLegalStmtLine = -1; 446 let lastIllegalStmtLine = -1; 447 for (const defStmt of globalDefs) { 448 if (!this.visited.has(targetMethod)) { 449 this.dvfgBuilder.buildForSingleMethod(targetMethod); 450 this.visited.add(targetMethod); 451 } 452 453 let checkAll = { value: true }; 454 let visited: Set<Stmt> = new Set(); 455 456 const currStmtLine = defStmt.getOriginPositionInfo().getLineNo(); 457 if (this.checkFromStmt(defStmt, globalVarMap, checkAll, visited)) { 458 if (currStmtLine > lastIllegalStmtLine) { 459 lastIllegalStmtLine = currStmtLine; 460 } 461 } else { 462 if (currStmtLine > lastLegalStmtLine) { 463 lastLegalStmtLine = currStmtLine; 464 } 465 } 466 } 467 if (lastIllegalStmtLine > lastLegalStmtLine) { 468 this.addIssueReport(stmt, targetLocal); 469 } 470 return null; 471 } 472 473 private getLastAssignStmt(local: Local, currStmt: Stmt): Stmt | null { 474 // 获取local变量在currStmt使用语句之前的最近一次赋值/声明语句,未找到表示直接来自于全局变量或import信息,则返回null 475 const usedStmts = local.getUsedStmts(); 476 let currLine = currStmt.getOriginPositionInfo().getLineNo(); 477 let lastAssignStmt = local.getDeclaringStmt(); 478 for (const stmt of usedStmts) { 479 if (stmt === currStmt || !(stmt instanceof ArkAssignStmt) || stmt.getLeftOp() !== local) { 480 continue; 481 } 482 const line = stmt.getOriginPositionInfo().getLineNo(); 483 if (line < currLine) { 484 lastAssignStmt = stmt; 485 currLine = line; 486 } 487 } 488 return lastAssignStmt; 489 } 490 491 private isLocalDefinedInStaticArkTS(local: Local): boolean { 492 return local.getDeclaringStmt()?.getCfg().getDeclaringMethod().getLanguage() === Language.ARKTS1_2; 493 } 494 495 // 判断语句是否为废弃API接口的调用语句,若废弃接口仅为一系列重载中的某一种,需要判断这一具体重载形态,若是返回对应需要查找的Local对象,否则返回null 496 private getTargetLocalOfDeprecatedAPICall(stmt: Stmt): Local | null { 497 const invokeExpr = this.getInvokeExpr(stmt); 498 if (invokeExpr === null) { 499 return null; 500 } 501 if (invokeExpr instanceof ArkInstanceInvokeExpr) { 502 const base = invokeExpr.getBase(); 503 if (this.isInstanceCallMethodInDeprecatedAPIs(base, stmt, invokeExpr.getMethodSignature(), invokeExpr.getArgs())) { 504 return base; 505 } 506 // Array.from的API调用ArkAnalyzer也表示为ArkInstanceInvokeExpr,因为API定义里没有明确的static标识 507 return this.getTargetLocalInArrayFrom(invokeExpr); 508 } else if (invokeExpr instanceof ArkPtrInvokeExpr) { 509 // TODO:可能存在ptr invoke的场景吗? 510 return null; 511 } else if (invokeExpr instanceof ArkStaticInvokeExpr) { 512 // Symbol.iterator API 检测的Reflect.get场景,是static invoke 513 return this.getTargetLocalInReflectGet(invokeExpr); 514 } 515 return null; 516 } 517 518 private getInvokeExpr(stmt: Stmt): AbstractInvokeExpr | null { 519 if (!(stmt instanceof ArkAssignStmt) && !(stmt instanceof ArkInvokeStmt)) { 520 return null; 521 } 522 523 if (stmt instanceof ArkInvokeStmt) { 524 return stmt.getInvokeExpr(); 525 } 526 527 const rightOp = stmt.getRightOp(); 528 if (rightOp instanceof AbstractInvokeExpr) { 529 return rightOp; 530 } 531 return null; 532 } 533 534 private compareParamTypes(apiParams: Type[], callApiParams: MethodParameter[]): boolean { 535 if (apiParams.length !== callApiParams.length) { 536 return false; 537 } 538 for (let i = 0; i < apiParams.length; i++) { 539 if (!this.isTypeMatch(apiParams[i], callApiParams[i].getType())) { 540 return false; 541 } 542 } 543 return true; 544 } 545 546 // 此函数仅用作Symbol.iterator API 检测的Reflect.get场景 547 private getTargetLocalInReflectGet(staticInvokeExpr: ArkStaticInvokeExpr): Local | null { 548 const method = staticInvokeExpr.getMethodSignature(); 549 const methodName = method.getMethodSubSignature().getMethodName(); 550 const namespaceName = method.getDeclaringClassSignature().getDeclaringNamespaceSignature()?.getNamespaceName(); 551 if (namespaceName !== 'Reflect' || methodName !== 'get') { 552 return null; 553 } 554 555 const args = staticInvokeExpr.getArgs(); 556 if (args.length < 2) { 557 return null; 558 } 559 const targetLocal = args[0]; 560 const apiCall = args[1]; 561 if (!(targetLocal instanceof Local) || !(apiCall instanceof Local)) { 562 return null; 563 } 564 565 const declaringStmt = apiCall.getDeclaringStmt(); 566 if (declaringStmt === null || !(declaringStmt instanceof ArkAssignStmt)) { 567 return null; 568 } 569 const rightOp = declaringStmt.getRightOp(); 570 if (!(rightOp instanceof ArkInstanceFieldRef)) { 571 return null; 572 } 573 const fieldBaseName = rightOp.getBase().getName(); 574 const fieldName = rightOp.getFieldName(); 575 if (fieldBaseName !== 'Symbol' || fieldName !== 'iterator') { 576 return null; 577 } 578 579 for (const api of DeprecatedAPIList.DeprecatedAPIs) { 580 if (api.name !== 'Symbol.iterator') { 581 continue; 582 } 583 if (api.base === APIBaseCategory.Array && targetLocal.getType() instanceof ArrayType) { 584 return targetLocal; 585 } 586 if (api.base === APIBaseCategory.Set) { 587 const localType = targetLocal.getType(); 588 if (localType instanceof ClassType && localType.getClassSignature().getClassName() === 'Set') { 589 return targetLocal; 590 } 591 } 592 if (api.base === APIBaseCategory.Map) { 593 const localType = targetLocal.getType(); 594 if (localType instanceof ClassType && localType.getClassSignature().getClassName() === 'Map') { 595 return targetLocal; 596 } 597 } 598 } 599 return null; 600 } 601 602 private getTargetLocalInArrayFrom(invokeExpr: ArkInstanceInvokeExpr): Local | null { 603 const callApiMethod = invokeExpr.getMethodSignature(); 604 const callApiClass = callApiMethod.getDeclaringClassSignature(); 605 for (const api of DeprecatedAPIList.DeprecatedAPIs) { 606 if (!api.isStatic) { 607 continue; 608 } 609 if (api.name !== callApiMethod.getMethodSubSignature().getMethodName()) { 610 continue; 611 } 612 // Array.from 形式的调用,from方法实际的class为ArrayConstructor 613 if (api.base !== callApiClass.getClassName() && `${api.base}Constructor` !== callApiClass.getClassName()) { 614 continue; 615 } 616 // 在本条规则检查范围内的static API的调用一定是带参数的,并且其中某个参数即为需要进行进一步查找的value 617 if (api.params === undefined) { 618 continue; 619 } 620 if (this.compareParamTypes(api.params, callApiMethod.getMethodSubSignature().getParameters())) { 621 const args = invokeExpr.getArgs(); 622 // 形参匹配的情况下,进一步比较传入的实参,因为当前废弃接口大多数为去掉any类型的第二个可选参数 623 // TODO:这里需要考虑如何做的更通用 624 if (args.length !== api.params.length) { 625 continue; 626 } 627 const index = api.targetParamIndex; 628 // 成功匹配到指定的API后,如果未提供下一步需要查找的目标param的index,则返回null。理论上不应该走到这个分支。 629 if (index === undefined) { 630 logger.error(`Missing targetParamIndex, api: ${api.name}, category ${api.base}`); 631 return null; 632 } 633 634 if (args.length <= index) { 635 logger.error(`Invalid targetParamIndex ${index}, totally invoke args size ${args.length}, api: ${api.name}, category ${api.base}`); 636 return null; 637 } 638 const target = args[index]; 639 if (target instanceof Local) { 640 return target; 641 } 642 logger.error(`Need to handle non-local target ${target.getType().getTypeString()}`); 643 return null; 644 } 645 } 646 return null; 647 } 648 649 private isInstanceCallMethodInDeprecatedAPIs(callBase: Local, stmt: Stmt, callMethod: MethodSignature, args: Value[]): boolean { 650 const callApiName = callMethod.getMethodSubSignature().getMethodName(); 651 const callApiParams = callMethod.getMethodSubSignature().getParameters(); 652 for (const api of DeprecatedAPIList.DeprecatedAPIs) { 653 // 对于map[Symbol.iterator]这样的API调用,callApiName是临时变量,需要进一步匹配 654 if (api.name !== callApiName) { 655 continue; 656 } 657 // 对于for...of的语句,ArkAnalyzer会为其生成Symbol.iterator的调用语句,此处从源码中查找关键字以区分是源码中有还是自动生成的 658 if (api.name === 'Symbol.iterator' && !stmt.getOriginalText()?.includes('Symbol.iterator')) { 659 continue; 660 } 661 if (api.isStatic) { 662 continue; 663 } 664 if (!this.isBaseTypeMatchAPIBase(api.base, callBase)) { 665 continue; 666 } 667 668 // Array concat API ArkAnalyzer当前无法很好处理...items形式的入参,此处作为特例处理 669 if (api.name === 'concat' && api.base === APIBaseCategory.Array) { 670 return this.isMatchArrayConcatAPI(args); 671 } 672 673 const apiParams = api.params; 674 if (apiParams === undefined) { 675 return true; 676 } 677 let allParamTypeMatch = this.compareParamTypes(apiParams, callApiParams); 678 if (allParamTypeMatch) { 679 // 形参匹配的情况下,进一步比较传入的实参,因为当前废弃接口大多数为去掉any类型的第二个可选参数 680 // TODO:这里需要考虑如何做的更通用 681 if (args.length !== apiParams.length) { 682 continue; 683 } 684 return true; 685 } 686 // 形参类型不匹配的情形,可能是由于ArkAnalyzer的类型推导未能找到正确的API,需要根据实参类型进行二次匹配 687 if (apiParams.length !== args.length) { 688 continue; 689 } 690 allParamTypeMatch = true; 691 for (let i = 0; i < apiParams.length; i++) { 692 // 对于lambda函数作为参数类型,此处不严格校验lambda的参数类型,仅判断是否为FunctionType 693 if (apiParams[i] instanceof FunctionType && args[i].getType() instanceof FunctionType) { 694 continue; 695 } 696 if (!this.isTypeMatch(apiParams[i], args[i].getType())) { 697 allParamTypeMatch = false; 698 break; 699 } 700 } 701 if (allParamTypeMatch) { 702 return true; 703 } 704 } 705 return false; 706 } 707 708 // 判断入参是否都为数组,不允许有单个元素 709 private isMatchArrayConcatAPI(args: Value[]): boolean { 710 for (const arg of args) { 711 if (!(arg.getType() instanceof ArrayType)) { 712 return true; 713 } 714 } 715 return false; 716 } 717 718 private isTypeMatch(apiType: Type, callApiType: Type): boolean { 719 const apiTypeStr = apiType.getTypeString(); 720 const callApiTypeStr = callApiType.getTypeString(); 721 if (callApiType instanceof FunctionType && apiType instanceof FunctionType) { 722 // 若类型为FunctionType,仅需匹配string中的形参部分 723 const regex = /\(([^()]*)\)/; 724 const apiMatch = apiTypeStr.match(regex); 725 const callApiMatch = callApiTypeStr.match(regex); 726 if (apiMatch === null || callApiMatch === null) { 727 return false; 728 } 729 // 移除字符串中的类型的文件签名、类签名、泛型等信息后进行比较 730 let apiParamsStr = apiMatch[0].replace(/@[^:]+:/, '').replace(/<[^>]+>/, ''); 731 let callApiParamsStr = callApiMatch[0].replace(/@[^:]+:/, '').replace(/<[^>]+>/, ''); 732 return apiParamsStr === callApiParamsStr; 733 } else if (callApiType instanceof ClassType && apiType instanceof ClassType) { 734 // 若类型为FunctionType,仅需匹配class name,因为apiTypeStr类型推导后有可能为@%unk/%unk: ArrayLike,而callApiTypeStr有明确的declaring file 735 return callApiType.getClassSignature().getClassName() === apiType.getClassSignature().getClassName(); 736 } else if (apiType instanceof AnyType) { 737 return true; 738 } else { 739 // 其他场景需严格判断字符串相等 740 return apiTypeStr === callApiTypeStr; 741 } 742 } 743 744 private isBaseTypeMatchAPIBase(apiBase: APIBaseCategory, callBase: Local): boolean { 745 if (apiBase === APIBaseCategory.Array && callBase.getType() instanceof ArrayType) { 746 return true; 747 } 748 if (apiBase === APIBaseCategory.Map) { 749 const callBaseType = callBase.getType(); 750 return callBaseType instanceof ClassType && callBaseType.getClassSignature().getClassName() === 'Map'; 751 } 752 if (apiBase === APIBaseCategory.Set) { 753 const callBaseType = callBase.getType(); 754 return callBaseType instanceof ClassType && callBaseType.getClassSignature().getClassName() === 'Set'; 755 } 756 return false; 757 } 758 759 private checkFromStmt(stmt: Stmt, globalVarMap: Map<string, Stmt[]>, checkAll: { value: boolean }, visited: Set<Stmt>, depth: number = 0): boolean { 760 if (depth > CALL_DEPTH_LIMIT) { 761 checkAll.value = false; 762 return true; 763 } 764 const node = this.dvfg.getOrNewDVFGNode(stmt); 765 let worklist: DVFGNode[] = [node]; 766 while (worklist.length > 0) { 767 const current = worklist.shift()!; 768 const currentStmt = current.getStmt(); 769 if (visited.has(currentStmt)) { 770 continue; 771 } 772 visited.add(currentStmt); 773 774 if (this.isLeftOpOrReturnOpDefinedInStaticArkTS(currentStmt)) { 775 return true; 776 } 777 778 // 当前语句的右值是全局变量,查找全局变量的定义语句 779 const gv = this.isRightOpGlobalVar(currentStmt); 780 if (gv) { 781 const globalDefs = globalVarMap.get(gv.getName()); 782 if (globalDefs === undefined) { 783 const importInfos = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getImportInfos(); 784 const importValue = this.isLocalFromImport(gv, importInfos); 785 if (importValue && importValue.getDeclaringStmt() !== null) { 786 worklist.push(this.dvfg.getOrNewDVFGNode(importValue.getDeclaringStmt()!)); 787 } 788 } else { 789 globalDefs.forEach(d => worklist.push(this.dvfg.getOrNewDVFGNode(d))); 790 } 791 continue; 792 } 793 794 // 当前语句的右值是函数返回值,查找调用函数的所有return语句 795 const callsite = this.cg.getCallSiteByStmt(currentStmt); 796 for (const cs of callsite) { 797 const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); 798 if (!declaringMtd || !declaringMtd.getCfg()) { 799 continue; 800 } 801 if (!this.visited.has(declaringMtd)) { 802 this.dvfgBuilder.buildForSingleMethod(declaringMtd); 803 this.visited.add(declaringMtd); 804 } 805 const returnStmts = declaringMtd.getReturnStmt(); 806 for (const stmt of returnStmts) { 807 const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); 808 if (res) { 809 return true; 810 } 811 } 812 } 813 814 // 当前语句的右值是函数参数赋值语句,查找所有调用处的入参情况 815 const paramRef = this.isFromParameter(currentStmt); 816 if (paramRef) { 817 const paramIdx = paramRef.getIndex(); 818 const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); 819 this.processCallsites(callsites); 820 const argDefs = this.collectArgDefs(paramIdx, callsites); 821 for (const stmt of argDefs) { 822 const res = this.checkFromStmt(stmt, globalVarMap, checkAll, visited, depth + 1); 823 if (res) { 824 return true; 825 } 826 } 827 } 828 829 // 当前语句的右值是属性赋值语句,查找该属性的初始化语句 830 if (currentStmt instanceof ArkAssignStmt && currentStmt.getRightOp() instanceof AbstractFieldRef) { 831 const fieldSignature = (currentStmt.getRightOp() as AbstractFieldRef).getFieldSignature(); 832 const classSignature = fieldSignature.getDeclaringSignature(); 833 if (classSignature instanceof ClassSignature) { 834 const field = this.scene.getClass(classSignature)?.getField(fieldSignature); 835 if (field) { 836 field.getInitializer().forEach(s => worklist.push(this.dvfg.getOrNewDVFGNode(s))); 837 } 838 } 839 } 840 841 // 当前语句是return语句,查找return操作数的相关语句 842 if (currentStmt instanceof ArkReturnStmt) { 843 const returnOp = currentStmt.getOp(); 844 if (returnOp instanceof Local) { 845 let checkStmt = 846 this.getLastAssignStmt(returnOp, stmt) ?? 847 this.checkTargetLocalAsGlobal(currentStmt.getCfg().getDeclaringMethod(), stmt, returnOp, globalVarMap); 848 if (checkStmt !== null) { 849 worklist.push(this.dvfg.getOrNewDVFGNode(checkStmt)); 850 } 851 } 852 } 853 current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); 854 } 855 return false; 856 } 857 858 private isRightOpGlobalVar(stmt: Stmt): Local | undefined { 859 if (stmt instanceof ArkAssignStmt) { 860 const rightOp = stmt.getRightOp(); 861 if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) { 862 return rightOp; 863 } 864 } 865 return undefined; 866 } 867 868 private isLocalFromImport(local: Local, importInfos: ImportInfo[]): Local | undefined { 869 for (const importInfo of importInfos) { 870 if (importInfo.getImportClauseName() === local.getName()) { 871 const exportInfo = importInfo.getLazyExportInfo(); 872 if (exportInfo === null) { 873 return undefined; 874 } 875 const arkExport = exportInfo.getArkExport(); 876 if (arkExport === null || arkExport === undefined) { 877 return undefined; 878 } 879 if (!(arkExport instanceof Local)) { 880 return undefined; 881 } 882 return arkExport; 883 } 884 } 885 return undefined; 886 } 887 888 private processCallsites(callsites: Stmt[]): void { 889 callsites.forEach(cs => { 890 const declaringMtd = cs.getCfg().getDeclaringMethod(); 891 if (!this.visited.has(declaringMtd)) { 892 this.dvfgBuilder.buildForSingleMethod(declaringMtd); 893 this.visited.add(declaringMtd); 894 } 895 }); 896 } 897 898 // 判断语句是否为赋值语句,且左值的定义来自于ArkTS1.2 899 private isLeftOpOrReturnOpDefinedInStaticArkTS(stmt: Stmt): boolean { 900 if (!(stmt instanceof ArkAssignStmt) && !(stmt instanceof ArkReturnStmt)) { 901 return false; 902 } 903 let operand: Value; 904 if (stmt instanceof ArkAssignStmt) { 905 operand = stmt.getLeftOp(); 906 } else { 907 operand = stmt.getOp(); 908 } 909 if (!(operand instanceof Local)) { 910 return false; 911 } 912 return this.isLocalDefinedInStaticArkTS(operand); 913 } 914 915 private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { 916 if (!(stmt instanceof ArkAssignStmt)) { 917 return undefined; 918 } 919 const rightOp = stmt.getRightOp(); 920 if (rightOp instanceof ArkParameterRef) { 921 return rightOp; 922 } 923 return undefined; 924 } 925 926 private collectArgDefs(argIdx: number, callsites: Stmt[]): Stmt[] { 927 const getKey = (v: Value): Value | FieldSignature => { 928 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 929 }; 930 return callsites.flatMap(callsite => { 931 const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx)); 932 let refs = Array.from(this.dvfg.getOrNewDVFGNode(callsite).getIncomingEdge()) 933 .map(e => (e.getSrcNode() as DVFGNode).getStmt()) 934 .filter(s => { 935 return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp()); 936 }); 937 // 以上步骤未找到defs语句,说明入参变量来源自import信息 938 if (refs.length === 0 && target instanceof Local) { 939 const importInfos = callsite.getCfg().getDeclaringMethod().getDeclaringArkFile().getImportInfos(); 940 const importValue = this.isLocalFromImport(target, importInfos); 941 if (importValue && importValue.getDeclaringStmt() !== null) { 942 return importValue.getDeclaringStmt()!; 943 } 944 } 945 return refs; 946 }); 947 } 948 949 private addIssueReport(stmt: Stmt, operand: Value, checkAll: boolean = true): void { 950 const severity = this.rule.alert ?? this.metaData.severity; 951 const warnInfo = this.getLineAndColumn(stmt, operand); 952 const problem = 'builtin-api'; 953 let desc = `Builtin API is not support in ArkTS1.2 (${this.rule.ruleId.replace('@migration/', '')})`; 954 if (!checkAll) { 955 desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${this.rule.ruleId.replace('@migration/', '')})`; 956 } 957 958 let defects = new Defects( 959 warnInfo.line, 960 warnInfo.startCol, 961 warnInfo.endCol, 962 problem, 963 desc, 964 severity, 965 this.rule.ruleId, 966 warnInfo.filePath, 967 this.metaData.ruleDocPath, 968 true, 969 false, 970 false 971 ); 972 this.issues.push(new IssueReport(defects, undefined)); 973 } 974 975 private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo { 976 const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); 977 const originPosition = stmt.getOperandOriginalPosition(operand); 978 if (arkFile && originPosition) { 979 const originPath = arkFile.getFilePath(); 980 const line = originPosition.getFirstLine(); 981 const startCol = originPosition.getFirstCol(); 982 const endCol = startCol; 983 return { line, startCol, endCol, filePath: originPath }; 984 } else { 985 logger.debug('ArkFile is null.'); 986 } 987 return { line: -1, startCol: -1, endCol: -1, filePath: '' }; 988 } 989} 990