• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 - 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 */
15import { ArkArrayRef, ArkAssignStmt, ArkClass, ArkIfStmt, ArkThisRef, BasicBlock, DEFAULT_ARK_METHOD_NAME, Local, Scene, Stmt } from 'arkanalyzer';
16import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
17import { CheckerStorage, CheckerUtils, Scope, ScopeType, TempLocation } from '../../Index';
18import { Variable } from '../../model/Variable';
19import { VarInfo } from '../../model/VarInfo';
20import { FixUtils } from './FixUtils';
21
22const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ScopeHelper');
23
24export class ScopeHelper {
25    private gFilePath: string = '';
26    private firstBlock: BasicBlock | undefined;
27    private finishBlockSet: Set<BasicBlock>;
28    private isSwitchLastCase = false;
29    private gFinishIfStmtLines: number[] = [];
30    private gTernaryConditionLines: Set<number> = new Set();
31
32    public buildScope(scene: Scene): void {
33        let scopeMap = new Map<string, Scope>();
34        for (const file of scene.getFiles()) {
35            this.gFilePath = file.getFilePath();
36            const firstScope = new Scope(null, new Array<Variable>(), 0);
37            scopeMap.set(this.gFilePath, firstScope);
38            for (const clazz of file.getClasses()) {
39                this.createScopeInClass(clazz, firstScope);
40            }
41        }
42        CheckerStorage.getInstance().setScopeMap(scopeMap);
43    }
44
45    private createScopeInClass(clazz: ArkClass, firstScope: Scope): void {
46        for (let method of clazz.getMethods()) {
47            this.gFinishIfStmtLines = [];
48            this.gTernaryConditionLines.clear();
49            this.finishBlockSet = new Set();
50            this.firstBlock = method.getBody()?.getCfg()?.getStartingBlock();
51            if (!this.firstBlock) {
52                logger.trace(`${clazz.getName()}::${method.getName()} has no body.`);
53                continue;
54            }
55            let curScope = firstScope;
56            if (method.getName() !== DEFAULT_ARK_METHOD_NAME) {
57                curScope = new Scope(firstScope, new Array<Variable>(), 1);
58                firstScope.setChildScope(curScope);
59            }
60            this.blockProcess(this.firstBlock, curScope);
61        }
62    }
63
64    private blockProcess(block: BasicBlock, parentScope: Scope): void {
65        let curScope = parentScope;
66        let stmts = block.getStmts();
67        if (stmts.length === 0) {
68            return;
69        }
70        if (this.isFirstThisBlock(block)) {
71            const succBlocks = block.getSuccessors();
72            if (succBlocks.length > 0) {
73                this.blockProcess(block.getSuccessors()[0], curScope);
74                return;
75            }
76        }
77        let isSwitchBlock = false;
78        let nextScopeType = CheckerUtils.getScopeType(stmts[stmts.length - 1]);
79        curScope.blocks.add(block);
80        this.finishBlockSet.add(block);
81        for (let i = 0; i < stmts.length; i++) {
82            const stmt = stmts[i];
83            if ((i === stmts.length - 1) && (this.isForStmtDefinedPart(stmts[stmts.length - 1], nextScopeType))) {
84                curScope = this.genChildScope(curScope, ScopeType.FOR_CONDITION_TYPE);
85                nextScopeType = ScopeType.UNKNOWN_TYPE;
86            }
87            if (!FixUtils.hasOwnPropertyOwn(stmt, 'scope')) {
88                Object.defineProperty(stmt, 'scope', { value: curScope });
89            }
90            if (stmt instanceof ArkAssignStmt && !this.assignStmtProcess(stmt, curScope)) {
91                continue;
92            } else if (stmt instanceof ArkIfStmt) {
93                this.gFinishIfStmtLines.push(stmt.getOriginPositionInfo().getLineNo());
94                if (/^.*\?.*:.*$/.test(stmt.getOriginalText() ?? '')) {
95                    this.gTernaryConditionLines.add(stmt.getOriginPositionInfo().getLineNo());
96                }
97            }
98        }
99        if (isSwitchBlock) {
100            this.switchBlockPreProcess(block, curScope);
101        } else {
102            this.nextBlockPreProcess(block, curScope, nextScopeType);
103        }
104    }
105
106    private isFirstThisBlock(block: BasicBlock): boolean {
107        if (block.getPredecessors().length === 0) {
108            const stmts = block.getStmts();
109            if (stmts.length === 1) {
110                if (stmts[0] instanceof ArkAssignStmt &&
111                    stmts[0].getRightOp() instanceof ArkThisRef) {
112                    return true;
113                }
114            }
115        }
116        return false;
117    }
118
119    private isForStmtDefinedPart(stmt: Stmt, nextScopeType: ScopeType): boolean {
120        if (stmt instanceof ArkAssignStmt && nextScopeType === ScopeType.FOR_CONDITION_TYPE &&
121            !this.gFinishIfStmtLines.includes(stmt.getOriginPositionInfo().getLineNo())) {
122            return true;
123        }
124        return false;
125    }
126
127    private genChildScope(curScope: Scope, scopeType: ScopeType): Scope {
128        let newScope = new Scope(curScope, new Array<Variable>(), curScope.scopeLevel + 1, scopeType);
129        curScope.setChildScope(newScope);
130        return newScope;
131    }
132
133    private assignStmtProcess(stmt: Stmt, curScope: Scope): boolean {
134        let def = stmt.getDef();
135        if (def instanceof Local) {
136            if (def.getName() === 'this') {
137                return false;
138            }
139            const isForStmtThirdPart = (CheckerUtils.getScopeType(stmt) === ScopeType.FOR_CONDITION_TYPE &&
140                this.gFinishIfStmtLines.includes(stmt.getOriginPositionInfo().getLineNo()));
141            if (CheckerUtils.wherIsTemp(stmt) === TempLocation.LEFT ||
142                (!isForStmtThirdPart && CheckerUtils.isDeclaringStmt(def.getName(), stmt))) {
143                curScope.addVariable(new Variable(stmt));
144            } else {
145                this.getDefAndSetRedef(def.getName(), curScope, curScope, stmt, SetDefMode.REDEF);
146            }
147        } else if (def instanceof ArkArrayRef) {
148            let base = def.getBase();
149            if (base instanceof Local && !base.getName().includes('%')) {
150                this.getDefAndSetRedef(base.getName(), curScope, curScope, stmt, SetDefMode.LEFTUSED);
151            }
152        }
153        return true;
154    }
155
156    private getDefAndSetRedef(name: string, searchScope: Scope, varScope: Scope, stmt: Stmt, mode: SetDefMode): boolean {
157        let defList = searchScope.defList;
158        for (let variable of defList) {
159            if (variable.getName() === name) {
160                if (mode === SetDefMode.REDEF) {
161                    variable.redefInfo.add(new VarInfo(stmt, varScope));
162                } else if (mode === SetDefMode.LEFTUSED) {
163                    variable.leftUsedInfo.add(new VarInfo(stmt, varScope));
164                }
165            }
166        }
167        if (searchScope.parentScope !== null) {
168            return this.getDefAndSetRedef(name, searchScope.parentScope, varScope, stmt, mode);
169        }
170        return false;
171    }
172
173    private switchBlockPreProcess(block: BasicBlock, curScope: Scope): void {
174        const caseBlocks = block.getSuccessors();
175        for (let caseBlock of caseBlocks) {
176            this.finishBlockSet.add(caseBlock);
177        }
178        for (let i = 0; i < caseBlocks.length; i++) {
179            if (i === caseBlocks.length - 1) {
180                this.isSwitchLastCase = true;
181            }
182            this.blockProcess(caseBlocks[i], this.genChildScope(curScope, ScopeType.CASE_TYPE));
183        }
184        this.isSwitchLastCase = false;
185    }
186
187    private nextBlockPreProcess(block: BasicBlock, curScope: Scope, nextScopeType: ScopeType): void {
188        const succBlocks = block.getSuccessors();
189        const proedBlocks = block.getPredecessors();
190        for (let i = 0; i < succBlocks.length; i++) {
191            if (this.finishBlockSet.has(succBlocks[i])) {
192                continue;
193            }
194            if (this.isTernaryCondition(succBlocks[i], proedBlocks)) {
195                this.blockProcess(succBlocks[i], curScope);
196                continue;
197            }
198            this.handleSuccessorBlock(succBlocks[i], curScope, nextScopeType, i);
199        }
200    }
201
202    private handleSuccessorBlock(succBlock: BasicBlock, curScope: Scope, nextScopeType: ScopeType, index: number): void {
203        if (index === 0) {
204            if (this.isNeedCreateScope(nextScopeType)) {
205                const type = (nextScopeType === ScopeType.FOR_CONDITION_TYPE) ? ScopeType.FOR_IN_TYPE : nextScopeType;
206                this.blockProcess(succBlock, this.genChildScope(curScope, type));
207            } else {
208                if (this.isSwitchLastCase) {
209                    this.isSwitchLastCase = false;
210                    curScope = curScope.parentScope ?? curScope;
211                }
212                this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope));
213            }
214        } else {
215            if (nextScopeType === ScopeType.FOR_CONDITION_TYPE) {
216                this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope.parentScope ?? curScope));
217            } else if (nextScopeType === ScopeType.WHILE_TYPE) {
218                this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope));
219            } else {
220                this.blockProcess(succBlock, this.genChildScope(curScope, ScopeType.ELSE_TYPE));
221            }
222        }
223    }
224
225    private isTernaryCondition(succBlock: BasicBlock, predBlocks: BasicBlock[]): boolean {
226        const succStmts = succBlock.getStmts();
227        if (succStmts.length > 0 && this.gTernaryConditionLines.has(succStmts[0].getOriginPositionInfo().getLineNo())) {
228            return true;
229        } else if (predBlocks.length === 1 && this.gTernaryConditionLines.has(predBlocks?.[0].getStmts()?.at(-1)?.getOriginPositionInfo().getLineNo() ?? 0)) {
230            return true;
231        } else {
232            return false;
233        }
234    }
235
236    private isNeedCreateScope(nextScopeType: ScopeType): boolean {
237        if (nextScopeType === ScopeType.IF_TYPE ||
238            nextScopeType === ScopeType.FOR_CONDITION_TYPE ||
239            nextScopeType === ScopeType.WHILE_TYPE) {
240            return true;
241        }
242        return false;
243    }
244
245    private getReturnScope(succBlock: BasicBlock, curScope: Scope): Scope {
246        const stmts = succBlock.getStmts();
247        if (stmts.length !== 0) {
248            const type = CheckerUtils.getScopeType(stmts[0]);
249            if ([ScopeType.WHILE_TYPE, ScopeType.FOR_CONDITION_TYPE].includes(type)) {
250                return curScope;
251            }
252        } else {
253            return curScope;
254        }
255        let returnScopeLevel = curScope.scopeLevel - (succBlock.getPredecessors().length - 1);
256        let exitNum = curScope.scopeLevel - returnScopeLevel;
257        while (exitNum) {
258            if (curScope.parentScope !== null) {
259                curScope = curScope.parentScope;
260                exitNum--;
261            } else {
262                logger.debug('CountExitScopeNum error!');
263                break;
264            }
265        }
266        return curScope;
267    }
268}
269
270enum SetDefMode {
271    REDEF = 0,
272    LEFTUSED
273}