• 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    FieldSignature,
20    Stmt,
21    Scene,
22    Value,
23    ArkClass,
24    ArkFile,
25    ArkInstanceOfExpr,
26    ArkNewExpr,
27    CallGraph,
28    ArkParameterRef,
29    ArkInstanceFieldRef,
30    AbstractFieldRef,
31    Local,
32    ArkArrayRef,
33    ClassSignature,
34} from 'arkanalyzer/lib';
35import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
36import { BaseChecker, BaseMetaData } from '../BaseChecker';
37import { Rule, Defects, MatcherCallback } from '../../Index';
38import { IssueReport } from '../../model/Defects';
39import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG';
40import { DVFGHelper, CALL_DEPTH_LIMIT, GlobalCallGraphHelper } from './Utils';
41import { WarnInfo } from '../../utils/common/Utils';
42
43const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ObjectLiteralCheck');
44const gMetaData: BaseMetaData = {
45    severity: 1,
46    ruleDocPath: '',
47    description: 'Object literal shall generate instance of a specific class',
48};
49
50export class ObjectLiteralCheck implements BaseChecker {
51    readonly metaData: BaseMetaData = gMetaData;
52    public rule: Rule;
53    public defects: Defects[] = [];
54    public issues: IssueReport[] = [];
55    private cg: CallGraph;
56    private visited: Set<ArkMethod> = new Set();
57
58    public registerMatchers(): MatcherCallback[] {
59        const matchBuildCb: MatcherCallback = {
60            matcher: undefined,
61            callback: this.check,
62        };
63        return [matchBuildCb];
64    }
65
66    public check = (scene: Scene): void => {
67        this.cg = GlobalCallGraphHelper.getCGInstance(scene);
68
69        for (let arkFile of scene.getFiles()) {
70            const topLevelVarMap: Map<string, Stmt[]> = new Map();
71            this.collectImportedVar(topLevelVarMap, arkFile, scene);
72            this.collectTopLevelVar(topLevelVarMap, arkFile, scene);
73
74            const handleClass = (cls: ArkClass): void => {
75                cls.getMethods().forEach(m => this.processArkMethod(m, topLevelVarMap, scene));
76            };
77
78            arkFile.getClasses().forEach(cls => handleClass(cls));
79            arkFile.getAllNamespacesUnderThisFile().forEach(n => n.getClasses().forEach(cls => handleClass(cls)));
80        }
81    };
82
83    public processArkMethod(target: ArkMethod, topLevelVarMap: Map<string, Stmt[]>, scene: Scene): void {
84        const stmts = target.getBody()?.getCfg().getStmts() ?? [];
85        for (const stmt of stmts) {
86            if (!(stmt instanceof ArkAssignStmt)) {
87                continue;
88            }
89            const rightOp = stmt.getRightOp();
90            if (!(rightOp instanceof ArkInstanceOfExpr)) {
91                continue;
92            }
93            if (!this.visited.has(target)) {
94                DVFGHelper.buildSingleDVFG(target, scene);
95                this.visited.add(target);
96            }
97
98            let result: Stmt[] = [];
99            let checkAll = { value: true };
100            let visited: Set<Stmt> = new Set();
101            this.checkFromStmt(stmt, scene, result, topLevelVarMap, checkAll, visited);
102            result.forEach(s => this.addIssueReport(s, (s as ArkAssignStmt).getRightOp()));
103            if (!checkAll.value) {
104                this.addIssueReport(stmt, rightOp, checkAll.value);
105            }
106        }
107    }
108
109    private collectImportedVar(importVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) {
110        file.getImportInfos().forEach(importInfo => {
111            const exportInfo = importInfo.getLazyExportInfo();
112            if (exportInfo === null) {
113                return;
114            }
115            const arkExport = exportInfo.getArkExport();
116            if (!arkExport || !(arkExport instanceof Local)) {
117                return;
118            }
119            const declaringStmt = arkExport.getDeclaringStmt();
120            if (!declaringStmt) {
121                return;
122            }
123            DVFGHelper.buildSingleDVFG(declaringStmt.getCfg().getDeclaringMethod(), scene);
124            importVarMap.set(arkExport.getName(), [declaringStmt]);
125        });
126    }
127
128    private collectTopLevelVar(topLevelVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) {
129        const defaultMethod = file.getDefaultClass().getDefaultArkMethod();
130        if (!defaultMethod) {
131            return;
132        }
133        DVFGHelper.buildSingleDVFG(defaultMethod, scene);
134        const stmts = defaultMethod.getBody()?.getCfg().getStmts() ?? [];
135        for (const stmt of stmts) {
136            if (!(stmt instanceof ArkAssignStmt)) {
137                continue;
138            }
139            const leftOp = stmt.getLeftOp();
140            if (!(leftOp instanceof Local)) {
141                continue;
142            }
143            const name = leftOp.getName();
144            if (name.startsWith('%') || name === 'this') {
145                continue;
146            }
147            topLevelVarMap.set(name, [...(topLevelVarMap.get(name) ?? []), stmt]);
148        }
149    }
150
151    private checkFromStmt(
152        stmt: Stmt,
153        scene: Scene,
154        res: Stmt[],
155        topLevelVarMap: Map<string, Stmt[]>,
156        checkAll: { value: boolean },
157        visited: Set<Stmt>,
158        depth: number = 0
159    ): void {
160        if (depth > CALL_DEPTH_LIMIT) {
161            checkAll.value = false;
162            return;
163        }
164        const node = DVFGHelper.getOrNewDVFGNode(stmt, scene);
165        let worklist: DVFGNode[] = [node];
166        while (worklist.length > 0) {
167            const current = worklist.shift()!;
168            const currentStmt = current.getStmt();
169            if (visited.has(currentStmt)) {
170                continue;
171            }
172            visited.add(currentStmt);
173            if (this.isObjectLiteral(currentStmt, scene)) {
174                res.push(currentStmt);
175                continue;
176            }
177            const isClsField = this.isClassField(currentStmt, scene);
178            if (isClsField) {
179                isClsField.forEach(d => worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene)));
180                continue;
181            }
182            const isArrayField = this.isArrayField(currentStmt, topLevelVarMap);
183            if (isArrayField) {
184                isArrayField.forEach(d => worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene)));
185                continue;
186            }
187            const gv = this.checkIfIsTopLevelVar(currentStmt);
188            if (gv) {
189                const globalDefs = topLevelVarMap.get(gv.getName());
190                globalDefs?.forEach(d => {
191                    worklist.push(DVFGHelper.getOrNewDVFGNode(d, scene));
192                });
193                continue;
194            }
195            const callsite = this.cg.getCallSiteByStmt(currentStmt);
196            callsite.forEach(cs => {
197                const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID);
198                if (!declaringMtd || !declaringMtd.getCfg()) {
199                    return;
200                }
201                if (!this.visited.has(declaringMtd)) {
202                    DVFGHelper.buildSingleDVFG(declaringMtd, scene);
203                    this.visited.add(declaringMtd);
204                }
205                declaringMtd.getReturnStmt().forEach(r => this.checkFromStmt(r, scene, res, topLevelVarMap, checkAll, visited, depth + 1));
206            });
207            const paramRef = this.isFromParameter(currentStmt);
208            if (paramRef) {
209                const paramIdx = paramRef.getIndex();
210                const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature());
211                this.processCallsites(callsites, scene);
212                this.collectArgDefs(paramIdx, callsites, scene).forEach(d => this.checkFromStmt(d, scene, res, topLevelVarMap, checkAll, visited, depth + 1));
213            }
214            current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode));
215        }
216    }
217
218    private checkIfIsTopLevelVar(stmt: Stmt): Local | undefined {
219        if (!(stmt instanceof ArkAssignStmt)) {
220            return undefined;
221        }
222        const rightOp = stmt.getRightOp();
223        if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) {
224            return rightOp;
225        }
226        if (!(rightOp instanceof ArkInstanceOfExpr)) {
227            return undefined;
228        }
229        const obj = rightOp.getOp();
230        if (obj instanceof Local && !obj.getDeclaringStmt()) {
231            return obj;
232        }
233        return undefined;
234    }
235
236    private processCallsites(callsites: Stmt[], scene: Scene): void {
237        callsites.forEach(cs => {
238            const declaringMtd = cs.getCfg().getDeclaringMethod();
239            if (!this.visited.has(declaringMtd)) {
240                DVFGHelper.buildSingleDVFG(declaringMtd, scene);
241                this.visited.add(declaringMtd);
242            }
243        });
244    }
245
246    private isObjectLiteral(stmt: Stmt, scene: Scene): boolean {
247        if (!(stmt instanceof ArkAssignStmt)) {
248            return false;
249        }
250        const rightOp = stmt.getRightOp();
251        if (!(rightOp instanceof ArkNewExpr)) {
252            return false;
253        }
254        const classSig = rightOp.getClassType().getClassSignature();
255        if (scene.getClass(classSig)?.isAnonymousClass()) {
256            return true;
257        }
258        return false;
259    }
260
261    private isClassField(stmt: Stmt, scene: Scene): Stmt[] | undefined {
262        if (!(stmt instanceof ArkAssignStmt)) {
263            return undefined;
264        }
265        const clsField = stmt.getRightOp();
266        if (!(clsField instanceof AbstractFieldRef)) {
267            return undefined;
268        }
269        if (clsField instanceof ArkInstanceFieldRef && clsField.getBase().getName() !== 'this') {
270            return undefined;
271        }
272        const fieldSig = clsField.getFieldSignature();
273        const clsSig = fieldSig.getDeclaringSignature();
274        if (!(clsSig instanceof ClassSignature)) {
275            return undefined;
276        }
277        const cls = scene.getClass(clsSig);
278        if (!cls) {
279            logger.error(`cannot find class based on class sig: ${clsSig.toString()}`);
280            return undefined;
281        }
282        const field = cls.getField(fieldSig);
283        if (!field) {
284            logger.error(`cannot find field based on field sig: ${fieldSig.toString()}`);
285            return undefined;
286        }
287        if (!field.isStatic()) {
288            DVFGHelper.buildSingleDVFG(cls.getInstanceInitMethod(), scene);
289        } else {
290            DVFGHelper.buildSingleDVFG(cls.getStaticInitMethod(), scene);
291        }
292        return field.getInitializer().slice(-1);
293    }
294
295    private isArrayField(stmt: Stmt, topLevelVarMap: Map<string, Stmt[]>): Stmt[] | undefined {
296        if (!(stmt instanceof ArkAssignStmt)) {
297            return undefined;
298        }
299        const arrField = stmt.getRightOp();
300        if (!(arrField instanceof ArkArrayRef)) {
301            return undefined;
302        }
303        const arr = arrField.getBase();
304        const index = arrField.getIndex();
305        let arrDeclarations: Stmt[] = [];
306        if (arr.getDeclaringStmt()) {
307            arrDeclarations.push(arr.getDeclaringStmt()!);
308        } else if (topLevelVarMap.has(arr.getName())) {
309            arrDeclarations = topLevelVarMap.get(arr.getName())!;
310        }
311        const res: Stmt[] = arrDeclarations.flatMap(d => {
312            // arr = %0
313            // %0[0] = ...
314            if (!(d instanceof ArkAssignStmt)) {
315                return [];
316            }
317            const arrVal = d.getRightOp();
318            if (!(arrVal instanceof Local)) {
319                return [];
320            }
321            return arrVal.getUsedStmts().filter(u => {
322                if (!(u instanceof ArkAssignStmt)) {
323                    return false;
324                }
325                const left = u.getLeftOp();
326                return left instanceof ArkArrayRef && left.getBase() === arrVal && left.getIndex() === index;
327            });
328        });
329        return res;
330    }
331
332    private isFromParameter(stmt: Stmt): ArkParameterRef | undefined {
333        if (!(stmt instanceof ArkAssignStmt)) {
334            return undefined;
335        }
336        const rightOp = stmt.getRightOp();
337        if (rightOp instanceof ArkParameterRef) {
338            return rightOp;
339        }
340        return undefined;
341    }
342
343    private collectArgDefs(argIdx: number, callsites: Stmt[], scene: Scene): Stmt[] {
344        const getKey = (v: Value): Value | FieldSignature => {
345            return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v;
346        };
347        return callsites.flatMap(callsite => {
348            const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx));
349            return Array.from(DVFGHelper.getOrNewDVFGNode(callsite, scene).getIncomingEdge())
350                .map(e => (e.getSrcNode() as DVFGNode).getStmt())
351                .filter(s => {
352                    return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp());
353                });
354        });
355    }
356
357    private addIssueReport(stmt: Stmt, operand: Value, checkAll: boolean = true): void {
358        const severity = this.rule.alert ?? this.metaData.severity;
359        const warnInfo = this.getLineAndColumn(stmt, operand);
360        const problem = 'ObjectLiteral';
361        let desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`;
362        if (!checkAll) {
363            desc = `Can not check when function call chain depth exceeds ${CALL_DEPTH_LIMIT}, please check it manually (${this.rule.ruleId.replace('@migration/', '')})`;
364        }
365        let defects = new Defects(
366            warnInfo.line,
367            warnInfo.startCol,
368            warnInfo.endCol,
369            problem,
370            desc,
371            severity,
372            this.rule.ruleId,
373            warnInfo.filePath,
374            this.metaData.ruleDocPath,
375            true,
376            false,
377            false
378        );
379        this.issues.push(new IssueReport(defects, undefined));
380    }
381
382    private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo {
383        const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile();
384        const originPosition = stmt.getOperandOriginalPosition(operand);
385        if (arkFile && originPosition) {
386            const originPath = arkFile.getFilePath();
387            const line = originPosition.getFirstLine();
388            const startCol = originPosition.getFirstCol();
389            const endCol = startCol;
390            return { line, startCol, endCol, filePath: originPath };
391        } else {
392            logger.debug('ArkFile is null.');
393        }
394        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
395    }
396}
397