• 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 */
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