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 Type, 18 ArkMethod, 19 ArkAssignStmt, 20 FieldSignature, 21 Stmt, 22 Scene, 23 Value, 24 CallGraph, 25 ArkParameterRef, 26 ArkInstanceFieldRef, 27 ArkInstanceInvokeExpr, 28 AnyType, 29 ClassType, 30 ArkStaticInvokeExpr, 31 AbstractInvokeExpr, 32 FunctionType, 33 UnknownType, 34 Local, 35 ArkClass, 36} from 'arkanalyzer/lib'; 37import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 38import { BaseChecker, BaseMetaData } from '../BaseChecker'; 39import { Rule, Defects, MatcherCallback } from '../../Index'; 40import { IssueReport } from '../../model/Defects'; 41import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 42import { CALL_DEPTH_LIMIT, GlobalCallGraphHelper, DVFGHelper } from './Utils'; 43import { findInteropRule } from './InteropRuleInfo'; 44import { ArkFile, Language } from 'arkanalyzer/lib/core/model/ArkFile'; 45 46const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropBackwardDFACheck'); 47const gMetaData: BaseMetaData = { 48 severity: 1, 49 ruleDocPath: '', 50 description: '', 51}; 52 53const REFLECT_API: Map<string, number> = new Map([ 54 ['apply', 0], 55 ['construct', 0], 56 ['defineProperty', 0], 57 ['deleteProperty', 0], 58 ['get', 0], 59 ['getOwnPropertyDescriptor', 0], 60 ['getPrototypeOf', 0], 61 ['has', 0], 62 ['isExtensible', 0], 63 ['ownKeys', 0], 64 ['preventExtensions', 0], 65 ['set', 0], 66 ['setPrototypeOf', 0], 67]); 68 69const OBJECT_API: Map<string, number> = new Map([ 70 ['getOwnPropertyDescriptor', 0], 71 ['getOwnPropertyDescriptors', 0], 72 ['getOwnPropertyNames', 0], 73 ['hasOwn', 0], 74 ['isExtensible', 0], 75 ['isFrozen', 0], 76 ['isSealed', 0], 77 ['keys', 0], 78 ['setPrototypeOf', 0], 79 ['values', 0], 80 ['assign', 1], 81 ['entries', 0], 82]); 83 84class ObjDefInfo { 85 problemStmt: Stmt; 86 objLanguage: Language; 87} 88 89export class InteropBackwardDFACheck implements BaseChecker { 90 readonly metaData: BaseMetaData = gMetaData; 91 public rule: Rule; 92 public defects: Defects[] = []; 93 public issues: IssueReport[] = []; 94 private cg: CallGraph; 95 96 public registerMatchers(): MatcherCallback[] { 97 const matchBuildCb: MatcherCallback = { 98 matcher: undefined, 99 callback: this.check, 100 }; 101 return [matchBuildCb]; 102 } 103 104 public check = (scene: Scene): void => { 105 this.cg = GlobalCallGraphHelper.getCGInstance(scene); 106 107 for (let arkFile of scene.getFiles()) { 108 const importVarMap: Map<string, Language> = new Map(); 109 this.collectImportedVar(importVarMap, arkFile, scene); 110 const topLevelVarMap: Map<string, Stmt[]> = new Map(); 111 this.collectTopLevelVar(topLevelVarMap, arkFile, scene); 112 113 const handleClass = (cls: ArkClass): void => { 114 cls.getMethods().forEach(m => this.processArkMethod(m, scene, importVarMap, topLevelVarMap)); 115 }; 116 117 arkFile.getClasses().forEach(cls => handleClass(cls)); 118 arkFile.getAllNamespacesUnderThisFile().forEach(n => n.getClasses().forEach(cls => handleClass(cls))); 119 } 120 }; 121 122 private collectImportedVar(importVarMap: Map<string, Language>, file: ArkFile, scene: Scene) { 123 file.getImportInfos().forEach(importInfo => { 124 const exportInfo = importInfo.getLazyExportInfo(); 125 if (exportInfo === null) { 126 return; 127 } 128 const arkExport = exportInfo.getArkExport(); 129 if (!arkExport || !(arkExport instanceof Local)) { 130 return; 131 } 132 const declaringStmt = arkExport.getDeclaringStmt(); 133 if (!declaringStmt) { 134 return; 135 } 136 const definedLang = this.getTypeDefinedLang(arkExport.getType(), scene) ?? file.getLanguage(); 137 importVarMap.set(arkExport.getName(), definedLang); 138 }); 139 } 140 141 private collectTopLevelVar(topLevelVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) { 142 const defaultMethod = file.getDefaultClass().getDefaultArkMethod(); 143 if (defaultMethod) { 144 DVFGHelper.buildSingleDVFG(defaultMethod, scene); 145 const stmts = defaultMethod.getBody()?.getCfg().getStmts() ?? []; 146 for (const stmt of stmts) { 147 if (!(stmt instanceof ArkAssignStmt)) { 148 continue; 149 } 150 const leftOp = stmt.getLeftOp(); 151 if (!(leftOp instanceof Local)) { 152 continue; 153 } 154 const name = leftOp.getName(); 155 if (name.startsWith('%') || name === 'this') { 156 continue; 157 } 158 topLevelVarMap.set(name, [...(topLevelVarMap.get(name) ?? []), stmt]); 159 } 160 } 161 } 162 163 private processArkMethod( 164 target: ArkMethod, 165 scene: Scene, 166 importVarMap: Map<string, Language>, 167 topLevelVarMap: Map<string, Stmt[]> 168 ): void { 169 const currentLang = target.getLanguage(); 170 if (currentLang === Language.UNKNOWN) { 171 logger.warn(`cannot find the language for method: ${target.getSignature()}`); 172 return; 173 } 174 const stmts = target.getBody()?.getCfg().getStmts() ?? []; 175 for (const stmt of stmts) { 176 const invoke = stmt.getInvokeExpr(); 177 let isReflect = false; 178 let paramIdx = -1; 179 if (invoke && invoke instanceof ArkStaticInvokeExpr) { 180 const classSig = invoke.getMethodSignature().getDeclaringClassSignature(); 181 if ( 182 classSig.getDeclaringFileSignature().getProjectName() === 'built-in' && 183 classSig.getDeclaringNamespaceSignature()?.getNamespaceName() === 'Reflect' 184 ) { 185 isReflect = true; 186 paramIdx = REFLECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1; 187 } 188 } 189 if (invoke && invoke instanceof ArkStaticInvokeExpr) { 190 const methodSig = invoke.getMethodSignature(); 191 const classSig = methodSig.getDeclaringClassSignature(); 192 if (classSig.getClassName() === 'ObjectConstructor' || classSig.getClassName() === 'Object') { 193 paramIdx = 194 OBJECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1; 195 } 196 } 197 if (paramIdx === -1) { 198 continue; 199 } 200 DVFGHelper.buildSingleDVFG(target, scene); 201 202 const argDefs = this.findArgumentDef(stmt, paramIdx, currentLang, importVarMap, topLevelVarMap, scene); 203 if (this.isLanguage(argDefs)) { 204 this.reportIssue({ problemStmt: stmt, objLanguage: argDefs as Language }, currentLang, isReflect); 205 } else { 206 argDefs.forEach(def => { 207 let result: ObjDefInfo[] = []; 208 let visited: Set<Stmt> = new Set(); 209 this.checkFromStmt(def, currentLang, result, visited, importVarMap, topLevelVarMap, scene); 210 result.forEach(objDefInfo => { 211 this.reportIssue(objDefInfo, currentLang, isReflect); 212 }); 213 }); 214 } 215 } 216 } 217 218 private reportIssue(objDefInfo: ObjDefInfo, apiLang: Language, isReflect: boolean) { 219 const problemStmt = objDefInfo.problemStmt; 220 const problemStmtMtd = problemStmt.getCfg().getDeclaringMethod(); 221 const problemStmtLang = problemStmtMtd?.getLanguage(); 222 const objLanguage = objDefInfo.objLanguage; 223 if (objLanguage === Language.UNKNOWN || problemStmtLang === Language.UNKNOWN) { 224 logger.warn(`cannot find the language for def: ${problemStmt.toString()}`); 225 return; 226 } 227 const interopRule = findInteropRule(apiLang, objLanguage, problemStmtLang, isReflect); 228 if (!interopRule) { 229 return; 230 } 231 const line = problemStmt.getOriginPositionInfo().getLineNo(); 232 const column = problemStmt.getOriginPositionInfo().getColNo(); 233 const problem = 'Interop'; 234 const desc = `${interopRule.description} (${interopRule.ruleId})`; 235 const severity = interopRule.severity; 236 const ruleId = this.rule.ruleId; 237 const filePath = problemStmtMtd?.getDeclaringArkFile()?.getFilePath() ?? ''; 238 const defeats = new Defects( 239 line, 240 column, 241 column, 242 problem, 243 desc, 244 severity, 245 ruleId, 246 filePath, 247 '', 248 true, 249 false, 250 false 251 ); 252 this.issues.push(new IssueReport(defeats, undefined)); 253 } 254 255 private checkFromStmt( 256 stmt: Stmt, 257 apiLanguage: Language, 258 res: ObjDefInfo[], 259 visited: Set<Stmt>, 260 importVarMap: Map<string, Language>, 261 topLevelVarMap: Map<string, Stmt[]>, 262 scene: Scene, 263 depth: number = 0 264 ): void { 265 if (depth > CALL_DEPTH_LIMIT) { 266 return; 267 } 268 const node = DVFGHelper.getOrNewDVFGNode(stmt, scene); 269 let worklist: DVFGNode[] = [node]; 270 while (worklist.length > 0) { 271 const current = worklist.shift()!; 272 const currentStmt = current.getStmt(); 273 if (visited.has(currentStmt)) { 274 continue; 275 } 276 visited.add(currentStmt); 277 if (currentStmt instanceof ArkAssignStmt) { 278 const rightOp = currentStmt.getRightOp(); 279 if (rightOp instanceof ArkInstanceFieldRef) { 280 // 处理 Reflect.apply(obj.getName, {a : 12}) 281 const base = rightOp.getBase(); 282 if (base instanceof Local && base.getDeclaringStmt()) { 283 worklist.push(DVFGHelper.getOrNewDVFGNode(base.getDeclaringStmt()!, scene)); 284 continue; 285 } 286 } 287 if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) { 288 const name = rightOp.getName(); 289 if (importVarMap.has(name)) { 290 res.push({ problemStmt: currentStmt, objLanguage: importVarMap.get(name)! }); 291 continue; 292 } 293 } 294 const rightOpTy = rightOp.getType(); 295 if (!this.isIrrelevantType(rightOpTy)) { 296 const rightOpTyLang = this.getTypeDefinedLang(rightOpTy, scene); 297 if (rightOpTyLang && rightOpTyLang !== apiLanguage) { 298 res.push({ problemStmt: currentStmt, objLanguage: rightOpTyLang }); 299 continue; 300 } 301 } 302 } 303 const callsite = this.cg.getCallSiteByStmt(currentStmt); 304 if (callsite.length > 0) { 305 callsite.forEach(cs => { 306 const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); 307 if (!declaringMtd || !declaringMtd.getCfg()) { 308 return; 309 } 310 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 311 declaringMtd 312 .getReturnStmt() 313 .forEach(r => 314 this.checkFromStmt( 315 r, 316 apiLanguage, 317 res, 318 visited, 319 importVarMap, 320 topLevelVarMap, 321 scene, 322 depth + 1 323 ) 324 ); 325 }); 326 continue; 327 } 328 const paramRef = this.isFromParameter(currentStmt); 329 if (paramRef) { 330 const paramIdx = paramRef.getIndex(); 331 this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()).forEach(cs => { 332 const declaringMtd = cs.getCfg().getDeclaringMethod(); 333 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 334 const argDefs = this.findArgumentDef( 335 cs, 336 paramIdx, 337 apiLanguage, 338 importVarMap, 339 topLevelVarMap, 340 scene 341 ); 342 if (this.isLanguage(argDefs)) { 343 // imported var 344 res.push({ problemStmt: cs, objLanguage: argDefs as Language }); 345 } else { 346 argDefs.forEach(d => { 347 this.checkFromStmt( 348 d, 349 apiLanguage, 350 res, 351 visited, 352 importVarMap, 353 topLevelVarMap, 354 scene, 355 depth + 1 356 ); 357 }); 358 } 359 }); 360 continue; 361 } 362 current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); 363 if (stmt instanceof ArkAssignStmt) { 364 const rightOp = stmt.getRightOp(); 365 if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) { 366 (topLevelVarMap.get(rightOp.getName()) ?? []).forEach(def => { 367 worklist.push(DVFGHelper.getOrNewDVFGNode(def, scene)); 368 }); 369 } 370 } 371 } 372 } 373 374 private isIrrelevantType(ty: Type): boolean { 375 const isObjectTy = (ty: Type): boolean => { 376 return ty instanceof ClassType && ty.getClassSignature().getClassName() === 'Object'; 377 }; 378 const isESObjectTy = (ty: Type): boolean => { 379 return ty.toString() === 'ESObject'; 380 }; 381 const isAnyTy = (ty: Type): ty is AnyType => { 382 return ty instanceof AnyType; 383 }; 384 const isUnkwonTy = (ty: Type): ty is UnknownType => { 385 return ty instanceof UnknownType; 386 }; 387 return isObjectTy(ty) || isESObjectTy(ty) || isAnyTy(ty) || isUnkwonTy(ty); 388 } 389 390 private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { 391 if (!(stmt instanceof ArkAssignStmt)) { 392 return undefined; 393 } 394 const rightOp = stmt.getRightOp(); 395 if (rightOp instanceof ArkParameterRef) { 396 return rightOp; 397 } 398 return undefined; 399 } 400 401 private isLanguage(value: Stmt[] | Language): value is Language { 402 return Object.values(Language).includes(value as Language); 403 } 404 405 private findArgumentDef( 406 stmt: Stmt, 407 argIdx: number, 408 apiLanguage: Language, 409 importVarMap: Map<string, Language>, 410 topLevelVarMap: Map<string, Stmt[]>, 411 scene: Scene 412 ): Stmt[] | Language { 413 const invoke = stmt.getInvokeExpr(); 414 const getKey = (v: Value): Value | FieldSignature => { 415 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 416 }; 417 const arg: Value | FieldSignature = getKey((invoke as AbstractInvokeExpr).getArg(argIdx)); 418 if (!arg) { 419 logger.error(`arg${argIdx} of invoke ${stmt.toString()} is undefined`); 420 return []; 421 } 422 if (arg instanceof Local && arg.getDeclaringStmt() instanceof ArkAssignStmt) { 423 // 特殊处理,obj.getName 的类型有 bug 424 const rightOp = (arg.getDeclaringStmt() as ArkAssignStmt).getRightOp(); 425 if (rightOp instanceof ArkInstanceFieldRef) { 426 const base = rightOp.getBase(); 427 if (base instanceof Local && base.getDeclaringStmt()) { 428 return [base.getDeclaringStmt()!]; 429 } 430 } 431 } 432 const argTy = arg.getType(); 433 if (!this.isIrrelevantType(argTy)) { 434 const argTyLang = this.getTypeDefinedLang(argTy, scene); 435 if (argTyLang && argTyLang !== apiLanguage) { 436 return argTyLang; 437 } 438 } 439 if (arg instanceof Local && !arg.getDeclaringStmt()) { 440 const name = arg.getName(); 441 return topLevelVarMap.get(name) ?? importVarMap.get(name) ?? []; 442 } 443 return Array.from(DVFGHelper.getOrNewDVFGNode(stmt, scene).getIncomingEdge()) 444 .map(e => (e.getSrcNode() as DVFGNode).getStmt()) 445 .filter(s => { 446 return s instanceof ArkAssignStmt && arg === getKey(s.getLeftOp()); 447 }); 448 } 449 450 private getTypeDefinedLang(type: Type, scene: Scene): Language | undefined { 451 let file = undefined; 452 if (type instanceof ClassType) { 453 file = scene.getFile(type.getClassSignature().getDeclaringFileSignature()); 454 } else if (type instanceof FunctionType) { 455 file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature()); 456 } 457 if (file) { 458 return file.getLanguage(); 459 } 460 return undefined; 461 } 462} 463