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 Scene, 21 ArkInstanceFieldRef, 22 FunctionType, 23 ClassType, 24 MethodSignature, 25 ArkParameterRef, 26 Value, 27 Stmt, 28 ArkDeleteExpr, 29 FieldSignature, 30 ArkNamespace, 31} from 'arkanalyzer/lib'; 32import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 33import { BaseChecker, BaseMetaData } from '../BaseChecker'; 34import { Rule, Defects, MatcherCallback } from '../../Index'; 35import { IssueReport } from '../../model/Defects'; 36import { ArkFile, Language } from 'arkanalyzer/lib/core/model/ArkFile'; 37import { CALL_DEPTH_LIMIT, DVFGHelper } from './Utils'; 38import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 39 40const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropJSModifyPropertyCheck'); 41const gMetaData: BaseMetaData = { 42 severity: 1, 43 ruleDocPath: '', 44 description: 'The layout of staic objects should not be modified', 45}; 46 47const RULE_ID = 'arkts-interop-js2s-js-add-detele-static-prop'; 48 49class ParamInfo { 50 flag: boolean; 51 paramAssign: ArkAssignStmt; 52 callsites: Set<Stmt>; 53} 54 55export class InteropJSModifyPropertyCheck implements BaseChecker { 56 readonly metaData: BaseMetaData = gMetaData; 57 public rule: Rule; 58 public defects: Defects[] = []; 59 public issues: IssueReport[] = []; 60 61 public registerMatchers(): MatcherCallback[] { 62 const matchBuildCb: MatcherCallback = { 63 matcher: undefined, 64 callback: this.check, 65 }; 66 return [matchBuildCb]; 67 } 68 69 public check = (scene: Scene): void => { 70 const targetMethods: Map<MethodSignature, ParamInfo[]> = new Map(); 71 this.collectTargetMethods(targetMethods, scene); 72 this.checkTargetMethods(targetMethods, scene); 73 }; 74 75 private collectTargetMethods(targetMethods: Map<MethodSignature, ParamInfo[]>, scene: Scene): void { 76 scene.getFiles().forEach(file => { 77 // 只处理 ArkTS1.2 import JS 函数的情况 78 if (file.getLanguage() !== Language.ARKTS1_2) { 79 return; 80 } 81 file.getImportInfos().forEach(importInfo => { 82 const exportInfo = importInfo.getLazyExportInfo(); 83 if (exportInfo === null) { 84 return; 85 } 86 const arkExport = exportInfo.getArkExport(); 87 if (arkExport === null || arkExport === undefined) { 88 return; 89 } 90 if (!(arkExport instanceof ArkMethod && arkExport.getLanguage() === Language.JAVASCRIPT)) { 91 return; 92 } 93 // 创建初始化的参数标志位信息(标志位代表该参数是否被传入了 1.2 对象,默认为 false) 94 const paramInfo = this.createParamInfo(arkExport); 95 if (paramInfo.length > 0) { 96 targetMethods.set(arkExport.getSignature(), paramInfo); 97 } 98 }); 99 100 // 寻找 JS 函数的被调用点,检查是否传入 ArkTS1.2 类型的对象并维护参数标志位信息 101 for (let clazz of file.getClasses()) { 102 for (let mtd of clazz.getMethods()) { 103 this.findCallsite(mtd, targetMethods, scene); 104 } 105 } 106 for (let namespace of file.getAllNamespacesUnderThisFile()) { 107 this.processNameSpace(namespace, targetMethods, scene); 108 } 109 }); 110 111 // 跨函数检查 ArkTS1.2 类型对象是否被跨函数传到其他 JS 函数 112 for (let i = 0; i < CALL_DEPTH_LIMIT; ++i) { 113 this.collectInterprocedualCallSites(targetMethods, scene); 114 } 115 } 116 117 public processNameSpace( 118 namespace: ArkNamespace, 119 targetMethods: Map<MethodSignature, ParamInfo[]>, 120 scene: Scene 121 ): void { 122 for (let clazz of namespace.getClasses()) { 123 for (let mtd of clazz.getMethods()) { 124 this.findCallsite(mtd, targetMethods, scene); 125 } 126 } 127 } 128 129 private createParamInfo(method: ArkMethod): ParamInfo[] { 130 // 初始化参数标志数组 131 const idxFlag = new Array(method.getParameters().length).fill(false); 132 // 寻找参数对应的 xxx = parameter 语句 133 const paramAssigns = (method.getCfg()?.getStmts() ?? []) 134 .filter(s => s instanceof ArkAssignStmt && s.getRightOp() instanceof ArkParameterRef) 135 .map(s => s as ArkAssignStmt); 136 if (idxFlag.length !== paramAssigns.length) { 137 logger.error('param index num != param assign num'); 138 return []; 139 } 140 const result: ParamInfo[] = []; 141 for (let i = 0; i < idxFlag.length; i++) { 142 result.push({ flag: idxFlag[i], paramAssign: paramAssigns[i], callsites: new Set() }); 143 } 144 return result; 145 } 146 147 private findCallsite(method: ArkMethod, targets: Map<MethodSignature, ParamInfo[]>, scene: Scene): void { 148 const stmts = method.getBody()?.getCfg().getStmts() ?? []; 149 for (const stmt of stmts) { 150 const invoke = stmt.getInvokeExpr(); 151 if (!invoke) { 152 continue; 153 } 154 const methodSig = invoke.getMethodSignature(); 155 if (!targets.has(methodSig)) { 156 continue; 157 } 158 invoke.getArgs().forEach((arg, idx): void => { 159 if (this.getTypeDefinedLang(arg.getType(), method.getDeclaringArkFile(), scene) !== Language.ARKTS1_2) { 160 return; 161 } 162 targets.get(methodSig)![idx].flag = true; 163 targets.get(methodSig)![idx].callsites.add(stmt); 164 }); 165 } 166 } 167 168 private collectInterprocedualCallSites(targets: Map<MethodSignature, ParamInfo[]>, scene: Scene): void { 169 new Map(targets).forEach((paramInfo, sig) => { 170 const method = scene.getMethod(sig)!; 171 DVFGHelper.buildSingleDVFG(method, scene); 172 paramInfo.forEach((paramInfo): void => { 173 if (paramInfo.flag) { 174 this.checkIfParamPassedToOtherMethod(paramInfo, targets, scene); 175 } 176 }); 177 }); 178 } 179 180 private checkIfParamPassedToOtherMethod( 181 callerParamInfo: ParamInfo, 182 targets: Map<MethodSignature, ParamInfo[]>, 183 scene: Scene 184 ): void { 185 const worklist: DVFGNode[] = [DVFGHelper.getOrNewDVFGNode(callerParamInfo.paramAssign, scene)]; 186 const visited: Set<Stmt> = new Set(); 187 while (worklist.length > 0) { 188 const current = worklist.shift()!; 189 const cunrrentStmt = current.getStmt(); 190 if (visited.has(cunrrentStmt)) { 191 continue; 192 } 193 visited.add(cunrrentStmt); 194 if (!(cunrrentStmt instanceof ArkAssignStmt)) { 195 continue; 196 } 197 current.getOutgoingEdges().forEach(edge => { 198 const dst = edge.getDstNode() as DVFGNode; 199 const dstStmt = dst.getStmt(); 200 // 假设有 JS 函数声明: function foo(obj),其中 obj 为受关注的参数 201 // 只有类似 let obj2 = obj 或者 goo(obj) 这样的语句受到关注 202 if (dstStmt instanceof ArkAssignStmt && dstStmt.getRightOp() === cunrrentStmt.getLeftOp()) { 203 worklist.push(dst); 204 return; 205 } 206 const invoke = dstStmt.getInvokeExpr(); 207 if (!invoke) { 208 return; 209 } 210 const calleeSig = invoke.getMethodSignature(); 211 const callee = scene.getMethod(calleeSig); 212 if (!callee || !callee.getCfg() || callee.getLanguage() !== Language.JAVASCRIPT) { 213 return; 214 } 215 if (!targets.has(calleeSig)) { 216 targets.set(calleeSig, this.createParamInfo(callee)); 217 } 218 const getKey = (v: Value): Value | FieldSignature => { 219 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 220 }; 221 invoke.getArgs().forEach((arg, idx) => { 222 if (getKey(arg) === getKey(cunrrentStmt.getLeftOp())) { 223 targets.get(calleeSig)![idx].flag = true; 224 callerParamInfo.callsites.forEach(cs => { 225 targets.get(calleeSig)![idx].callsites.add(cs); 226 }); 227 } 228 }); 229 }); 230 } 231 } 232 233 private checkTargetMethods(targetMethods: Map<MethodSignature, ParamInfo[]>, scene: Scene): void { 234 targetMethods.forEach((paramInfos, methodSig): void => { 235 const method = scene.getMethod(methodSig); 236 if (!method) { 237 logger.error(`cannot find ark method by method sig: ${methodSig.toString()}`); 238 return; 239 } 240 const targetParams = paramInfos 241 .filter(paramInfo => paramInfo.flag) 242 .map(paramInfo => paramInfo.paramAssign.getLeftOp()); 243 const stmts = method.getBody()?.getCfg().getStmts() ?? []; 244 for (const stmt of stmts) { 245 if (!(stmt instanceof ArkAssignStmt)) { 246 continue; 247 } 248 let paramIdx = -1; 249 const rightOp = stmt.getRightOp(); 250 if (rightOp instanceof ArkDeleteExpr) { 251 const fieldRef = rightOp.getField(); 252 if (fieldRef instanceof ArkInstanceFieldRef) { 253 paramIdx = targetParams.findIndex(p => p === fieldRef.getBase()); 254 } 255 } 256 const leftOp = stmt.getLeftOp(); 257 if (leftOp instanceof ArkInstanceFieldRef) { 258 paramIdx = targetParams.findIndex(p => p === leftOp.getBase()); 259 } 260 if (paramIdx !== -1) { 261 const callers = paramInfos[paramIdx]!; 262 callers.callsites.forEach(cs => this.reportIssue(cs)); 263 } 264 } 265 }); 266 } 267 268 private getTypeDefinedLang(type: Type, defaultFile: ArkFile, scene: Scene): Language { 269 let file; 270 if (type instanceof ClassType) { 271 file = scene.getFile(type.getClassSignature().getDeclaringFileSignature()); 272 } else if (type instanceof FunctionType) { 273 file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature()); 274 } else { 275 file = defaultFile; 276 } 277 if (file) { 278 return file.getLanguage(); 279 } 280 return Language.UNKNOWN; 281 } 282 283 private reportIssue(problemStmt: Stmt) { 284 const line = problemStmt.getOriginPositionInfo().getLineNo(); 285 const column = problemStmt.getOriginPositionInfo().getColNo(); 286 const problem = 'Interop'; 287 const desc = `${this.metaData.description} (${RULE_ID})`; 288 const severity = this.metaData.severity; 289 const ruleId = this.rule.ruleId; 290 const filePath = problemStmt.getCfg().getDeclaringMethod().getDeclaringArkFile()?.getFilePath() ?? ''; 291 const defeats = new Defects( 292 line, 293 column, 294 column, 295 problem, 296 desc, 297 severity, 298 ruleId, 299 filePath, 300 '', 301 true, 302 false, 303 false 304 ); 305 this.issues.push(new IssueReport(defeats, undefined)); 306 } 307} 308