* Copyright (c) 2024 - 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ArkArrayRef, ArkAssignStmt, ArkClass, ArkIfStmt, ArkThisRef, BasicBlock, DEFAULT_ARK_METHOD_NAME, Local, Scene, Stmt } from 'arkanalyzer';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { CheckerStorage, CheckerUtils, Scope, ScopeType, TempLocation } from '../../Index';
import { Variable } from '../../model/Variable';
import { VarInfo } from '../../model/VarInfo';
import { FixUtils } from './FixUtils';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ScopeHelper');
export class ScopeHelper {
private gFilePath: string = '';
private firstBlock: BasicBlock | undefined;
private finishBlockSet: Set<BasicBlock>;
private isSwitchLastCase = false;
private gFinishIfStmtLines: number[] = [];
private gTernaryConditionLines: Set<number> = new Set();
public buildScope(scene: Scene): void {
let scopeMap = new Map<string, Scope>();
for (const file of scene.getFiles()) {
this.gFilePath = file.getFilePath();
const firstScope = new Scope(null, new Array<Variable>(), 0);
scopeMap.set(this.gFilePath, firstScope);
for (const clazz of file.getClasses()) {
this.createScopeInClass(clazz, firstScope);
}
}
CheckerStorage.getInstance().setScopeMap(scopeMap);
}
private createScopeInClass(clazz: ArkClass, firstScope: Scope): void {
for (let method of clazz.getMethods()) {
this.gFinishIfStmtLines = [];
this.gTernaryConditionLines.clear();
this.finishBlockSet = new Set();
this.firstBlock = method.getBody()?.getCfg()?.getStartingBlock();
if (!this.firstBlock) {
logger.trace(`${clazz.getName()}::${method.getName()} has no body.`);
continue;
}
let curScope = firstScope;
if (method.getName() !== DEFAULT_ARK_METHOD_NAME) {
curScope = new Scope(firstScope, new Array<Variable>(), 1);
firstScope.setChildScope(curScope);
}
this.blockProcess(this.firstBlock, curScope);
}
}
private blockProcess(block: BasicBlock, parentScope: Scope): void {
let curScope = parentScope;
let stmts = block.getStmts();
if (stmts.length === 0) {
return;
}
if (this.isFirstThisBlock(block)) {
const succBlocks = block.getSuccessors();
if (succBlocks.length > 0) {
this.blockProcess(block.getSuccessors()[0], curScope);
return;
}
}
let isSwitchBlock = false;
let nextScopeType = CheckerUtils.getScopeType(stmts[stmts.length - 1]);
curScope.blocks.add(block);
this.finishBlockSet.add(block);
for (let i = 0; i < stmts.length; i++) {
const stmt = stmts[i];
if ((i === stmts.length - 1) && (this.isForStmtDefinedPart(stmts[stmts.length - 1], nextScopeType))) {
curScope = this.genChildScope(curScope, ScopeType.FOR_CONDITION_TYPE);
nextScopeType = ScopeType.UNKNOWN_TYPE;
}
if (!FixUtils.hasOwnPropertyOwn(stmt, 'scope')) {
Object.defineProperty(stmt, 'scope', { value: curScope });
}
if (stmt instanceof ArkAssignStmt && !this.assignStmtProcess(stmt, curScope)) {
continue;
} else if (stmt instanceof ArkIfStmt) {
this.gFinishIfStmtLines.push(stmt.getOriginPositionInfo().getLineNo());
if (/^.*\?.*:.*$/.test(stmt.getOriginalText() ?? '')) {
this.gTernaryConditionLines.add(stmt.getOriginPositionInfo().getLineNo());
}
}
}
if (isSwitchBlock) {
this.switchBlockPreProcess(block, curScope);
} else {
this.nextBlockPreProcess(block, curScope, nextScopeType);
}
}
private isFirstThisBlock(block: BasicBlock): boolean {
if (block.getPredecessors().length === 0) {
const stmts = block.getStmts();
if (stmts.length === 1) {
if (stmts[0] instanceof ArkAssignStmt &&
stmts[0].getRightOp() instanceof ArkThisRef) {
return true;
}
}
}
return false;
}
private isForStmtDefinedPart(stmt: Stmt, nextScopeType: ScopeType): boolean {
if (stmt instanceof ArkAssignStmt && nextScopeType === ScopeType.FOR_CONDITION_TYPE &&
!this.gFinishIfStmtLines.includes(stmt.getOriginPositionInfo().getLineNo())) {
return true;
}
return false;
}
private genChildScope(curScope: Scope, scopeType: ScopeType): Scope {
let newScope = new Scope(curScope, new Array<Variable>(), curScope.scopeLevel + 1, scopeType);
curScope.setChildScope(newScope);
return newScope;
}
private assignStmtProcess(stmt: Stmt, curScope: Scope): boolean {
let def = stmt.getDef();
if (def instanceof Local) {
if (def.getName() === 'this') {
return false;
}
const isForStmtThirdPart = (CheckerUtils.getScopeType(stmt) === ScopeType.FOR_CONDITION_TYPE &&
this.gFinishIfStmtLines.includes(stmt.getOriginPositionInfo().getLineNo()));
if (CheckerUtils.wherIsTemp(stmt) === TempLocation.LEFT ||
(!isForStmtThirdPart && CheckerUtils.isDeclaringStmt(def.getName(), stmt))) {
curScope.addVariable(new Variable(stmt));
} else {
this.getDefAndSetRedef(def.getName(), curScope, curScope, stmt, SetDefMode.REDEF);
}
} else if (def instanceof ArkArrayRef) {
let base = def.getBase();
if (base instanceof Local && !base.getName().includes('%')) {
this.getDefAndSetRedef(base.getName(), curScope, curScope, stmt, SetDefMode.LEFTUSED);
}
}
return true;
}
private getDefAndSetRedef(name: string, searchScope: Scope, varScope: Scope, stmt: Stmt, mode: SetDefMode): boolean {
let defList = searchScope.defList;
for (let variable of defList) {
if (variable.getName() === name) {
if (mode === SetDefMode.REDEF) {
variable.redefInfo.add(new VarInfo(stmt, varScope));
} else if (mode === SetDefMode.LEFTUSED) {
variable.leftUsedInfo.add(new VarInfo(stmt, varScope));
}
}
}
if (searchScope.parentScope !== null) {
return this.getDefAndSetRedef(name, searchScope.parentScope, varScope, stmt, mode);
}
return false;
}
private switchBlockPreProcess(block: BasicBlock, curScope: Scope): void {
const caseBlocks = block.getSuccessors();
for (let caseBlock of caseBlocks) {
this.finishBlockSet.add(caseBlock);
}
for (let i = 0; i < caseBlocks.length; i++) {
if (i === caseBlocks.length - 1) {
this.isSwitchLastCase = true;
}
this.blockProcess(caseBlocks[i], this.genChildScope(curScope, ScopeType.CASE_TYPE));
}
this.isSwitchLastCase = false;
}
private nextBlockPreProcess(block: BasicBlock, curScope: Scope, nextScopeType: ScopeType): void {
const succBlocks = block.getSuccessors();
const proedBlocks = block.getPredecessors();
for (let i = 0; i < succBlocks.length; i++) {
if (this.finishBlockSet.has(succBlocks[i])) {
continue;
}
if (this.isTernaryCondition(succBlocks[i], proedBlocks)) {
this.blockProcess(succBlocks[i], curScope);
continue;
}
this.handleSuccessorBlock(succBlocks[i], curScope, nextScopeType, i);
}
}
private handleSuccessorBlock(succBlock: BasicBlock, curScope: Scope, nextScopeType: ScopeType, index: number): void {
if (index === 0) {
if (this.isNeedCreateScope(nextScopeType)) {
const type = (nextScopeType === ScopeType.FOR_CONDITION_TYPE) ? ScopeType.FOR_IN_TYPE : nextScopeType;
this.blockProcess(succBlock, this.genChildScope(curScope, type));
} else {
if (this.isSwitchLastCase) {
this.isSwitchLastCase = false;
curScope = curScope.parentScope ?? curScope;
}
this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope));
}
} else {
if (nextScopeType === ScopeType.FOR_CONDITION_TYPE) {
this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope.parentScope ?? curScope));
} else if (nextScopeType === ScopeType.WHILE_TYPE) {
this.blockProcess(succBlock, this.getReturnScope(succBlock, curScope));
} else {
this.blockProcess(succBlock, this.genChildScope(curScope, ScopeType.ELSE_TYPE));
}
}
}
private isTernaryCondition(succBlock: BasicBlock, predBlocks: BasicBlock[]): boolean {
const succStmts = succBlock.getStmts();
if (succStmts.length > 0 && this.gTernaryConditionLines.has(succStmts[0].getOriginPositionInfo().getLineNo())) {
return true;
} else if (predBlocks.length === 1 && this.gTernaryConditionLines.has(predBlocks[0].getTail()?.getOriginPositionInfo().getLineNo() ?? 0)) {
return true;
} else {
return false;
}
}
private isNeedCreateScope(nextScopeType: ScopeType): boolean {
if (nextScopeType === ScopeType.IF_TYPE ||
nextScopeType === ScopeType.FOR_CONDITION_TYPE ||
nextScopeType === ScopeType.WHILE_TYPE) {
return true;
}
return false;
}
private getReturnScope(succBlock: BasicBlock, curScope: Scope): Scope {
const stmts = succBlock.getStmts();
if (stmts.length !== 0) {
const type = CheckerUtils.getScopeType(stmts[0]);
if ([ScopeType.WHILE_TYPE, ScopeType.FOR_CONDITION_TYPE].includes(type)) {
return curScope;
}
} else {
return curScope;
}
let returnScopeLevel = curScope.scopeLevel - (succBlock.getPredecessors().length - 1);
let exitNum = curScope.scopeLevel - returnScopeLevel;
while (exitNum) {
if (curScope.parentScope !== null) {
curScope = curScope.parentScope;
exitNum--;
} else {
logger.debug('CountExitScopeNum error!');
break;
}
}
return curScope;
}
}
enum SetDefMode {
REDEF = 0,
LEFTUSED
}