• 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    Type,
18    ArkMethod,
19    ArkAssignStmt,
20    Scene,
21    ArkInstanceFieldRef,
22    FunctionType,
23    ClassType,
24    MethodSignature,
25    ArkParameterRef,
26    Value,
27    Stmt,
28    ArkDeleteExpr,
29    FieldSignature,
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 { ArkFile, Language } from 'arkanalyzer/lib/core/model/ArkFile';
37import { CALL_DEPTH_LIMIT, DVFGHelper } from './Utils';
38import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG';
39
40const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropJSModifyPropertyCheck');
41const gMetaData: BaseMetaData = {
42    severity: 1,
43    ruleDocPath: '',
44    description: 'The layout of staic objects should not be modified',
45};
46
47const RULE_ID = 'arkts-interop-js2s-js-add-detele-static-prop';
48
49class ParamInfo {
50    flag: boolean;
51    paramAssign: ArkAssignStmt;
52    callsites: Set<Stmt>;
53}
54
55export class InteropJSModifyPropertyCheck implements BaseChecker {
56    readonly metaData: BaseMetaData = gMetaData;
57    public rule: Rule;
58    public defects: Defects[] = [];
59    public issues: IssueReport[] = [];
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        const targetMethods: Map<MethodSignature, ParamInfo[]> = new Map();
71        this.collectTargetMethods(targetMethods, scene);
72        this.checkTargetMethods(targetMethods, scene);
73    };
74
75    private collectTargetMethods(targetMethods: Map<MethodSignature, ParamInfo[]>, scene: Scene): void {
76        scene.getFiles().forEach(file => {
77            // 只处理 ArkTS1.2 import JS 函数的情况
78            if (file.getLanguage() !== Language.ARKTS1_2) {
79                return;
80            }
81            file.getImportInfos().forEach(importInfo => {
82                const exportInfo = importInfo.getLazyExportInfo();
83                if (exportInfo === null) {
84                    return;
85                }
86                const arkExport = exportInfo.getArkExport();
87                if (arkExport === null || arkExport === undefined) {
88                    return;
89                }
90                if (!(arkExport instanceof ArkMethod && arkExport.getLanguage() === Language.JAVASCRIPT)) {
91                    return;
92                }
93                // 创建初始化的参数标志位信息(标志位代表该参数是否被传入了 1.2 对象,默认为 false)
94                const paramInfo = this.createParamInfo(arkExport);
95                if (paramInfo.length > 0) {
96                    targetMethods.set(arkExport.getSignature(), paramInfo);
97                }
98            });
99
100            // 寻找 JS 函数的被调用点,检查是否传入 ArkTS1.2 类型的对象并维护参数标志位信息
101            for (let clazz of file.getClasses()) {
102                for (let mtd of clazz.getMethods()) {
103                    this.findCallsite(mtd, targetMethods, scene);
104                }
105            }
106            for (let namespace of file.getAllNamespacesUnderThisFile()) {
107                this.processNameSpace(namespace, targetMethods, scene);
108            }
109        });
110
111        // 跨函数检查 ArkTS1.2 类型对象是否被跨函数传到其他 JS 函数
112        for (let i = 0; i < CALL_DEPTH_LIMIT; ++i) {
113            this.collectInterprocedualCallSites(targetMethods, scene);
114        }
115    }
116
117    public processNameSpace(
118        namespace: ArkNamespace,
119        targetMethods: Map<MethodSignature, ParamInfo[]>,
120        scene: Scene
121    ): void {
122        for (let clazz of namespace.getClasses()) {
123            for (let mtd of clazz.getMethods()) {
124                this.findCallsite(mtd, targetMethods, scene);
125            }
126        }
127    }
128
129    private createParamInfo(method: ArkMethod): ParamInfo[] {
130        // 初始化参数标志数组
131        const idxFlag = new Array(method.getParameters().length).fill(false);
132        // 寻找参数对应的 xxx = parameter 语句
133        const paramAssigns = (method.getCfg()?.getStmts() ?? [])
134            .filter(s => s instanceof ArkAssignStmt && s.getRightOp() instanceof ArkParameterRef)
135            .map(s => s as ArkAssignStmt);
136        if (idxFlag.length !== paramAssigns.length) {
137            logger.error('param index num != param assign num');
138            return [];
139        }
140        const result: ParamInfo[] = [];
141        for (let i = 0; i < idxFlag.length; i++) {
142            result.push({ flag: idxFlag[i], paramAssign: paramAssigns[i], callsites: new Set() });
143        }
144        return result;
145    }
146
147    private findCallsite(method: ArkMethod, targets: Map<MethodSignature, ParamInfo[]>, scene: Scene): void {
148        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
149        for (const stmt of stmts) {
150            const invoke = stmt.getInvokeExpr();
151            if (!invoke) {
152                continue;
153            }
154            const methodSig = invoke.getMethodSignature();
155            if (!targets.has(methodSig)) {
156                continue;
157            }
158            invoke.getArgs().forEach((arg, idx): void => {
159                if (this.getTypeDefinedLang(arg.getType(), method.getDeclaringArkFile(), scene) !== Language.ARKTS1_2) {
160                    return;
161                }
162                targets.get(methodSig)![idx].flag = true;
163                targets.get(methodSig)![idx].callsites.add(stmt);
164            });
165        }
166    }
167
168    private collectInterprocedualCallSites(targets: Map<MethodSignature, ParamInfo[]>, scene: Scene): void {
169        new Map(targets).forEach((paramInfo, sig) => {
170            const method = scene.getMethod(sig)!;
171            DVFGHelper.buildSingleDVFG(method, scene);
172            paramInfo.forEach((paramInfo): void => {
173                if (paramInfo.flag) {
174                    this.checkIfParamPassedToOtherMethod(paramInfo, targets, scene);
175                }
176            });
177        });
178    }
179
180    private checkIfParamPassedToOtherMethod(
181        callerParamInfo: ParamInfo,
182        targets: Map<MethodSignature, ParamInfo[]>,
183        scene: Scene
184    ): void {
185        const worklist: DVFGNode[] = [DVFGHelper.getOrNewDVFGNode(callerParamInfo.paramAssign, scene)];
186        const visited: Set<Stmt> = new Set();
187        while (worklist.length > 0) {
188            const current = worklist.shift()!;
189            const cunrrentStmt = current.getStmt();
190            if (visited.has(cunrrentStmt)) {
191                continue;
192            }
193            visited.add(cunrrentStmt);
194            if (!(cunrrentStmt instanceof ArkAssignStmt)) {
195                continue;
196            }
197            current.getOutgoingEdges().forEach(edge => {
198                const dst = edge.getDstNode() as DVFGNode;
199                const dstStmt = dst.getStmt();
200                // 假设有 JS 函数声明: function foo(obj),其中 obj 为受关注的参数
201                // 只有类似 let obj2 = obj 或者 goo(obj) 这样的语句受到关注
202                if (dstStmt instanceof ArkAssignStmt && dstStmt.getRightOp() === cunrrentStmt.getLeftOp()) {
203                    worklist.push(dst);
204                    return;
205                }
206                const invoke = dstStmt.getInvokeExpr();
207                if (!invoke) {
208                    return;
209                }
210                const calleeSig = invoke.getMethodSignature();
211                const callee = scene.getMethod(calleeSig);
212                if (!callee || !callee.getCfg() || callee.getLanguage() !== Language.JAVASCRIPT) {
213                    return;
214                }
215                if (!targets.has(calleeSig)) {
216                    targets.set(calleeSig, this.createParamInfo(callee));
217                }
218                const getKey = (v: Value): Value | FieldSignature => {
219                    return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v;
220                };
221                invoke.getArgs().forEach((arg, idx) => {
222                    if (getKey(arg) === getKey(cunrrentStmt.getLeftOp())) {
223                        targets.get(calleeSig)![idx].flag = true;
224                        callerParamInfo.callsites.forEach(cs => {
225                            targets.get(calleeSig)![idx].callsites.add(cs);
226                        });
227                    }
228                });
229            });
230        }
231    }
232
233    private checkTargetMethods(targetMethods: Map<MethodSignature, ParamInfo[]>, scene: Scene): void {
234        targetMethods.forEach((paramInfos, methodSig): void => {
235            const method = scene.getMethod(methodSig);
236            if (!method) {
237                logger.error(`cannot find ark method by method sig: ${methodSig.toString()}`);
238                return;
239            }
240            const targetParams = paramInfos
241                .filter(paramInfo => paramInfo.flag)
242                .map(paramInfo => paramInfo.paramAssign.getLeftOp());
243            const stmts = method.getBody()?.getCfg().getStmts() ?? [];
244            for (const stmt of stmts) {
245                if (!(stmt instanceof ArkAssignStmt)) {
246                    continue;
247                }
248                let paramIdx = -1;
249                const rightOp = stmt.getRightOp();
250                if (rightOp instanceof ArkDeleteExpr) {
251                    const fieldRef = rightOp.getField();
252                    if (fieldRef instanceof ArkInstanceFieldRef) {
253                        paramIdx = targetParams.findIndex(p => p === fieldRef.getBase());
254                    }
255                }
256                const leftOp = stmt.getLeftOp();
257                if (leftOp instanceof ArkInstanceFieldRef) {
258                    paramIdx = targetParams.findIndex(p => p === leftOp.getBase());
259                }
260                if (paramIdx !== -1) {
261                    const callers = paramInfos[paramIdx]!;
262                    callers.callsites.forEach(cs => this.reportIssue(cs));
263                }
264            }
265        });
266    }
267
268    private getTypeDefinedLang(type: Type, defaultFile: ArkFile, scene: Scene): Language {
269        let file;
270        if (type instanceof ClassType) {
271            file = scene.getFile(type.getClassSignature().getDeclaringFileSignature());
272        } else if (type instanceof FunctionType) {
273            file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature());
274        } else {
275            file = defaultFile;
276        }
277        if (file) {
278            return file.getLanguage();
279        }
280        return Language.UNKNOWN;
281    }
282
283    private reportIssue(problemStmt: Stmt) {
284        const line = problemStmt.getOriginPositionInfo().getLineNo();
285        const column = problemStmt.getOriginPositionInfo().getColNo();
286        const problem = 'Interop';
287        const desc = `${this.metaData.description} (${RULE_ID})`;
288        const severity = this.metaData.severity;
289        const ruleId = this.rule.ruleId;
290        const filePath = problemStmt.getCfg().getDeclaringMethod().getDeclaringArkFile()?.getFilePath() ?? '';
291        const defeats = new Defects(
292            line,
293            column,
294            column,
295            problem,
296            desc,
297            severity,
298            ruleId,
299            filePath,
300            '',
301            true,
302            false,
303            false
304        );
305        this.issues.push(new IssueReport(defeats, undefined));
306    }
307}
308