• 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    FunctionType,
28    ClassType,
29    ArkNamespace,
30    PrimitiveType,
31    UnclearReferenceType,
32} from 'arkanalyzer/lib';
33import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
34import { BaseChecker, BaseMetaData } from '../BaseChecker';
35import { Rule, Defects, MatcherCallback } from '../../Index';
36import { IssueReport } from '../../model/Defects';
37import { DVFGNode } from 'arkanalyzer/lib/VFG/DVFG';
38import { CALL_DEPTH_LIMIT, DVFGHelper, GlobalCallGraphHelper } from './Utils';
39import { Language } from 'arkanalyzer/lib/core/model/ArkFile';
40
41const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'InteropAssignCheck');
42const gMetaData: BaseMetaData = {
43    severity: 1,
44    ruleDocPath: '',
45    description: 'Should not pass or assign a dynamic object to a variable of static type',
46};
47
48const RULE_ID = 'arkts-interop-s2d-dynamic-args-to-static';
49const BOXED_SET: Set<string> = new Set<string>(['String', 'Boolean', 'BigInt', 'Number']);
50export class InteropAssignCheck implements BaseChecker {
51    readonly metaData: BaseMetaData = gMetaData;
52    public rule: Rule;
53    public defects: Defects[] = [];
54    public issues: IssueReport[] = [];
55    private cg: CallGraph;
56
57    public registerMatchers(): MatcherCallback[] {
58        const matchBuildCb: MatcherCallback = {
59            matcher: undefined,
60            callback: this.check,
61        };
62        return [matchBuildCb];
63    }
64
65    public check = (scene: Scene): void => {
66        this.cg = GlobalCallGraphHelper.getCGInstance(scene);
67
68        for (let arkFile of scene.getFiles()) {
69            for (let clazz of arkFile.getClasses()) {
70                for (let mtd of clazz.getMethods()) {
71                    this.processArkMethod(mtd, scene);
72                }
73            }
74            for (let namespace of arkFile.getAllNamespacesUnderThisFile()) {
75                this.processNameSpace(namespace, scene);
76            }
77        }
78    };
79
80    public processNameSpace(namespace: ArkNamespace, scene: Scene): void {
81        for (let clazz of namespace.getClasses()) {
82            for (let mtd of clazz.getMethods()) {
83                this.processArkMethod(mtd, scene);
84            }
85        }
86    }
87
88    public processArkMethod(target: ArkMethod, scene: Scene): void {
89        if (target.getLanguage() === Language.ARKTS1_2) {
90            this.checkPassToFunction(target, scene);
91        } else if (target.getLanguage() === Language.ARKTS1_1) {
92            this.checkAssignToField(target, scene);
93        }
94    }
95
96    private checkPassToFunction(target: ArkMethod, scene: Scene) {
97        const callsites = this.cg.getInvokeStmtByMethod(target.getSignature());
98        callsites
99            .filter(cs => cs.getCfg().getDeclaringMethod().getLanguage() === Language.ARKTS1_1)
100            .forEach(cs => {
101                let hasTargetArg = false;
102                const invoke = cs.getInvokeExpr()!;
103                const csMethod = cs.getCfg().getDeclaringMethod();
104                invoke.getArgs().forEach(arg => {
105                    const argTy = arg.getType();
106                    if (argTy instanceof PrimitiveType || this.isBoxedType(argTy)) {
107                        return;
108                    }
109                    const argTyLang = this.getTypeDefinedLang(argTy, scene) ?? csMethod?.getLanguage() ?? Language.UNKNOWN;
110                    if (argTyLang === Language.ARKTS1_1) {
111                        hasTargetArg = true;
112                    }
113                });
114                if (!hasTargetArg) {
115                    return;
116                }
117                let line = cs.getOriginPositionInfo().getLineNo();
118                let column = cs.getOriginPositionInfo().getColNo();
119                const problem = 'Interop';
120                const desc = `${this.metaData.description} (${RULE_ID})`;
121                const severity = this.metaData.severity;
122                const ruleId = this.rule.ruleId;
123                const filePath = csMethod?.getDeclaringArkFile()?.getFilePath() ?? '';
124                const defeats = new Defects(line, column, column, problem, desc, severity, ruleId, filePath, '', true, false, false);
125                this.issues.push(new IssueReport(defeats, undefined));
126            });
127    }
128
129    private isBoxedType(checkType: Type): boolean {
130        const unclear = checkType instanceof UnclearReferenceType && BOXED_SET.has(checkType.getName());
131        const cls = checkType instanceof ClassType && BOXED_SET.has(checkType.getClassSignature().getClassName());
132        return unclear || cls;
133    }
134
135    private checkAssignToField(target: ArkMethod, scene: Scene) {
136        const assigns: Stmt[] = this.collectAssignToObjectField(target, scene);
137        if (assigns.length > 0) {
138            DVFGHelper.buildSingleDVFG(target, scene);
139        }
140        assigns.forEach(assign => {
141            let result: Stmt[] = [];
142            let visited: Set<Stmt> = new Set();
143            this.checkFromStmt(assign, scene, result, visited);
144            if (result.length === 0) {
145                // 这句 a.data = y 右侧的值没有从 1.1 传入的可能
146                return;
147            }
148            let line = assign.getOriginPositionInfo().getLineNo();
149            let column = assign.getOriginPositionInfo().getColNo();
150            const problem = 'Interop';
151            const desc = `${this.metaData.description} (${RULE_ID})`;
152            const severity = this.metaData.severity;
153            const ruleId = this.rule.ruleId;
154            const filePath = assign.getCfg().getDeclaringMethod().getDeclaringArkFile()?.getFilePath() ?? '';
155            const defeats = new Defects(line, column, column, problem, desc, severity, ruleId, filePath, '', true, false, false);
156            this.issues.push(new IssueReport(defeats, undefined));
157        });
158    }
159
160    private collectAssignToObjectField(method: ArkMethod, scene: Scene): Stmt[] {
161        const res: Stmt[] = [];
162        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
163        for (const stmt of stmts) {
164            if (!(stmt instanceof ArkAssignStmt)) {
165                continue;
166            }
167            const leftOp = stmt.getLeftOp();
168            if (!(leftOp instanceof ArkInstanceFieldRef)) {
169                continue;
170            }
171            if (!this.isObjectTy(leftOp.getType())) {
172                continue;
173            }
174            const baseTy = leftOp.getBase().getType();
175            if (baseTy instanceof ClassType) {
176                const klass = scene.getClass(baseTy.getClassSignature());
177                if (!klass) {
178                    logger.warn(`check field of type 'Object' failed: cannot find arkclass by sig ${baseTy.getClassSignature().toString()}`);
179                } else if (klass.getLanguage() === Language.ARKTS1_2) {
180                    res.push(stmt);
181                }
182            } else {
183                logger.warn(`check field of type 'Object' failed: unexpected base type ${baseTy.toString()}`);
184            }
185        }
186        return res;
187    }
188
189    private checkFromStmt(stmt: Stmt, scene: Scene, res: Stmt[], visited: Set<Stmt>, depth: number = 0): void {
190        if (depth > CALL_DEPTH_LIMIT) {
191            return;
192        }
193        const node = DVFGHelper.getOrNewDVFGNode(stmt, scene);
194        let worklist: DVFGNode[] = [node];
195        while (worklist.length > 0) {
196            const current = worklist.shift()!;
197            const currentStmt = current.getStmt();
198            if (visited.has(currentStmt)) {
199                continue;
200            }
201            visited.add(currentStmt);
202            if (currentStmt instanceof ArkAssignStmt) {
203                const rightOpTy = currentStmt.getRightOp().getType();
204                if (!this.isObjectTy(rightOpTy) && this.getTypeDefinedLang(rightOpTy, scene) === Language.ARKTS1_1) {
205                    res.push(currentStmt);
206                    continue;
207                }
208            }
209            const callsite = this.cg.getCallSiteByStmt(currentStmt);
210            callsite.forEach(cs => {
211                const declaringMtd = this.cg.getArkMethodByFuncID(cs.calleeFuncID);
212                if (!declaringMtd || !declaringMtd.getCfg()) {
213                    return;
214                }
215                DVFGHelper.buildSingleDVFG(declaringMtd, scene);
216                declaringMtd.getReturnStmt().forEach(r => this.checkFromStmt(r, scene, res, visited, depth + 1));
217            });
218            const paramRef = this.isFromParameter(currentStmt);
219            if (paramRef) {
220                const paramIdx = paramRef.getIndex();
221                const callsites = this.cg.getInvokeStmtByMethod(currentStmt.getCfg().getDeclaringMethod().getSignature());
222                callsites.forEach(cs => {
223                    const declaringMtd = cs.getCfg().getDeclaringMethod();
224                    DVFGHelper.buildSingleDVFG(declaringMtd, scene);
225                });
226                this.collectArgDefs(paramIdx, callsites, scene).forEach(d => this.checkFromStmt(d, scene, res, visited, depth + 1));
227            }
228            current.getIncomingEdge().forEach(e => worklist.push(e.getSrcNode() as DVFGNode));
229        }
230    }
231
232    private isFromParameter(stmt: Stmt): ArkParameterRef | undefined {
233        if (!(stmt instanceof ArkAssignStmt)) {
234            return undefined;
235        }
236        const rightOp = stmt.getRightOp();
237        if (rightOp instanceof ArkParameterRef) {
238            return rightOp;
239        }
240        return undefined;
241    }
242
243    private collectArgDefs(argIdx: number, callsites: Stmt[], scene: Scene): Stmt[] {
244        const getKey = (v: Value): Value | FieldSignature => {
245            return v instanceof ArkInstanceFieldRef ? v.getFieldSignature() : v;
246        };
247        return callsites.flatMap(callsite => {
248            const target: Value | FieldSignature = getKey(callsite.getInvokeExpr()!.getArg(argIdx));
249            return Array.from(DVFGHelper.getOrNewDVFGNode(callsite, scene).getIncomingEdge())
250                .map(e => (e.getSrcNode() as DVFGNode).getStmt())
251                .filter(s => {
252                    return s instanceof ArkAssignStmt && target === getKey(s.getLeftOp());
253                });
254        });
255    }
256
257    private isObjectTy(ty: Type): boolean {
258        return ty instanceof ClassType && ty.getClassSignature().getClassName() === 'Object';
259    }
260
261    private getTypeDefinedLang(type: Type, scene: Scene): Language | undefined {
262        let file = undefined;
263        if (type instanceof ClassType) {
264            file = scene.getFile(type.getClassSignature().getDeclaringFileSignature());
265        } else if (type instanceof FunctionType) {
266            file = scene.getFile(type.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature());
267        }
268        if (file) {
269            return file.getLanguage();
270        }
271        return undefined;
272    }
273}
274