• 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    ArkInstanceInvokeExpr,
18    ArkMethod,
19    ArkStaticInvokeExpr,
20    CallGraph,
21    CallGraphBuilder,
22    Stmt,
23    Value,
24} from 'arkanalyzer/lib';
25import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
26import { BaseChecker, BaseMetaData } from '../BaseChecker';
27import { Rule, Defects, ClassMatcher, MethodMatcher, MatcherTypes, MatcherCallback } from '../../Index';
28import { IssueReport } from '../../model/Defects';
29import { CALL_DEPTH_LIMIT, CALLBACK_METHOD_NAME, CallGraphHelper } from './Utils';
30import { WarnInfo } from '../../utils/common/Utils';
31
32const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'AppStorageGetCheck');
33const gMetaData: BaseMetaData = {
34    severity: 1,
35    ruleDocPath: '',
36    description:
37        'Get State of AppStorage in component build function, it will update UI interface when the state of AppStorage is changed',
38};
39
40const APP_STORAGE_STR = 'AppStorage';
41const API_SET: Set<string> = new Set<string>(['has', 'get', 'keys', 'size']);
42
43export class AppStorageGetCheck implements BaseChecker {
44    readonly metaData: BaseMetaData = gMetaData;
45    public rule: Rule;
46    public defects: Defects[] = [];
47    public issues: IssueReport[] = [];
48
49    private classMatcher: ClassMatcher = {
50        matcherType: MatcherTypes.CLASS,
51        hasViewTree: true,
52    };
53
54    private buildMatcher: MethodMatcher = {
55        matcherType: MatcherTypes.METHOD,
56        class: [this.classMatcher],
57        name: ['build'],
58    };
59
60    public registerMatchers(): MatcherCallback[] {
61        const matchBuildCb: MatcherCallback = {
62            matcher: this.buildMatcher,
63            callback: this.check,
64        };
65        return [matchBuildCb];
66    }
67
68    public check = (targetMtd: ArkMethod): void => {
69        const scene = targetMtd.getDeclaringArkFile().getScene();
70        let callGraph = CallGraphHelper.getCGInstance(scene);
71        let callGraphBuilder = new CallGraphBuilder(callGraph, scene);
72        callGraphBuilder.buildClassHierarchyCallGraph([targetMtd.getSignature()]);
73
74        this.checkMethod(targetMtd, callGraph);
75    };
76
77    private checkMethod(targetMtd: ArkMethod, cg: CallGraph, depth: number = 0): void {
78        if (depth > CALL_DEPTH_LIMIT) {
79            return;
80        }
81        const stmts = targetMtd.getBody()?.getCfg().getStmts() ?? [];
82        for (const stmt of stmts) {
83            this.checkAppStorageGet(stmt);
84            const invokeExpr = stmt.getInvokeExpr();
85            if (invokeExpr && invokeExpr instanceof ArkInstanceInvokeExpr) {
86                if (
87                    CALLBACK_METHOD_NAME.includes(
88                        invokeExpr.getMethodSignature().getMethodSubSignature().getMethodName()
89                    )
90                ) {
91                    continue;
92                }
93            }
94            let callsite = cg.getCallSiteByStmt(stmt);
95            callsite.forEach(cs => {
96                let callee = cg.getArkMethodByFuncID(cs.calleeFuncID);
97                if (callee) {
98                    this.checkMethod(callee, cg, depth + 1);
99                }
100            });
101        }
102    }
103
104    private checkAppStorageGet(stmt: Stmt): void {
105        let invokeExpr = stmt.getInvokeExpr();
106        if (!(invokeExpr instanceof ArkStaticInvokeExpr)) {
107            return;
108        }
109        const methodSig = invokeExpr.getMethodSignature();
110        if (methodSig.getDeclaringClassSignature().getClassName() !== APP_STORAGE_STR) {
111            return;
112        }
113        if (!API_SET.has(methodSig.getMethodSubSignature().getMethodName())) {
114            return;
115        }
116        this.addIssueReport(stmt, invokeExpr);
117    }
118
119    private addIssueReport(stmt: Stmt, operand: Value): void {
120        const severity = this.rule.alert ?? this.metaData.severity;
121        const warnInfo = this.getLineAndColumn(stmt, operand);
122        const problem = 'AppStorageSpecChanged';
123        const desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`;
124        let defects = new Defects(
125            warnInfo.line,
126            warnInfo.startCol,
127            warnInfo.endCol,
128            problem,
129            desc,
130            severity,
131            this.rule.ruleId,
132            warnInfo.filePath,
133            this.metaData.ruleDocPath,
134            true,
135            false,
136            false
137        );
138        this.issues.push(new IssueReport(defects, undefined));
139    }
140
141    private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo {
142        const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile();
143        const originPosition = stmt.getOperandOriginalPosition(operand);
144        if (arkFile && originPosition) {
145            const originPath = arkFile.getFilePath();
146            const line = originPosition.getFirstLine();
147            const startCol = originPosition.getFirstCol();
148            const endCol = startCol;
149            return { line, startCol, endCol, filePath: originPath };
150        } else {
151            logger.debug('ArkFile is null.');
152        }
153        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
154    }
155}
156