• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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