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}