• 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    FieldSignature,
21    Stmt,
22    Scene,
23    Value,
24    CallGraph,
25    ArkParameterRef,
26    ArkInstanceFieldRef,
27    ArkInstanceInvokeExpr,
28    AnyType,
29    ClassType,
30    ArkStaticInvokeExpr,
31    AbstractInvokeExpr,
32    FunctionType,
33    UnknownType,
34    Local,
35    ArkClass,
36} from 'arkanalyzer/lib';
37import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
38import { BaseChecker, BaseMetaData } from '../BaseChecker';
39import { Rule, Defects, MatcherCallback } from '../../Index';
40import { IssueReport } from '../../model/Defects';
41import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG';
42import { CALL_DEPTH_LIMIT, GlobalCallGraphHelper, DVFGHelper } from './Utils';
43import { findInteropRule } from './InteropRuleInfo';
44import { ArkFile, Language } from 'arkanalyzer/lib/core/model/ArkFile';
45
46const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropBackwardDFACheck');
47const gMetaData: BaseMetaData = {
48    severity: 1,
49    ruleDocPath: '',
50    description: '',
51};
52
53const REFLECT_API: Map<string, number> = new Map([
54    ['apply', 0],
55    ['construct', 0],
56    ['defineProperty', 0],
57    ['deleteProperty', 0],
58    ['get', 0],
59    ['getOwnPropertyDescriptor', 0],
60    ['getPrototypeOf', 0],
61    ['has', 0],
62    ['isExtensible', 0],
63    ['ownKeys', 0],
64    ['preventExtensions', 0],
65    ['set', 0],
66    ['setPrototypeOf', 0],
67]);
68
69const OBJECT_API: Map<string, number> = new Map([
70    ['getOwnPropertyDescriptor', 0],
71    ['getOwnPropertyDescriptors', 0],
72    ['getOwnPropertyNames', 0],
73    ['hasOwn', 0],
74    ['isExtensible', 0],
75    ['isFrozen', 0],
76    ['isSealed', 0],
77    ['keys', 0],
78    ['setPrototypeOf', 0],
79    ['values', 0],
80    ['assign', 1],
81    ['entries', 0],
82]);
83
84class ObjDefInfo {
85    problemStmt: Stmt;
86    objLanguage: Language;
87}
88
89export class InteropBackwardDFACheck implements BaseChecker {
90    readonly metaData: BaseMetaData = gMetaData;
91    public rule: Rule;
92    public defects: Defects[] = [];
93    public issues: IssueReport[] = [];
94    private cg: CallGraph;
95
96    public registerMatchers(): MatcherCallback[] {
97        const matchBuildCb: MatcherCallback = {
98            matcher: undefined,
99            callback: this.check,
100        };
101        return [matchBuildCb];
102    }
103
104    public check = (scene: Scene): void => {
105        this.cg = GlobalCallGraphHelper.getCGInstance(scene);
106
107        for (let arkFile of scene.getFiles()) {
108            const importVarMap: Map<string, Language> = new Map();
109            this.collectImportedVar(importVarMap, arkFile, scene);
110            const topLevelVarMap: Map<string, Stmt[]> = new Map();
111            this.collectTopLevelVar(topLevelVarMap, arkFile, scene);
112
113            const handleClass = (cls: ArkClass): void => {
114                cls.getMethods().forEach(m => this.processArkMethod(m, scene, importVarMap, topLevelVarMap));
115            };
116
117            arkFile.getClasses().forEach(cls => handleClass(cls));
118            arkFile.getAllNamespacesUnderThisFile().forEach(n => n.getClasses().forEach(cls => handleClass(cls)));
119        }
120    };
121
122    private collectImportedVar(importVarMap: Map<string, Language>, file: ArkFile, scene: Scene) {
123        file.getImportInfos().forEach(importInfo => {
124            const exportInfo = importInfo.getLazyExportInfo();
125            if (exportInfo === null) {
126                return;
127            }
128            const arkExport = exportInfo.getArkExport();
129            if (!arkExport || !(arkExport instanceof Local)) {
130                return;
131            }
132            const declaringStmt = arkExport.getDeclaringStmt();
133            if (!declaringStmt) {
134                return;
135            }
136            const definedLang = this.getTypeDefinedLang(arkExport.getType(), scene) ?? file.getLanguage();
137            importVarMap.set(arkExport.getName(), definedLang);
138        });
139    }
140
141    private collectTopLevelVar(topLevelVarMap: Map<string, Stmt[]>, file: ArkFile, scene: Scene) {
142        const defaultMethod = file.getDefaultClass().getDefaultArkMethod();
143        if (defaultMethod) {
144            DVFGHelper.buildSingleDVFG(defaultMethod, scene);
145            const stmts = defaultMethod.getBody()?.getCfg().getStmts() ?? [];
146            for (const stmt of stmts) {
147                if (!(stmt instanceof ArkAssignStmt)) {
148                    continue;
149                }
150                const leftOp = stmt.getLeftOp();
151                if (!(leftOp instanceof Local)) {
152                    continue;
153                }
154                const name = leftOp.getName();
155                if (name.startsWith('%') || name === 'this') {
156                    continue;
157                }
158                topLevelVarMap.set(name, [...(topLevelVarMap.get(name) ?? []), stmt]);
159            }
160        }
161    }
162
163    private processArkMethod(
164        target: ArkMethod,
165        scene: Scene,
166        importVarMap: Map<string, Language>,
167        topLevelVarMap: Map<string, Stmt[]>
168    ): void {
169        const currentLang = target.getLanguage();
170        if (currentLang === Language.UNKNOWN) {
171            logger.warn(`cannot find the language for method: ${target.getSignature()}`);
172            return;
173        }
174        const stmts = target.getBody()?.getCfg().getStmts() ?? [];
175        for (const stmt of stmts) {
176            const invoke = stmt.getInvokeExpr();
177            let isReflect = false;
178            let paramIdx = -1;
179            if (invoke && invoke instanceof ArkStaticInvokeExpr) {
180                const classSig = invoke.getMethodSignature().getDeclaringClassSignature();
181                if (
182                    classSig.getDeclaringFileSignature().getProjectName() === 'built-in' &&
183                    classSig.getDeclaringNamespaceSignature()?.getNamespaceName() === 'Reflect'
184                ) {
185                    isReflect = true;
186                    paramIdx = REFLECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1;
187                }
188            }
189            if (invoke && invoke instanceof ArkStaticInvokeExpr) {
190                const methodSig = invoke.getMethodSignature();
191                const classSig = methodSig.getDeclaringClassSignature();
192                if (classSig.getClassName() === 'ObjectConstructor' || classSig.getClassName() === 'Object') {
193                    paramIdx =
194                        OBJECT_API.get(invoke.getMethodSignature().getMethodSubSignature().getMethodName()) ?? -1;
195                }
196            }
197            if (paramIdx === -1) {
198                continue;
199            }
200            DVFGHelper.buildSingleDVFG(target, scene);
201
202            const argDefs = this.findArgumentDef(stmt, paramIdx, currentLang, importVarMap, topLevelVarMap, scene);
203            if (this.isLanguage(argDefs)) {
204                this.reportIssue({ problemStmt: stmt, objLanguage: argDefs as Language }, currentLang, isReflect);
205            } else {
206                argDefs.forEach(def => {
207                    let result: ObjDefInfo[] = [];
208                    let visited: Set<Stmt> = new Set();
209                    this.checkFromStmt(def, currentLang, result, visited, importVarMap, topLevelVarMap, scene);
210                    result.forEach(objDefInfo => {
211                        this.reportIssue(objDefInfo, currentLang, isReflect);
212                    });
213                });
214            }
215        }
216    }
217
218    private reportIssue(objDefInfo: ObjDefInfo, apiLang: Language, isReflect: boolean) {
219        const problemStmt = objDefInfo.problemStmt;
220        const problemStmtMtd = problemStmt.getCfg().getDeclaringMethod();
221        const problemStmtLang = problemStmtMtd?.getLanguage();
222        const objLanguage = objDefInfo.objLanguage;
223        if (objLanguage === Language.UNKNOWN || problemStmtLang === Language.UNKNOWN) {
224            logger.warn(`cannot find the language for def: ${problemStmt.toString()}`);
225            return;
226        }
227        const interopRule = findInteropRule(apiLang, objLanguage, problemStmtLang, isReflect);
228        if (!interopRule) {
229            return;
230        }
231        const line = problemStmt.getOriginPositionInfo().getLineNo();
232        const column = problemStmt.getOriginPositionInfo().getColNo();
233        const problem = 'Interop';
234        const desc = `${interopRule.description} (${interopRule.ruleId})`;
235        const severity = interopRule.severity;
236        const ruleId = this.rule.ruleId;
237        const filePath = problemStmtMtd?.getDeclaringArkFile()?.getFilePath() ?? '';
238        const defeats = new Defects(
239            line,
240            column,
241            column,
242            problem,
243            desc,
244            severity,
245            ruleId,
246            filePath,
247            '',
248            true,
249            false,
250            false
251        );
252        this.issues.push(new IssueReport(defeats, undefined));
253    }
254
255    private checkFromStmt(
256        stmt: Stmt,
257        apiLanguage: Language,
258        res: ObjDefInfo[],
259        visited: Set<Stmt>,
260        importVarMap: Map<string, Language>,
261        topLevelVarMap: Map<string, Stmt[]>,
262        scene: Scene,
263        depth: number = 0
264    ): void {
265        if (depth > CALL_DEPTH_LIMIT) {
266            return;
267        }
268        const node = DVFGHelper.getOrNewDVFGNode(stmt, scene);
269        let worklist: DVFGNode[] = [node];
270        while (worklist.length > 0) {
271            const current = worklist.shift()!;
272            const currentStmt = current.getStmt();
273            if (visited.has(currentStmt)) {
274                continue;
275            }
276            visited.add(currentStmt);
277            if (currentStmt instanceof ArkAssignStmt) {
278                const rightOp = currentStmt.getRightOp();
279                if (rightOp instanceof ArkInstanceFieldRef) {
280                    // 处理 Reflect.apply(obj.getName, {a : 12})
281                    const base = rightOp.getBase();
282                    if (base instanceof Local && base.getDeclaringStmt()) {
283                        worklist.push(DVFGHelper.getOrNewDVFGNode(base.getDeclaringStmt()!, scene));
284                        continue;
285                    }
286                }
287                if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) {
288                    const name = rightOp.getName();
289                    if (importVarMap.has(name)) {
290                        res.push({ problemStmt: currentStmt, objLanguage: importVarMap.get(name)! });
291                        continue;
292                    }
293                }
294                const rightOpTy = rightOp.getType();
295                if (!this.isIrrelevantType(rightOpTy)) {
296                    const rightOpTyLang = this.getTypeDefinedLang(rightOpTy, scene);
297                    if (rightOpTyLang && rightOpTyLang !== apiLanguage) {
298                        res.push({ problemStmt: currentStmt, objLanguage: rightOpTyLang });
299                        continue;
300                    }
301                }
302            }
303            const callsite = this.cg.getCallSiteByStmt(currentStmt);
304            if (callsite.length > 0) {
305                callsite.forEach(cs => {
306                    const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID);
307                    if (!declaringMtd || !declaringMtd.getCfg()) {
308                        return;
309                    }
310                    DVFGHelper.buildSingleDVFG(declaringMtd, scene);
311                    declaringMtd
312                        .getReturnStmt()
313                        .forEach(r =>
314                            this.checkFromStmt(
315                                r,
316                                apiLanguage,
317                                res,
318                                visited,
319                                importVarMap,
320                                topLevelVarMap,
321                                scene,
322                                depth + 1
323                            )
324                        );
325                });
326                continue;
327            }
328            const paramRef = this.isFromParameter(currentStmt);
329            if (paramRef) {
330                const paramIdx = paramRef.getIndex();
331                this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature()).forEach(cs => {
332                    const declaringMtd = cs.getCfg().getDeclaringMethod();
333                    DVFGHelper.buildSingleDVFG(declaringMtd, scene);
334                    const argDefs = this.findArgumentDef(
335                        cs,
336                        paramIdx,
337                        apiLanguage,
338                        importVarMap,
339                        topLevelVarMap,
340                        scene
341                    );
342                    if (this.isLanguage(argDefs)) {
343                        // imported var
344                        res.push({ problemStmt: cs, objLanguage: argDefs as Language });
345                    } else {
346                        argDefs.forEach(d => {
347                            this.checkFromStmt(
348                                d,
349                                apiLanguage,
350                                res,
351                                visited,
352                                importVarMap,
353                                topLevelVarMap,
354                                scene,
355                                depth + 1
356                            );
357                        });
358                    }
359                });
360                continue;
361            }
362            current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode));
363            if (stmt instanceof ArkAssignStmt) {
364                const rightOp = stmt.getRightOp();
365                if (rightOp instanceof Local && !rightOp.getDeclaringStmt()) {
366                    (topLevelVarMap.get(rightOp.getName()) ?? []).forEach(def => {
367                        worklist.push(DVFGHelper.getOrNewDVFGNode(def, scene));
368                    });
369                }
370            }
371        }
372    }
373
374    private isIrrelevantType(ty: Type): boolean {
375        const isObjectTy = (ty: Type): boolean => {
376            return ty instanceof ClassType && ty.getClassSignature().getClassName() === 'Object';
377        };
378        const isESObjectTy = (ty: Type): boolean => {
379            return ty.toString() === 'ESObject';
380        };
381        const isAnyTy = (ty: Type): ty is AnyType => {
382            return ty instanceof AnyType;
383        };
384        const isUnkwonTy = (ty: Type): ty is UnknownType => {
385            return ty instanceof UnknownType;
386        };
387        return isObjectTy(ty) || isESObjectTy(ty) || isAnyTy(ty) || isUnkwonTy(ty);
388    }
389
390    private isFromParameter(stmt: Stmt): ArkParameterRef | undefined {
391        if (!(stmt instanceof ArkAssignStmt)) {
392            return undefined;
393        }
394        const rightOp = stmt.getRightOp();
395        if (rightOp instanceof ArkParameterRef) {
396            return rightOp;
397        }
398        return undefined;
399    }
400
401    private isLanguage(value: Stmt[] | Language): value is Language {
402        return Object.values(Language).includes(value as Language);
403    }
404
405    private findArgumentDef(
406        stmt: Stmt,
407        argIdx: number,
408        apiLanguage: Language,
409        importVarMap: Map<string, Language>,
410        topLevelVarMap: Map<string, Stmt[]>,
411        scene: Scene
412    ): Stmt[] | Language {
413        const invoke = stmt.getInvokeExpr();
414        const getKey = (v: Value): Value | FieldSignature => {
415            return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v;
416        };
417        const arg: Value | FieldSignature = getKey((invoke as AbstractInvokeExpr).getArg(argIdx));
418        if (!arg) {
419            logger.error(`arg${argIdx} of invoke ${stmt.toString()} is undefined`);
420            return [];
421        }
422        if (arg instanceof Local && arg.getDeclaringStmt() instanceof ArkAssignStmt) {
423            // 特殊处理,obj.getName 的类型有 bug
424            const rightOp = (arg.getDeclaringStmt() as ArkAssignStmt).getRightOp();
425            if (rightOp instanceof ArkInstanceFieldRef) {
426                const base = rightOp.getBase();
427                if (base instanceof Local && base.getDeclaringStmt()) {
428                    return [base.getDeclaringStmt()!];
429                }
430            }
431        }
432        const argTy = arg.getType();
433        if (!this.isIrrelevantType(argTy)) {
434            const argTyLang = this.getTypeDefinedLang(argTy, scene);
435            if (argTyLang && argTyLang !== apiLanguage) {
436                return argTyLang;
437            }
438        }
439        if (arg instanceof Local && !arg.getDeclaringStmt()) {
440            const name = arg.getName();
441            return topLevelVarMap.get(name) ?? importVarMap.get(name) ?? [];
442        }
443        return Array.from(DVFGHelper.getOrNewDVFGNode(stmt, scene).getIncomingEdge())
444            .map(e => (e.getSrcNode() as DVFGNode).getStmt())
445            .filter(s => {
446                return s instanceof ArkAssignStmt && arg === getKey(s.getLeftOp());
447            });
448    }
449
450    private getTypeDefinedLang(type: Type, scene: Scene): Language | undefined {
451        let file = undefined;
452        if (type instanceof ClassType) {
453            file = scene.getFile(type.getClassSignature().getDeclaringFileSignature());
454        } else if (type instanceof FunctionType) {
455            file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature());
456        }
457        if (file) {
458            return file.getLanguage();
459        }
460        return undefined;
461    }
462}
463