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 Stmt, 20 Scene, 21 Value, 22 ArkInstanceFieldRef, 23 ClassType, 24 ArkInvokeStmt, 25 AbstractInvokeExpr, 26 ArkField, 27 ArkReturnStmt, 28} from 'arkanalyzer/lib'; 29import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 30import { BaseChecker, BaseMetaData } from '../BaseChecker'; 31import { Rule, Defects, MatcherCallback, MatcherTypes, MethodMatcher } from '../../Index'; 32import { IssueReport } from '../../model/Defects'; 33import { ArkClass, ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass'; 34import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; 35import { getLanguageStr, getLineAndColumn } from './Utils'; 36import { ArkThisRef } from 'arkanalyzer'; 37 38const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropS2DObjectLiteralCheck'); 39const gMetaData: BaseMetaData = { 40 severity: 1, 41 ruleDocPath: '', 42 description: '', 43}; 44 45const s2dRuleId: string = 'arkts-interop-s2d-object-literal'; 46 47type IssueData = { 48 stmt: Stmt; 49 value: Value; 50}; 51 52export class InteropS2DObjectLiteralCheck implements BaseChecker { 53 private scene: Scene; 54 readonly metaData: BaseMetaData = gMetaData; 55 public rule: Rule; 56 public defects: Defects[] = []; 57 public issues: IssueReport[] = []; 58 59 private methodMatcher: MethodMatcher = { 60 matcherType: MatcherTypes.METHOD, 61 }; 62 63 public registerMatchers(): MatcherCallback[] { 64 const methodMatcher: MatcherCallback = { 65 matcher: this.methodMatcher, 66 callback: this.check, 67 }; 68 return [methodMatcher]; 69 } 70 71 public check = (arkMethod: ArkMethod): void => { 72 this.scene = arkMethod.getDeclaringArkFile().getScene(); 73 74 if (arkMethod.getLanguage() !== Language.ARKTS1_1) { 75 return; 76 } 77 // 检测的sink点为赋值语句,且左值的类型注解明确为1.2的class或者包含1.2的class的更复杂数据结构 78 const stmts = arkMethod.getBody()?.getCfg().getStmts() ?? []; 79 // 检查所有语句 80 // 1. 对于赋值语句,检查左边是否为arkts1.2的class类型或某个field为arkts1.2的class类型,右边对象或对应属性是否用arkts1.1的object litral赋值 81 // 2. 对于函数调用,可能为invoke语句或赋值语句的右值为invoke表达式,检查入参是否存在如上的情况 82 for (const stmt of stmts) { 83 if (stmt instanceof ArkAssignStmt) { 84 this.checkAssignWithObjectLiteral(stmt, arkMethod); 85 if (stmt.getRightOp() instanceof AbstractInvokeExpr) { 86 this.checkInvokeWithObjectLiteral(stmt, arkMethod); 87 } 88 } else if (stmt instanceof ArkInvokeStmt) { 89 this.checkInvokeWithObjectLiteral(stmt, arkMethod); 90 } 91 } 92 // 检查函数的返回值,若函数签名中返回类型声明是否为arkts1.2的class类型或某个field为arkts1.2的class类型,且实际返回对象或对应属性是否为arkts1.1的object litral 93 this.checkReturnWithObjectLiteral(arkMethod); 94 }; 95 96 private getClassWithType(checkType: ClassType): ArkClass | null { 97 return this.scene.getClass(checkType.getClassSignature()); 98 } 99 100 private isClassFromEtsStatic(clazz: ArkClass): boolean { 101 return clazz.getLanguage() === Language.ARKTS1_2 && clazz.getCategory() !== ClassCategory.OBJECT; 102 } 103 104 private isObjectLiteralFromEtsDynamic(clazz: ArkClass): boolean { 105 return clazz.getLanguage() === Language.ARKTS1_1 && clazz.getCategory() === ClassCategory.OBJECT; 106 } 107 108 private checkAssignWithObjectLiteral(stmt: ArkAssignStmt, target: ArkMethod): void { 109 // this = thisRef 赋值语句需要跳过,否则该class一定会被扫描一遍,此次扫描多余,且可能会产生行号为-1的错误issue 110 // 若此class有问题,会在真正使用到此class的源码处进行告警,无需查找this ref语句 111 if (stmt.getRightOp() instanceof ArkThisRef) { 112 return; 113 } 114 const leftOpType = stmt.getLeftOp().getType(); 115 if (!(leftOpType instanceof ClassType)) { 116 return; 117 } 118 const leftTypeClass = this.getClassWithType(leftOpType); 119 if (leftTypeClass === null) { 120 logger.debug(`Failed to find class of type ${leftOpType.toString()}`); 121 return; 122 } 123 if (this.isClassFromEtsStatic(leftTypeClass)) { 124 const rightOpType = stmt.getRightOp().getType(); 125 if (!(rightOpType instanceof ClassType)) { 126 return; 127 } 128 const rightTypeClass = this.getClassWithType(rightOpType); 129 if (rightTypeClass === null) { 130 logger.debug(`Failed to find class of type ${rightOpType.toString()}`); 131 return; 132 } 133 if (this.isObjectLiteralFromEtsDynamic(rightTypeClass)) { 134 this.addIssueReport(stmt, stmt.getRightOp()); 135 return; 136 } 137 } 138 let results: IssueData[] = []; 139 this.checkAllClassFieldWithValue(stmt, stmt.getRightOp(), leftTypeClass, results); 140 for (const result of results) { 141 this.addIssueReport(result.stmt, result.value); 142 } 143 } 144 145 private checkInvokeWithObjectLiteral(stmt: ArkInvokeStmt | ArkAssignStmt, target: ArkMethod): void { 146 let invokeExpr: AbstractInvokeExpr; 147 if (stmt instanceof ArkInvokeStmt) { 148 invokeExpr = stmt.getInvokeExpr(); 149 } else { 150 const rightOp = stmt.getRightOp(); 151 if (!(rightOp instanceof AbstractInvokeExpr)) { 152 return; 153 } 154 invokeExpr = rightOp; 155 } 156 const method = this.scene.getMethod(invokeExpr.getMethodSignature()); 157 if (method === null) { 158 logger.debug(`Failed to find method in invoke expr, method: ${invokeExpr.getMethodSignature().toString()}`); 159 return; 160 } 161 for (const [index, param] of method.getParameters().entries()) { 162 const paramType = param.getType(); 163 if (!(paramType instanceof ClassType)) { 164 continue; 165 } 166 const paramTypeClass = this.getClassWithType(paramType); 167 if (paramTypeClass === null) { 168 logger.debug(`Failed to find class of method param type ${paramType.toString()}, method: ${method.getSignature().toString()}`); 169 continue; 170 } 171 if (index >= invokeExpr.getArgs().length) { 172 logger.debug(`Failed to find param with index ${index} of method: ${method.getSignature().toString()}`); 173 continue; 174 } 175 const arg = invokeExpr.getArg(index); 176 if (this.isClassFromEtsStatic(paramTypeClass)) { 177 const argType = arg.getType(); 178 if (!(argType instanceof ClassType)) { 179 continue; 180 } 181 const argTypeClass = this.getClassWithType(argType); 182 if (argTypeClass === null) { 183 logger.debug(`Failed to find class of invoke arg type ${argType.toString()}, method: ${method.getSignature().toString()}`); 184 continue; 185 } 186 if (this.isObjectLiteralFromEtsDynamic(argTypeClass)) { 187 this.addIssueReport(stmt, arg); 188 return; 189 } 190 } 191 let results: IssueData[] = []; 192 this.checkAllClassFieldWithValue(stmt, arg, paramTypeClass, results); 193 for (const result of results) { 194 this.addIssueReport(result.stmt, result.value); 195 } 196 } 197 } 198 199 private checkReturnWithObjectLiteral(target: ArkMethod): void { 200 // 构造函数的返回值一定是当前class本身,其各field和method已在其他地方进行检查,这里无需检查构造函数的返回值 201 if (target.getName() === 'constructor') { 202 return; 203 } 204 const returnType = target.getReturnType(); 205 if (!(returnType instanceof ClassType)) { 206 return; 207 } 208 const returnTypeClass = this.getClassWithType(returnType); 209 if (returnTypeClass === null) { 210 logger.debug(`Failed to find method of return type ${returnType.toString()}, method ${target.getSignature().toString()}`); 211 return; 212 } 213 const returnStmts = target.getReturnStmt(); 214 if (this.isClassFromEtsStatic(returnTypeClass)) { 215 for (const returnStmt of returnStmts) { 216 if (!(returnStmt instanceof ArkReturnStmt)) { 217 continue; 218 } 219 const valueType = returnStmt.getOp().getType(); 220 if (!(valueType instanceof ClassType)) { 221 continue; 222 } 223 const valueTypeClass = this.getClassWithType(valueType); 224 if (valueTypeClass === null) { 225 logger.debug(`Failed to find method of return value type ${valueType.toString()}, method ${target.getSignature().toString()}`); 226 continue; 227 } 228 if (this.isObjectLiteralFromEtsDynamic(valueTypeClass)) { 229 this.addIssueReport(returnStmt, returnStmt.getOp()); 230 } 231 } 232 return; 233 } 234 235 for (const returnStmt of returnStmts) { 236 if (!(returnStmt instanceof ArkReturnStmt)) { 237 continue; 238 } 239 let results: IssueData[] = []; 240 this.checkAllClassFieldWithValue(returnStmt, returnStmt.getOp(), returnTypeClass, results); 241 if (results.length > 0) { 242 this.addIssueReport(returnStmt, returnStmt.getOp()); 243 } 244 } 245 } 246 247 private checkAllClassFieldWithValue(sinkStmt: Stmt, val: Value, needCheckClass: ArkClass, result: IssueData[], checkedTypes?: Set<string>): void { 248 let visited: Set<string> = checkedTypes ?? new Set<string>(); 249 if (visited.has(needCheckClass.getSignature().toString())) { 250 return; 251 } 252 visited.add(needCheckClass.getSignature().toString()); 253 for (const field of needCheckClass.getFields()) { 254 const fieldType = field.getType(); 255 if (!(fieldType instanceof ClassType)) { 256 continue; 257 } 258 const fieldTypeClass = this.getClassWithType(fieldType); 259 if (fieldTypeClass === null) { 260 logger.debug( 261 `Failed to find class of type ${fieldType.toString()} of field: ${field.getName()}, class ${needCheckClass.getSignature().toString()}}` 262 ); 263 continue; 264 } 265 const fieldInitializers = this.getFieldInitializersWithValue(field, val); 266 const fieldAssignStmt = this.getFieldAssignStmtInInitializers(field, fieldInitializers); 267 if (fieldAssignStmt === null) { 268 continue; 269 } 270 if (this.isClassFromEtsStatic(fieldTypeClass)) { 271 const rightOpType = fieldAssignStmt.getRightOp().getType(); 272 if (!(rightOpType instanceof ClassType)) { 273 continue; 274 } 275 const rightOpTypeClass = this.getClassWithType(rightOpType); 276 if (rightOpTypeClass === null) { 277 logger.debug( 278 `Failed to find class of type ${rightOpType.toString()} of field initializer, field: ${field.getName()}, class ${needCheckClass.getSignature().toString()}}` 279 ); 280 continue; 281 } 282 if (this.isObjectLiteralFromEtsDynamic(rightOpTypeClass)) { 283 result.push({ stmt: sinkStmt, value: val }); 284 continue; 285 } 286 continue; 287 } 288 this.checkAllClassFieldWithValue(fieldAssignStmt, fieldAssignStmt.getRightOp(), fieldTypeClass, result, visited); 289 } 290 } 291 292 private getFieldAssignStmtInInitializers(field: ArkField, fieldInitializers: Stmt[]): ArkAssignStmt | null { 293 for (const stmt of fieldInitializers) { 294 if (!(stmt instanceof ArkAssignStmt)) { 295 continue; 296 } 297 const leftOp = stmt.getLeftOp(); 298 if (!(leftOp instanceof ArkInstanceFieldRef)) { 299 continue; 300 } 301 if (leftOp.getFieldName() === field.getName()) { 302 return stmt; 303 } 304 } 305 return null; 306 } 307 308 // 对于object literal(主要是多层嵌套场景),根据需要查找的field的名字,获取其对应的内部嵌套class的初始化语句 309 private getFieldInitializersWithValue(leftField: ArkField, val: Value): Stmt[] { 310 const res: Stmt[] = []; 311 const rightOpType = val.getType(); 312 if (!(rightOpType instanceof ClassType)) { 313 return res; 314 } 315 const rightOpTypeClass = this.getClassWithType(rightOpType); 316 if (rightOpTypeClass === null) { 317 logger.debug(`Failed to find class of type ${rightOpType.toString()} of field: ${leftField.getSignature().toString()}`); 318 return res; 319 } 320 for (const field of rightOpTypeClass.getFields()) { 321 if (field.getName() === leftField.getName()) { 322 return field.getInitializer(); 323 } 324 } 325 return res; 326 } 327 328 private addIssueReport(stmt: Stmt, operand: Value): void { 329 const severity = this.metaData.severity; 330 let warnInfo = getLineAndColumn(stmt, operand); 331 let targetLan1 = getLanguageStr(Language.ARKTS1_1); 332 let targetLan2 = getLanguageStr(Language.ARKTS1_2); 333 334 const problem = 'Interop'; 335 const desc = `In ${targetLan1}, it is not allowed to create object literal of type from ${targetLan2} (${s2dRuleId})`; 336 337 let defects = new Defects( 338 warnInfo.line, 339 warnInfo.startCol, 340 warnInfo.endCol, 341 problem, 342 desc, 343 severity, 344 this.rule.ruleId, 345 warnInfo.filePath, 346 this.metaData.ruleDocPath, 347 true, 348 false, 349 false 350 ); 351 this.issues.push(new IssueReport(defects, undefined)); 352 } 353} 354