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 ArkInstanceOfExpr, 25 ArkNewExpr, 26 CallGraph, 27 ArkParameterRef, 28 ArkInstanceFieldRef, 29 ClassType, 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 { DVFG, DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; 37import { CALL_DEPTH_LIMIT, getLanguageStr, getLineAndColumn, GlobalCallGraphHelper } from './Utils'; 38import { ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass'; 39import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; 40 41const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropObjectLiteralCheck'); 42const gMetaData: BaseMetaData = { 43 severity: 1, 44 ruleDocPath: '', 45 description: '', 46}; 47 48const d2sRuleId: string = 'arkts-interop-d2s-object-literal'; 49const ts2sRuleId: string = 'arkts-interop-ts2s-object-literal'; 50 51export class InteropObjectLiteralCheck implements BaseChecker { 52 readonly metaData: BaseMetaData = gMetaData; 53 public rule: Rule; 54 public defects: Defects[] = []; 55 public issues: IssueReport[] = []; 56 private cg: CallGraph; 57 private dvfg: DVFG; 58 private dvfgBuilder: DVFGBuilder; 59 private visited: Set<ArkMethod> = new Set(); 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 this.cg = GlobalCallGraphHelper.getCGInstance(scene); 71 72 this.dvfg = new DVFG(this.cg); 73 this.dvfgBuilder = new DVFGBuilder(this.dvfg, scene); 74 75 for (let arkFile of scene.getFiles()) { 76 if (arkFile.getLanguage() !== Language.ARKTS1_2) { 77 continue; 78 } 79 for (let clazz of arkFile.getClasses()) { 80 for (let mtd of clazz.getMethods()) { 81 this.processArkMethod(mtd, scene); 82 } 83 } 84 for (let namespace of arkFile.getAllNamespacesUnderThisFile()) { 85 this.processNameSpace(namespace, scene); 86 } 87 } 88 }; 89 90 public processNameSpace(namespace: ArkNamespace, scene: Scene): void { 91 for (let clazz of namespace.getClasses()) { 92 for (let mtd of clazz.getMethods()) { 93 this.processArkMethod(mtd, scene); 94 } 95 } 96 } 97 98 public processArkMethod(target: ArkMethod, scene: Scene): void { 99 const stmts = target.getBody()?.getCfg().getStmts() ?? []; 100 for (const stmt of stmts) { 101 if (!(stmt instanceof ArkAssignStmt)) { 102 continue; 103 } 104 const rightOp = stmt.getRightOp(); 105 if (!(rightOp instanceof ArkInstanceOfExpr)) { 106 continue; 107 } 108 if (!this.visited.has(target)) { 109 this.dvfgBuilder.buildForSingleMethod(target); 110 this.visited.add(target); 111 } 112 113 let checkAll = { value: true }; 114 let visited: Set<Stmt> = new Set(); 115 116 // 对于待检查的instanceof语句,其检查对象存在用字面量赋值的情况,需要判断对象声明时的类型注解的来源,满足interop场景时需在此处告警 117 if (this.checkFromStmt(stmt, scene, checkAll, visited)) { 118 const opType = rightOp.getOp().getType(); 119 if (!(opType instanceof ClassType)) { 120 continue; 121 } 122 const opTypeClass = scene.getClass(opType.getClassSignature()); 123 if (opTypeClass === null || opTypeClass.getCategory() === ClassCategory.OBJECT) { 124 continue; 125 } 126 if (opTypeClass.getLanguage() === Language.TYPESCRIPT || opTypeClass.getLanguage() === Language.ARKTS1_1) { 127 this.addIssueReport(stmt, rightOp, opTypeClass.getLanguage(), checkAll.value); 128 } 129 } 130 } 131 } 132 133 private checkFromStmt(stmt: Stmt, scene: Scene, checkAll: { value: boolean }, visited: Set<Stmt>, depth: number = 0): boolean { 134 if (depth > CALL_DEPTH_LIMIT) { 135 checkAll.value = false; 136 return true; 137 } 138 const node = this.dvfg.getOrNewDVFGNode(stmt); 139 let worklist: DVFGNode[] = [node]; 140 while (worklist.length > 0) { 141 const current = worklist.shift()!; 142 const currentStmt = current.getStmt(); 143 if (visited.has(currentStmt)) { 144 continue; 145 } 146 visited.add(currentStmt); 147 if (this.isObjectLiteral(currentStmt, scene)) { 148 return true; 149 } 150 const callsite = this.cg.getCallSiteByStmt(currentStmt); 151 for (const cs of callsite) { 152 const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID); 153 if (!declaringMtd || !declaringMtd.getCfg()) { 154 return false; 155 } 156 if (!this.visited.has(declaringMtd)) { 157 this.dvfgBuilder.buildForSingleMethod(declaringMtd); 158 this.visited.add(declaringMtd); 159 } 160 const returnStmts = declaringMtd.getReturnStmt(); 161 for (const stmt of returnStmts) { 162 const res = this.checkFromStmt(stmt, scene, checkAll, visited, depth + 1); 163 if (res) { 164 return true; 165 } 166 } 167 } 168 const paramRef = this.isFromParameter(currentStmt); 169 if (paramRef) { 170 const paramIdx = paramRef.getIndex(); 171 const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()); 172 this.processCallsites(callsites); 173 const argDefs = this.collectArgDefs(paramIdx, callsites); 174 for (const def of argDefs) { 175 const res = this.checkFromStmt(def, scene, checkAll, visited, depth + 1); 176 if (res) { 177 return true; 178 } 179 } 180 } 181 current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode)); 182 } 183 return false; 184 } 185 186 private processCallsites(callsites: Stmt[]): void { 187 callsites.forEach(cs => { 188 const declaringMtd = cs.getCfg().getDeclaringMethod(); 189 if (!this.visited.has(declaringMtd)) { 190 this.dvfgBuilder.buildForSingleMethod(declaringMtd); 191 this.visited.add(declaringMtd); 192 } 193 }); 194 } 195 196 private isObjectLiteral(stmt: Stmt, scene: Scene): boolean { 197 if (!(stmt instanceof ArkAssignStmt)) { 198 return false; 199 } 200 const rightOp = stmt.getRightOp(); 201 if (!(rightOp instanceof ArkNewExpr)) { 202 return false; 203 } 204 const classSig = rightOp.getClassType().getClassSignature(); 205 return scene.getClass(classSig)?.getCategory() === ClassCategory.OBJECT; 206 } 207 208 private isFromParameter(stmt: Stmt): ArkParameterRef | undefined { 209 if (!(stmt instanceof ArkAssignStmt)) { 210 return undefined; 211 } 212 const rightOp = stmt.getRightOp(); 213 if (rightOp instanceof ArkParameterRef) { 214 return rightOp; 215 } 216 return undefined; 217 } 218 219 private collectArgDefs(argIdx: number, callsites: Stmt[]): Stmt[] { 220 const getKey = (v: Value): Value | FieldSignature => { 221 return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v; 222 }; 223 return callsites.flatMap(callsite => { 224 const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx)); 225 return Array.from(this.dvfg.getOrNewDVFGNode(callsite).getIncomingEdge()) 226 .map(e => (e.getSrcNode() as DVFGNode).getStmt()) 227 .filter(s => { 228 return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp()); 229 }); 230 }); 231 } 232 233 private addIssueReport(stmt: Stmt, operand: Value, targetLanguage: Language, checkAll: boolean = true): void { 234 const interopRuleId = this.getInteropRule(targetLanguage); 235 if (interopRuleId === null) { 236 return; 237 } 238 const severity = this.metaData.severity; 239 const warnInfo = getLineAndColumn(stmt, operand); 240 let targetLan = getLanguageStr(targetLanguage); 241 242 const problem = 'Interop'; 243 let desc = `instanceof including object literal with class type from ${targetLan} (${interopRuleId})`; 244 if (!checkAll) { 245 desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${interopRuleId})`; 246 } 247 let defects = new Defects( 248 warnInfo.line, 249 warnInfo.startCol, 250 warnInfo.endCol, 251 problem, 252 desc, 253 severity, 254 this.rule.ruleId, 255 warnInfo.filePath, 256 this.metaData.ruleDocPath, 257 true, 258 false, 259 false 260 ); 261 this.issues.push(new IssueReport(defects, undefined)); 262 } 263 264 private getInteropRule(targetLanguage: Language): string | null { 265 if (targetLanguage === Language.TYPESCRIPT) { 266 return ts2sRuleId; 267 } 268 if (targetLanguage === Language.ARKTS1_1) { 269 return d2sRuleId; 270 } 271 return null; 272 } 273} 274