• 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    ArkAssignStmt,
18    ArkInstanceFieldRef,
19    ArkInstanceInvokeExpr,
20    CallGraph,
21    CallGraphBuilder,
22    Local,
23    Stmt,
24    Value,
25    FieldSignature,
26    ArkMethod,
27} from 'arkanalyzer';
28import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
29import { BaseChecker, BaseMetaData } from '../BaseChecker';
30import { Rule, Defects, MatcherTypes, MatcherCallback, ClassMatcher, MethodMatcher } from '../../Index';
31import { IssueReport } from '../../model/Defects';
32import { CALL_DEPTH_LIMIT, CALLBACK_METHOD_NAME, CallGraphHelper } from './Utils';
33import { WarnInfo } from '../../utils/common/Utils';
34
35const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ModifyStateVarCheck');
36const gMetaData: BaseMetaData = {
37    severity: 1,
38    ruleDocPath: '',
39    description: 'It is not allowed to update state when the build function is running',
40};
41
42export class ModifyStateVarCheck implements BaseChecker {
43    readonly metaData: BaseMetaData = gMetaData;
44    public rule: Rule;
45    public defects: Defects[] = [];
46    public issues: IssueReport[] = [];
47
48    private classMatcher: ClassMatcher = {
49        matcherType: MatcherTypes.CLASS,
50        hasViewTree: true,
51    };
52
53    private buildMatcher: MethodMatcher = {
54        matcherType: MatcherTypes.METHOD,
55        class: [this.classMatcher],
56        name: ['build'],
57    };
58
59    public registerMatchers(): MatcherCallback[] {
60        const matchBuildCb: MatcherCallback = {
61            matcher: this.buildMatcher,
62            callback: this.check,
63        };
64        return [matchBuildCb];
65    }
66
67    public check = (target: ArkMethod): void => {
68        const scene = target.getDeclaringArkFile().getScene();
69        let callGraph = CallGraphHelper.getCGInstance(scene);
70        let callGraphBuilder = new CallGraphBuilder(callGraph, scene);
71        callGraphBuilder.buildClassHierarchyCallGraph([target.getSignature()]);
72
73        const arkClass = target.getDeclaringArkClass();
74        const stateVars = new Set(
75            arkClass
76                .getFields()
77                .filter(f => f.hasDecorator('State'))
78                .map(f => f.getSignature())
79        );
80        if (stateVars.size > 0) {
81            this.checkMethod(target, callGraph, stateVars);
82        }
83    };
84
85    private checkMethod(target: ArkMethod, cg: CallGraph, stateVars: Set<FieldSignature>, depth: number = 0): void {
86        if (depth > CALL_DEPTH_LIMIT) {
87            return;
88        }
89        let aliases = new Set<Local>();
90        const stmts = target.getBody()?.getCfg().getStmts() ?? [];
91        for (const stmt of stmts) {
92            const invokeExpr = stmt.getInvokeExpr();
93            if (invokeExpr && invokeExpr instanceof ArkInstanceInvokeExpr) {
94                if (
95                    CALLBACK_METHOD_NAME.includes(
96                        invokeExpr.getMethodSignature().getMethodSubSignature().getMethodName()
97                    )
98                ) {
99                    continue;
100                }
101            }
102            cg.getCallSiteByStmt(stmt).forEach(cs => {
103                const callee = cg.getArkMethodByFuncID(cs.calleeFuncID);
104                if (callee) {
105                    this.checkMethod(callee, cg, stateVars, depth + 1);
106                }
107            });
108            if (!(stmt instanceof ArkAssignStmt)) {
109                continue;
110            }
111            if (this.isAssignToStateVar(stmt, stateVars, aliases)) {
112                this.addIssueReport(stmt, stmt.getLeftOp());
113            } else {
114                const alias = this.isAliasOfStateVar(stmt, stateVars, aliases);
115                if (alias) {
116                    aliases.add(alias);
117                }
118            }
119        }
120    }
121
122    private isAssignToStateVar(stmt: ArkAssignStmt, stateVars: Set<FieldSignature>, aliases: Set<Local>): boolean {
123        const leftOp = stmt.getLeftOp();
124        if (leftOp instanceof ArkInstanceFieldRef) {
125            // this.n = 1 or this.obj.n = 1
126            return stateVars.has(leftOp.getFieldSignature()) || aliases.has(leftOp.getBase());
127        } else if (leftOp instanceof Local) {
128            return aliases.has(leftOp);
129        }
130        return false;
131    }
132
133    private isAliasOfStateVar(
134        stmt: ArkAssignStmt,
135        stateVars: Set<FieldSignature>,
136        aliases: Set<Local>
137    ): Local | undefined {
138        const leftOp = stmt.getLeftOp();
139        const rightOp = stmt.getRightOp();
140        if (leftOp instanceof Local && rightOp instanceof ArkInstanceFieldRef) {
141            // %1 = this.n
142            // or
143            // %1 = this.obj; %2 = %1.n
144            if (stateVars.has(rightOp.getFieldSignature()) || aliases.has(rightOp.getBase())) {
145                return leftOp;
146            }
147        }
148        return undefined;
149    }
150
151    private addIssueReport(stmt: Stmt, operand: Value): void {
152        const severity = this.rule.alert ?? this.metaData.severity;
153        const warnInfo = this.getLineAndColumn(stmt, operand);
154        const problem = 'NoStateUpdateDuringRender';
155        const desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`;
156        let defects = new Defects(
157            warnInfo.line,
158            warnInfo.startCol,
159            warnInfo.endCol,
160            problem,
161            desc,
162            severity,
163            this.rule.ruleId,
164            warnInfo.filePath,
165            this.metaData.ruleDocPath,
166            true,
167            false,
168            false
169        );
170        this.issues.push(new IssueReport(defects, undefined));
171    }
172
173    private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo {
174        const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile();
175        const originPosition = stmt.getOperandOriginalPosition(operand);
176        if (arkFile && originPosition) {
177            const originPath = arkFile.getFilePath();
178            const line = originPosition.getFirstLine();
179            const startCol = originPosition.getFirstCol();
180            const endCol = startCol;
181            return { line, startCol, endCol, filePath: originPath };
182        } else {
183            logger.debug('ArkFile is null.');
184        }
185        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
186    }
187}
188