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 FunctionType, 28 ClassType, 29 ArkNamespace, 30 PrimitiveType, 31 UnclearReferenceType, 32} from 'arkanalyzer/lib'; 33import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 34import { BaseChecker, BaseMetaData } from '../BaseChecker'; 35import { Rule, Defects, MatcherCallback } from '../../Index'; 36import { IssueReport } from '../../model/Defects'; 37import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 38import { CALL_DEPTH_LIMIT, DVFGHelper, GlobalCallGraphHelper } from './Utils'; 39import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; 40 41const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropAssignCheck'); 42const gMetaData: BaseMetaData = { 43 severity: 1, 44 ruleDocPath: '', 45 description: 'Should not pass or assign a dynamic object to a variable of static type', 46}; 47 48const RULE_ID = 'arkts-interop-s2d-dynamic-args-to-static'; 49const BOXED_SET: Set<string> = new Set<string>(['String', 'Boolean', 'BigInt', 'Number']); 50export class InteropAssignCheck implements BaseChecker { 51 readonly metaData: BaseMetaData = gMetaData; 52 public rule: Rule; 53 public defects: Defects[] = []; 54 public issues: IssueReport[] = []; 55 private cg: CallGraph; 56 57 public registerMatchers(): MatcherCallback[] { 58 const matchBuildCb: MatcherCallback = { 59 matcher: undefined, 60 callback: this.check, 61 }; 62 return [matchBuildCb]; 63 } 64 65 public check = (scene: Scene): void => { 66 this.cg = GlobalCallGraphHelper.getCGInstance(scene); 67 68 for (let arkFile of scene.getFiles()) { 69 for (let clazz of arkFile.getClasses()) { 70 for (let mtd of clazz.getMethods()) { 71 this.processArkMethod(mtd, scene); 72 } 73 } 74 for (let namespace of arkFile.getAllNamespacesUnderThisFile()) { 75 this.processNameSpace(namespace, scene); 76 } 77 } 78 }; 79 80 public processNameSpace(namespace: ArkNamespace, scene: Scene): void { 81 for (let clazz of namespace.getClasses()) { 82 for (let mtd of clazz.getMethods()) { 83 this.processArkMethod(mtd, scene); 84 } 85 } 86 } 87 88 public processArkMethod(target: ArkMethod, scene: Scene): void { 89 if (target.getLanguage() === Language.ARKTS1_2) { 90 this.checkPassToFunction(target, scene); 91 } else if (target.getLanguage() === Language.ARKTS1_1) { 92 this.checkAssignToField(target, scene); 93 } 94 } 95 96 private checkPassToFunction(target: ArkMethod, scene: Scene) { 97 const callsites = this.cg.getInvokeStmtByMethod(target.getSignature()); 98 callsites 99 .filter(cs => cs.getCfg().getDeclaringMethod().getLanguage() === Language.ARKTS1_1) 100 .forEach(cs => { 101 let hasTargetArg = false; 102 const invoke = cs.getInvokeExpr()!; 103 const csMethod = cs.getCfg().getDeclaringMethod(); 104 invoke.getArgs().forEach(arg => { 105 const argTy = arg.getType(); 106 if (argTy instanceof PrimitiveType || this.isBoxedType(argTy)) { 107 return; 108 } 109 const argTyLang = this.getTypeDefinedLang(argTy, scene) ?? csMethod?.getLanguage() ?? Language.UNKNOWN; 110 if (argTyLang === Language.ARKTS1_1) { 111 hasTargetArg = true; 112 } 113 }); 114 if (!hasTargetArg) { 115 return; 116 } 117 let line = cs.getOriginPositionInfo().getLineNo(); 118 let column = cs.getOriginPositionInfo().getColNo(); 119 const problem = 'Interop'; 120 const desc = `${this.metaData.description} (${RULE_ID})`; 121 const severity = this.metaData.severity; 122 const ruleId = this.rule.ruleId; 123 const filePath = csMethod?.getDeclaringArkFile()?.getFilePath() ?? ''; 124 const defeats = new Defects(line, column, column, problem, desc, severity, ruleId, filePath, '', true, false, false); 125 this.issues.push(new IssueReport(defeats, undefined)); 126 }); 127 } 128 129 private isBoxedType(checkType: Type): boolean { 130 const unclear = checkType instanceof UnclearReferenceType && BOXED_SET.has(checkType.getName()); 131 const cls = checkType instanceof ClassType && BOXED_SET.has(checkType.getClassSignature().getClassName()); 132 return unclear || cls; 133 } 134 135 private checkAssignToField(target: ArkMethod, scene: Scene) { 136 const assigns: Stmt[] = this.collectAssignToObjectField(target, scene); 137 if (assigns.length > 0) { 138 DVFGHelper.buildSingleDVFG(target, scene); 139 } 140 assigns.forEach(assign => { 141 let result: Stmt[] = []; 142 let visited: Set<Stmt> = new Set(); 143 this.checkFromStmt(assign, scene, result, visited); 144 if (result.length === 0) { 145 // 这句 a.data = y 右侧的值没有从 1.1 传入的可能 146 return; 147 } 148 let line = assign.getOriginPositionInfo().getLineNo(); 149 let column = assign.getOriginPositionInfo().getColNo(); 150 const problem = 'Interop'; 151 const desc = `${this.metaData.description} (${RULE_ID})`; 152 const severity = this.metaData.severity; 153 const ruleId = this.rule.ruleId; 154 const filePath = assign.getCfg().getDeclaringMethod().getDeclaringArkFile()?.getFilePath() ?? ''; 155 const defeats = new Defects(line, column, column, problem, desc, severity, ruleId, filePath, '', true, false, false); 156 this.issues.push(new IssueReport(defeats, undefined)); 157 }); 158 } 159 160 private collectAssignToObjectField(method: ArkMethod, scene: Scene): Stmt[] { 161 const res: Stmt[] = []; 162 const stmts = method.getBody()?.getCfg().getStmts() ?? []; 163 for (const stmt of stmts) { 164 if (!(stmt instanceof ArkAssignStmt)) { 165 continue; 166 } 167 const leftOp = stmt.getLeftOp(); 168 if (!(leftOp instanceof ArkInstanceFieldRef)) { 169 continue; 170 } 171 if (!this.isObjectTy(leftOp.getType())) { 172 continue; 173 } 174 const baseTy = leftOp.getBase().getType(); 175 if (baseTy instanceof ClassType) { 176 const klass = scene.getClass(baseTy.getClassSignature()); 177 if (!klass) { 178 logger.warn(`check field of type 'Object' failed: cannot find arkclass by sig ${baseTy.getClassSignature().toString()}`); 179 } else if (klass.getLanguage() === Language.ARKTS1_2) { 180 res.push(stmt); 181 } 182 } else { 183 logger.warn(`check field of type 'Object' failed: unexpected base type ${baseTy.toString()}`); 184 } 185 } 186 return res; 187 } 188 189 private checkFromStmt(stmt: Stmt, scene: Scene, res: Stmt[], visited: Set<Stmt>, depth: number = 0): void { 190 if (depth > CALL_DEPTH_LIMIT) { 191 return; 192 } 193 const node = DVFGHelper.getOrNewDVFGNode(stmt, scene); 194 let worklist: DVFGNode[] = [node]; 195 while (worklist.length > 0) { 196 const current = worklist.shift()!; 197 const currentStmt = current.getStmt(); 198 if (visited.has(currentStmt)) { 199 continue; 200 } 201 visited.add(currentStmt); 202 if (currentStmt instanceof ArkAssignStmt) { 203 const rightOpTy = currentStmt.getRightOp().getType(); 204 if (!this.isObjectTy(rightOpTy) && this.getTypeDefinedLang(rightOpTy, scene) === Language.ARKTS1_1) { 205 res.push(currentStmt); 206 continue; 207 } 208 } 209 const callsite = this.cg.getCallSiteByStmt(currentStmt); 210 callsite.forEach(cs => { 211 const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); 212 if (!declaringMtd || !declaringMtd.getCfg()) { 213 return; 214 } 215 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 216 declaringMtd.getReturnStmt().forEach(r => this.checkFromStmt(r, scene, res, visited, depth + 1)); 217 }); 218 const paramRef = this.isFromParameter(currentStmt); 219 if (paramRef) { 220 const paramIdx = paramRef.getIndex(); 221 const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); 222 callsites.forEach(cs => { 223 const declaringMtd = cs.getCfg().getDeclaringMethod(); 224 DVFGHelper.buildSingleDVFG(declaringMtd, scene); 225 }); 226 this.collectArgDefs(paramIdx, callsites, scene).forEach(d => this.checkFromStmt(d, scene, res, visited, depth + 1)); 227 } 228 current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); 229 } 230 } 231 232 private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { 233 if (!(stmt instanceof ArkAssignStmt)) { 234 return undefined; 235 } 236 const rightOp = stmt.getRightOp(); 237 if (rightOp instanceof ArkParameterRef) { 238 return rightOp; 239 } 240 return undefined; 241 } 242 243 private collectArgDefs(argIdx: number, callsites: Stmt[], scene: Scene): Stmt[] { 244 const getKey = (v: Value): Value | FieldSignature => { 245 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 246 }; 247 return callsites.flatMap(callsite => { 248 const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx)); 249 return Array.from(DVFGHelper.getOrNewDVFGNode(callsite, scene).getIncomingEdge()) 250 .map(e => (e.getSrcNode() as DVFGNode).getStmt()) 251 .filter(s => { 252 return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp()); 253 }); 254 }); 255 } 256 257 private isObjectTy(ty: Type): boolean { 258 return ty instanceof ClassType && ty.getClassSignature().getClassName() === 'Object'; 259 } 260 261 private getTypeDefinedLang(type: Type, scene: Scene): Language | undefined { 262 let file = undefined; 263 if (type instanceof ClassType) { 264 file = scene.getFile(type.getClassSignature().getDeclaringFileSignature()); 265 } else if (type instanceof FunctionType) { 266 file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature()); 267 } 268 if (file) { 269 return file.getLanguage(); 270 } 271 return undefined; 272 } 273} 274