• 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    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