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 */ 15 16import { ArkInstanceInvokeExpr } from '../../core/base/Expr'; 17import { Local } from '../../core/base/Local'; 18import { ArkAssignStmt, ArkIfStmt, ArkInvokeStmt, Stmt } from '../../core/base/Stmt'; 19import { BasicBlock } from '../../core/graph/BasicBlock'; 20import { ArkBody } from '../../core/model/ArkBody'; 21import { ArkMethod } from '../../core/model/ArkMethod'; 22import Logger, { LOG_MODULE_TYPE } from '../../utils/logger'; 23import { ArkCodeBuffer } from '../ArkStream'; 24import { 25 SourceBreakStmt, 26 SourceCatchStmt, 27 SourceCompoundEndStmt, 28 SourceContinueStmt, 29 SourceDoStmt, 30 SourceDoWhileStmt, 31 SourceElseStmt, 32 SourceFinallyStmt, 33 SourceForStmt, 34 SourceIfStmt, 35 SourceStmt, 36 SourceTryStmt, 37 SourceWhileStmt, 38 stmt2SourceStmt, 39 StmtPrinterContext, 40} from './SourceStmt'; 41import { AbstractFlowGraph, CodeBlockType } from '../../utils/CfgStructualAnalysis'; 42import { ArkClass } from '../../core/model/ArkClass'; 43import { ArkFile } from '../../core/model/ArkFile'; 44import { ClassSignature, MethodSignature } from '../../core/model/ArkSignature'; 45import { ModelUtils } from '../../core/common/ModelUtils'; 46import { PrinterUtils } from '../base/PrinterUtils'; 47import { ArkNamespace } from '../../core/model/ArkNamespace'; 48 49const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'SourceBody'); 50 51export class SourceBody implements StmtPrinterContext { 52 protected printer: ArkCodeBuffer; 53 private arkBody: ArkBody; 54 private stmts: SourceStmt[] = []; 55 private method: ArkMethod; 56 private cfgUtils: AbstractFlowGraph; 57 private tempCodeMap: Map<string, string>; 58 private tempVisitor: Set<string>; 59 private skipStmts: Set<Stmt>; 60 private stmtReader: StmtReader; 61 private definedLocals: Set<Local>; 62 private inBuilder: boolean; 63 private lastStmt: Stmt; 64 65 public constructor(indent: string, method: ArkMethod, inBuilder: boolean) { 66 this.printer = new ArkCodeBuffer(indent); 67 this.method = method; 68 this.arkBody = method.getBody()!; 69 this.cfgUtils = new AbstractFlowGraph(method.getCfg()!, this.arkBody.getTraps()); 70 this.tempCodeMap = new Map(); 71 this.tempVisitor = new Set(); 72 this.definedLocals = new Set(); 73 this.inBuilder = inBuilder; 74 this.skipStmts = new Set(); 75 this.stmtReader = new StmtReader([]); 76 this.lastStmt = this.arkBody.getCfg().getStartingStmt(); 77 this.buildSourceStmt(); 78 } 79 setSkipStmt(stmt: Stmt): void { 80 this.skipStmts.add(stmt); 81 } 82 83 isInBuilderMethod(): boolean { 84 return this.inBuilder; 85 } 86 isInDefaultMethod(): boolean { 87 return this.method.isDefaultArkMethod(); 88 } 89 public getArkFile(): ArkFile { 90 return this.method.getDeclaringArkFile(); 91 } 92 93 public getDeclaringArkNamespace(): ArkNamespace | undefined { 94 return this.method.getDeclaringArkClass().getDeclaringArkNamespace(); 95 } 96 97 public getMethod(signature: MethodSignature): ArkMethod | null { 98 let method = this.method.getDeclaringArkFile().getScene().getMethod(signature); 99 if (method) { 100 return method; 101 } 102 return this.method.getDeclaringArkClass().getMethodWithName(signature.getMethodSubSignature().getMethodName()); 103 } 104 105 public getClass(signature: ClassSignature): ArkClass | null { 106 return ModelUtils.getClass(this.method, signature); 107 } 108 109 public getLocals(): Map<string, Local> { 110 return this.arkBody.getLocals(); 111 } 112 113 public defineLocal(local: Local): void { 114 this.definedLocals.add(local); 115 } 116 117 public isLocalDefined(local: Local): boolean { 118 return this.definedLocals.has(local); 119 } 120 121 public getStmtReader(): StmtReader { 122 return this.stmtReader; 123 } 124 125 public setTempCode(temp: string, code: string): void { 126 this.tempCodeMap.set(temp, code); 127 } 128 129 public transTemp2Code(temp: Local, isLeftOp: boolean = false): string { 130 // if the temp local is not the left op of ArkAssignStmt, it should get the actual text from tempCodeMap 131 if (!isLeftOp && this.tempCodeMap.has(temp.getName()) && PrinterUtils.isTemp(temp.getName())) { 132 this.tempVisitor.add(temp.getName()); 133 return this.tempCodeMap.get(temp.getName())!; 134 } 135 136 return temp.getName(); 137 } 138 139 public getTempCodeMap(): Map<string, string> { 140 return this.tempCodeMap; 141 } 142 143 public hasTempVisit(temp: string): boolean { 144 return this.tempVisitor.has(temp); 145 } 146 147 public setTempVisit(temp: string): void { 148 this.tempVisitor.add(temp); 149 } 150 151 public getPrinter(): ArkCodeBuffer { 152 return this.printer; 153 } 154 155 public dump(): string { 156 this.printStmts(); 157 return this.printer.toString(); 158 } 159 160 private buildSourceStmt(): void { 161 this.cfgUtils.preOrder(this.cfgUtils.getEntry(), (block, type) => { 162 this.buildBasicBlock(block, type); 163 }); 164 } 165 166 private buildBasicBlock(block: BasicBlock | undefined, type: CodeBlockType): void { 167 if (type === CodeBlockType.BREAK) { 168 this.pushStmt(new SourceBreakStmt(this, this.lastStmt)); 169 return; 170 } else if (type === CodeBlockType.CONTINUE) { 171 this.pushStmt(new SourceContinueStmt(this, this.lastStmt)); 172 } else if (type === CodeBlockType.COMPOUND_END) { 173 this.pushStmt(new SourceCompoundEndStmt(this, this.lastStmt, '}')); 174 } else if (type === CodeBlockType.ELSE) { 175 this.pushStmt(new SourceElseStmt(this, this.lastStmt)); 176 } else if (type === CodeBlockType.DO) { 177 this.pushStmt(new SourceDoStmt(this, this.lastStmt)); 178 } else if (type === CodeBlockType.TRY) { 179 this.pushStmt(new SourceTryStmt(this, this.lastStmt)); 180 } else if (type === CodeBlockType.CATCH) { 181 this.pushStmt(new SourceCatchStmt(this, this.lastStmt, block)); 182 // catch need read block first stmt, using return to void walk block twice. 183 return; 184 } else if (type === CodeBlockType.FINALLY) { 185 this.pushStmt(new SourceFinallyStmt(this, this.lastStmt)); 186 } 187 188 if (!block) { 189 return; 190 } 191 192 let originalStmts: Stmt[] = this.sortStmt(block.getStmts()); 193 this.stmtReader = new StmtReader(originalStmts); 194 while (this.stmtReader.hasNext()) { 195 let stmt = this.stmtReader.next(); 196 if (this.skipStmts.has(stmt)) { 197 continue; 198 } 199 if (stmt instanceof ArkIfStmt) { 200 if (type === CodeBlockType.IF) { 201 this.pushStmt(new SourceIfStmt(this, stmt)); 202 } else if (type === CodeBlockType.WHILE) { 203 this.pushStmt(new SourceWhileStmt(this, stmt, block)); 204 } else if (type === CodeBlockType.FOR) { 205 let inc = this.cfgUtils.getForIncBlock(block)!; 206 this.pushStmt(new SourceForStmt(this, stmt, block, inc)); 207 } else if (type === CodeBlockType.DO_WHILE) { 208 this.pushStmt(new SourceDoWhileStmt(this, stmt, block)); 209 } 210 } else { 211 this.pushStmt(stmt2SourceStmt(this, stmt)); 212 } 213 this.lastStmt = stmt; 214 } 215 } 216 217 private printStmts(): void { 218 for (let stmt of this.stmts) { 219 if (this.skipStmts.has(stmt.original)) { 220 continue; 221 } 222 this.printer.write(stmt.dump()); 223 } 224 } 225 226 public getStmts(): SourceStmt[] { 227 return this.stmts.filter(value => !this.skipStmts.has(value.original)); 228 } 229 230 public pushStmt(stmt: SourceStmt): void { 231 let lastLine = this.getLastLine(); 232 if (stmt.getLine() < lastLine) { 233 stmt.setLine(lastLine + 0.1); 234 } 235 stmt.transfer2ts(); 236 this.stmts.push(stmt); 237 } 238 239 private getLastLine(): number { 240 if (this.stmts.length > 0) { 241 return this.stmts[this.stmts.length - 1].getLine(); 242 } 243 244 return 0; 245 } 246 247 /* 248 * temp9 = new <>.<>(); temp10 = new Array<number>(3); 249 * temp10 = new Array<number>(3); temp10[0] = 'Cat'; 250 * temp10[0] = 'Cat'; ==> temp10[1] = 'Dog'; 251 * temp10[1] = 'Dog'; temp10[2] = 'Hamster'; 252 * temp10[2] = 'Hamster'; temp9 = new <>.<>(); 253 * temp9.constructor(temp10); temp9.constructor(temp10); 254 */ 255 private sortStmt(stmts: Stmt[]): Stmt[] { 256 for (let i = stmts.length - 1; i > 0; i--) { 257 if (stmts[i] instanceof ArkInvokeStmt && (stmts[i].getInvokeExpr() as ArkInstanceInvokeExpr)) { 258 let instanceInvokeExpr = stmts[i].getInvokeExpr() as ArkInstanceInvokeExpr; 259 if ('constructor' !== instanceInvokeExpr.getMethodSignature().getMethodSubSignature().getMethodName()) { 260 continue; 261 } 262 let localName = instanceInvokeExpr.getBase().getName(); 263 let newExprIdx = findNewExpr(i, localName); 264 if (newExprIdx >= 0 && newExprIdx < i - 1) { 265 moveStmt(i, newExprIdx); 266 } 267 } 268 } 269 return stmts; 270 271 function findNewExpr(constructorIdx: number, name: string): number { 272 for (let j = constructorIdx - 1; j >= 0; j--) { 273 if (!(stmts[j] instanceof ArkAssignStmt)) { 274 continue; 275 } 276 const leftOp = (stmts[j] as ArkAssignStmt).getLeftOp(); 277 if (leftOp instanceof Local && leftOp.getName() === name) { 278 return j; 279 } 280 } 281 return -1; 282 } 283 284 function moveStmt(constructorIdx: number, newExprIdx: number): void { 285 let back = stmts[newExprIdx]; 286 for (let i = newExprIdx; i < constructorIdx - 1; i++) { 287 stmts[i] = stmts[i + 1]; 288 } 289 stmts[constructorIdx - 1] = back; 290 } 291 } 292} 293 294export class StmtReader { 295 private stmts: Stmt[] = []; 296 private pos: number; 297 298 constructor(stmts: Stmt[]) { 299 this.stmts = stmts; 300 this.pos = 0; 301 } 302 303 first(): Stmt { 304 return this.stmts[0]; 305 } 306 307 hasNext(): boolean { 308 return this.pos < this.stmts.length; 309 } 310 311 next(): Stmt { 312 if (!this.hasNext()) { 313 logger.error('SourceBody: StmtReader->next No more stmt.'); 314 throw new Error('No more stmt.'); 315 } 316 let stmt = this.stmts[this.pos]; 317 this.pos++; 318 return stmt; 319 } 320 321 rollback(): void { 322 if (this.pos === 0) { 323 logger.error('SourceBody: StmtReader->rollback No more stmt to rollback.'); 324 throw new Error('No more stmt to rollback.'); 325 } 326 this.pos--; 327 } 328} 329