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 ArkClass, 24 ArkFile, 25 ArkInstanceOfExpr, 26 ArkNewExpr, 27 CallGraph, 28 ArkParameterRef, 29 ArkInstanceFieldRef, 30 AbstractFieldRef, 31 Local, 32 ArkArrayRef, 33 ClassSignature, 34} from 'arkanalyzer/lib'; 35import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 36import { BaseChecker, BaseMetaData } from '../BaseChecker'; 37import { Rule, Defects, MatcherCallback } from '../../Index'; 38import { IssueReport } from '../../model/Defects'; 39import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 40import { DVFGHelper, CALL_DEPTH_LIMIT, GlobalCallGraphHelper } from './Utils'; 41import { WarnInfo } from '../../utils/common/Utils'; 42 43const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ObjectLiteralCheck'); 44const gMetaData: BaseMetaData = { 45 severity: 1, 46 ruleDocPath: '', 47 description: 'Object literal shall generate instance of a specific class', 48}; 49 50export class ObjectLiteralCheck implements BaseChecker { 51 readonly metaData: BaseMetaData = gMetaData; 52 public rule: Rule; 53 public defects: Defects[] = []; 54 public issues: IssueReport[] = []; 55 private cg: CallGraph; 56 private visited: Set<ArkMethod> = new Set(); 57 58 public registerMatchers(): MatcherCallback[] { 59 const matchBuildCb: MatcherCallback = { 60 matcher: undefined, 61 callback: this.check, 62 }; 63 return [matchBuildCb]; 64 } 65 66 public check = (scene: Scene): void => { 67 this.cg = GlobalCallGraphHelper.getCGInstance(scene); 68 69 for (let arkFile of scene.getFiles()) { 70 const topLevelVarMap: Map<string, Stmt[]> = new Map(); 71 this.collectImportedVar(topLevelVarMap, arkFile, scene); 72 this.collectTopLevelVar(topLevelVarMap, arkFile, scene); 73 74 const handleClass = (cls: ArkClass): void => { 75 cls.getMethods().forEach(m => this.processArkMethod(m, topLevelVarMap, scene)); 76 }; 77 78 arkFile.getClasses().forEach(cls => handleClass(cls)); 79 arkFile.getAllNamespacesUnderThisFile().forEach(n => n.getClasses().forEach(cls => handleClass(cls))); 80 } 81 }; 82 83 public processArkMethod(target: ArkMethod, topLevelVarMap: Map<string, Stmt[]>, scene: Scene): void { 84 const stmts = target.getBody()?.getCfg().getStmts() ?? []; 85 for (const stmt of stmts) { 86 if (!(stmt instanceof ArkAssignStmt)) { 87 continue; 88 } 89 const rightOp = stmt.getRightOp(); 90 if (!(rightOp instanceof ArkInstanceOfExpr)) { 91 continue; 92 } 93 if (!this.visited.has(target)) { 94 DVFGHelper.buildSingleDVFG(target, scene); 95 this.visited.add(target); 96 } 97 98 let result: Stmt[] = []; 99 let checkAll = { value: true }; 100 let visited: Set<Stmt> = new Set(); 101 this.checkFromStmt(stmt, scene, result, topLevelVarMap, checkAll, visited); 102 result.forEach(s => this.addIssueReport(s, (s as ArkAssignStmt).getRightOp())); 103 if (!checkAll.value) { 104 this.addIssueReport(stmt, rightOp, checkAll.value); 105 } 106 } 107 } 108 109 private collectImportedVar(importVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) { 110 file.getImportInfos().forEach(importInfo => { 111 const exportInfo = importInfo.getLazyExportInfo(); 112 if (exportInfo === null) { 113 return; 114 } 115 const arkExport = exportInfo.getArkExport(); 116 if (!arkExport || !(arkExport instanceof Local)) { 117 return; 118 } 119 const declaringStmt = arkExport.getDeclaringStmt(); 120 if (!declaringStmt) { 121 return; 122 } 123 DVFGHelper.buildSingleDVFG(declaringStmt.getCfg().getDeclaringMethod(), scene); 124 importVarMap.set(arkExport.getName(), [declaringStmt]); 125 }); 126 } 127 128 private collectTopLevelVar(topLevelVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) { 129 const defaultMethod = file.getDefaultClass().getDefaultArkMethod(); 130 if (!defaultMethod) { 131 return; 132 } 133 DVFGHelper.buildSingleDVFG(defaultMethod, scene); 134 const stmts = defaultMethod.getBody()?.getCfg().getStmts() ?? []; 135 for (const stmt of stmts) { 136 if (!(stmt instanceof ArkAssignStmt)) { 137 continue; 138 } 139 const leftOp = stmt.getLeftOp(); 140 if (!(leftOp instanceof Local)) { 141 continue; 142 } 143 const name = leftOp.getName(); 144 if (name.startsWith('%') || name === 'this') { 145 continue; 146 } 147 topLevelVarMap.set(name, [...(topLevelVarMap.get(name) ?? []), stmt]); 148 } 149 } 150 151 private checkFromStmt( 152 stmt: Stmt, 153 scene: Scene, 154 res: Stmt[], 155 topLevelVarMap: Map<string, Stmt[]>, 156 checkAll: { value: boolean }, 157 visited: Set<Stmt>, 158 depth: number = 0 159 ): void { 160 if (depth > CALL_DEPTH_LIMIT) { 161 checkAll.value = false; 162 return; 163 } 164 const node = DVFGHelper.getOrNewDVFGNode(stmt, scene); 165 let worklist: DVFGNode[] = [node]; 166 while (worklist.length > 0) { 167 const current = worklist.shift()!; 168 const currentStmt = current.getStmt(); 169 if (visited.has(currentStmt)) { 170 continue; 171 } 172 visited.add(currentStmt); 173 if (this.isObjectLiteral(currentStmt, scene)) { 174 res.push(currentStmt); 175 continue; 176 } 177 const isClsField = this.isClassField(currentStmt, scene); 178 if (isClsField) { 179 isClsField.forEach(d => worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene))); 180 continue; 181 } 182 const isArrayField = this.isArrayField(currentStmt, topLevelVarMap); 183 if (isArrayField) { 184 isArrayField.forEach(d => worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene))); 185 continue; 186 } 187 const gv = this.checkIfIsTopLevelVar(currentStmt); 188 if (gv) { 189 const globalDefs = topLevelVarMap.get(gv.getName()); 190 globalDefs?.forEach(d => { 191 worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene)); 192 }); 193 continue; 194 } 195 const callsite = this.cg.getCallSiteByStmt(currentStmt); 196 callsite.forEach(cs => { 197 const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); 198 if (!declaringMtd || !declaringMtd.getCfg()) { 199 return; 200 } 201 if (!this.visited.has(declaringMtd)) { 202 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 203 this.visited.add(declaringMtd); 204 } 205 declaringMtd.getReturnStmt().forEach(r => this.checkFromStmt(r, scene, res, topLevelVarMap, checkAll, visited, depth + 1)); 206 }); 207 const paramRef = this.isFromParameter(currentStmt); 208 if (paramRef) { 209 const paramIdx = paramRef.getIndex(); 210 const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); 211 this.processCallsites(callsites, scene); 212 this.collectArgDefs(paramIdx, callsites, scene).forEach(d => this.checkFromStmt(d, scene, res, topLevelVarMap, checkAll, visited, depth + 1)); 213 } 214 current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); 215 } 216 } 217 218 private checkIfIsTopLevelVar(stmt: Stmt): Local | undefined { 219 if (!(stmt instanceof ArkAssignStmt)) { 220 return undefined; 221 } 222 const rightOp = stmt.getRightOp(); 223 if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) { 224 return rightOp; 225 } 226 if (!(rightOp instanceof ArkInstanceOfExpr)) { 227 return undefined; 228 } 229 const obj = rightOp.getOp(); 230 if (obj instanceof Local && !obj.getDeclaringStmt()) { 231 return obj; 232 } 233 return undefined; 234 } 235 236 private processCallsites(callsites: Stmt[], scene: Scene): void { 237 callsites.forEach(cs => { 238 const declaringMtd = cs.getCfg().getDeclaringMethod(); 239 if (!this.visited.has(declaringMtd)) { 240 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 241 this.visited.add(declaringMtd); 242 } 243 }); 244 } 245 246 private isObjectLiteral(stmt: Stmt, scene: Scene): boolean { 247 if (!(stmt instanceof ArkAssignStmt)) { 248 return false; 249 } 250 const rightOp = stmt.getRightOp(); 251 if (!(rightOp instanceof ArkNewExpr)) { 252 return false; 253 } 254 const classSig = rightOp.getClassType().getClassSignature(); 255 if (scene.getClass(classSig)?.isAnonymousClass()) { 256 return true; 257 } 258 return false; 259 } 260 261 private isClassField(stmt: Stmt, scene: Scene): Stmt[] | undefined { 262 if (!(stmt instanceof ArkAssignStmt)) { 263 return undefined; 264 } 265 const clsField = stmt.getRightOp(); 266 if (!(clsField instanceof AbstractFieldRef)) { 267 return undefined; 268 } 269 if (clsField instanceof ArkInstanceFieldRef && clsField.getBase().getName() !== 'this') { 270 return undefined; 271 } 272 const fieldSig = clsField.getFieldSignature(); 273 const clsSig = fieldSig.getDeclaringSignature(); 274 if (!(clsSig instanceof ClassSignature)) { 275 return undefined; 276 } 277 const cls = scene.getClass(clsSig); 278 if (!cls) { 279 logger.error(`cannot find class based on class sig: ${clsSig.toString()}`); 280 return undefined; 281 } 282 const field = cls.getField(fieldSig); 283 if (!field) { 284 logger.error(`cannot find field based on field sig: ${fieldSig.toString()}`); 285 return undefined; 286 } 287 if (!field.isStatic()) { 288 DVFGHelper.buildSingleDVFG(cls.getInstanceInitMethod(), scene); 289 } else { 290 DVFGHelper.buildSingleDVFG(cls.getStaticInitMethod(), scene); 291 } 292 return field.getInitializer().slice(-1); 293 } 294 295 private isArrayField(stmt: Stmt, topLevelVarMap: Map<string, Stmt[]>): Stmt[] | undefined { 296 if (!(stmt instanceof ArkAssignStmt)) { 297 return undefined; 298 } 299 const arrField = stmt.getRightOp(); 300 if (!(arrField instanceof ArkArrayRef)) { 301 return undefined; 302 } 303 const arr = arrField.getBase(); 304 const index = arrField.getIndex(); 305 let arrDeclarations: Stmt[] = []; 306 if (arr.getDeclaringStmt()) { 307 arrDeclarations.push(arr.getDeclaringStmt()!); 308 } else if (topLevelVarMap.has(arr.getName())) { 309 arrDeclarations = topLevelVarMap.get(arr.getName())!; 310 } 311 const res: Stmt[] = arrDeclarations.flatMap(d => { 312 // arr = %0 313 // %0[0] = ... 314 if (!(d instanceof ArkAssignStmt)) { 315 return []; 316 } 317 const arrVal = d.getRightOp(); 318 if (!(arrVal instanceof Local)) { 319 return []; 320 } 321 return arrVal.getUsedStmts().filter(u => { 322 if (!(u instanceof ArkAssignStmt)) { 323 return false; 324 } 325 const left = u.getLeftOp(); 326 return left instanceof ArkArrayRef && left.getBase() === arrVal && left.getIndex() === index; 327 }); 328 }); 329 return res; 330 } 331 332 private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { 333 if (!(stmt instanceof ArkAssignStmt)) { 334 return undefined; 335 } 336 const rightOp = stmt.getRightOp(); 337 if (rightOp instanceof ArkParameterRef) { 338 return rightOp; 339 } 340 return undefined; 341 } 342 343 private collectArgDefs(argIdx: number, callsites: Stmt[], scene: Scene): Stmt[] { 344 const getKey = (v: Value): Value | FieldSignature => { 345 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 346 }; 347 return callsites.flatMap(callsite => { 348 const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx)); 349 return Array.from(DVFGHelper.getOrNewDVFGNode(callsite, scene).getIncomingEdge()) 350 .map(e => (e.getSrcNode() as DVFGNode).getStmt()) 351 .filter(s => { 352 return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp()); 353 }); 354 }); 355 } 356 357 private addIssueReport(stmt: Stmt, operand: Value, checkAll: boolean = true): void { 358 const severity = this.rule.alert ?? this.metaData.severity; 359 const warnInfo = this.getLineAndColumn(stmt, operand); 360 const problem = 'ObjectLiteral'; 361 let desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`; 362 if (!checkAll) { 363 desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${this.rule.ruleId.replace('@migration/', '')})`; 364 } 365 let defects = new Defects( 366 warnInfo.line, 367 warnInfo.startCol, 368 warnInfo.endCol, 369 problem, 370 desc, 371 severity, 372 this.rule.ruleId, 373 warnInfo.filePath, 374 this.metaData.ruleDocPath, 375 true, 376 false, 377 false 378 ); 379 this.issues.push(new IssueReport(defects, undefined)); 380 } 381 382 private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo { 383 const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); 384 const originPosition = stmt.getOperandOriginalPosition(operand); 385 if (arkFile && originPosition) { 386 const originPath = arkFile.getFilePath(); 387 const line = originPosition.getFirstLine(); 388 const startCol = originPosition.getFirstCol(); 389 const endCol = startCol; 390 return { line, startCol, endCol, filePath: originPath }; 391 } else { 392 logger.debug('ArkFile is null.'); 393 } 394 return { line: -1, startCol: -1, endCol: -1, filePath: '' }; 395 } 396} 397