• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 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 { Compiler, ControlFlowChange } from "../compiler";
17import { Label, VReg } from "../irnodes";
18import { PandaGen } from "../pandagen";
19import * as ts from "typescript";
20import { Recorder } from "../recorder";
21import { LocalScope, LoopScope } from "../scope";
22import { LReference } from "../base/lreference";
23import { LabelTarget } from "./labelTarget";
24import {
25    CacheList,
26    getVregisterCache
27} from "../base/vregisterCache";
28import { IteratorRecord, IteratorType } from "./forOfStatement";
29import * as jshelpers from "../jshelpers";
30import { AsyncGeneratorFunctionBuilder } from "src/function/asyncGeneratorFunctionBuilder";
31
32// adjust the try...catch...finally into nested try(try...catch) finally
33export function transformTryCatchFinally(tryStmt: ts.TryStatement, recorder: Recorder): ts.TryStatement {
34    // after create new try block node, mapped with new scope, and adjust parent node
35    let tryStmtScope = <LocalScope>recorder.getScopeOfNode(tryStmt);
36    let newTryBlockScope = new LocalScope(tryStmtScope);
37    let newTryStmtScope = new LocalScope(newTryBlockScope);
38    (<LocalScope>recorder.getScopeOfNode(tryStmt.tryBlock)).setParent(newTryStmtScope);
39    (<LocalScope>recorder.getScopeOfNode(tryStmt.catchClause!)).setParent(newTryStmtScope);
40
41    const newTryStmt = ts.factory.createTryStatement(tryStmt.tryBlock, tryStmt.catchClause, undefined);
42    recorder.setScopeMap(newTryStmt, newTryStmtScope);
43
44    const newTryBlock = [newTryStmt];
45    newTryBlock[0] = jshelpers.setParent(newTryBlock[0], tryStmt)!;
46    newTryBlock[0] = ts.setTextRange(newTryBlock[0], tryStmt.tryBlock)!;
47    let tryBlock = ts.factory.updateBlock(tryStmt.tryBlock, newTryBlock);
48    tryStmt = ts.factory.updateTryStatement(tryStmt, tryBlock, undefined, tryStmt.finallyBlock);
49    recorder.setScopeMap(tryStmt.tryBlock, newTryBlockScope);
50    return tryStmt;
51}
52
53export class LabelPair {
54    private beginLabel: Label;
55    private endLabel: Label;
56
57    constructor(beginLabel: Label, endLabel: Label) {
58        this.beginLabel = beginLabel;
59        this.endLabel = endLabel;
60    }
61
62    getBeginLabel() {
63        return this.beginLabel;
64    }
65
66    getEndLabel() {
67        return this.endLabel;
68    }
69}
70
71export class CatchTable {
72    private labelPairs: LabelPair[] = [];
73    private catchBeginLabel: Label;
74    private depth: number;
75
76    constructor(pandaGen: PandaGen, catchBeginLabel: Label, labelPair: LabelPair) {
77        this.catchBeginLabel = catchBeginLabel;
78        this.labelPairs.push(labelPair);
79        this.depth = TryStatement.getcurrentTryStatementDepth();
80
81        pandaGen.getCatchMap().set(catchBeginLabel, this);
82    }
83
84    getLabelPairs(): LabelPair[] {
85        return this.labelPairs;
86    }
87
88    getCatchBeginLabel(): Label {
89        return this.catchBeginLabel;
90    }
91
92    getDepth(): number {
93        return this.depth;
94    }
95
96    // split the last labelPair after inline finally.
97    splitLabelPair(inlinedLabelPair: LabelPair): void {
98        let lastLabelPair = this.labelPairs.pop();
99        if (lastLabelPair) {
100            this.labelPairs.push(new LabelPair(lastLabelPair.getBeginLabel(), inlinedLabelPair.getBeginLabel()));
101            this.labelPairs.push(new LabelPair(inlinedLabelPair.getEndLabel(), lastLabelPair.getEndLabel()));
102        }
103    }
104}
105
106// record the info of the tryStatement
107export class TryStatement {
108    private static currentTryStatement: TryStatement | undefined;
109    private static currentTryStatementDepth: number = 0;
110    private outer: TryStatement | undefined;
111    private stmt: ts.Statement;
112    private catchTable: CatchTable;
113    private loopEnvLevel: number = 0;
114    trybuilder: TryBuilderBase | undefined;
115
116    constructor(stmt: ts.Statement, catchTable: CatchTable, trybuilder?: TryBuilderBase) {
117        TryStatement.currentTryStatementDepth++;
118        this.outer = TryStatement.currentTryStatement;
119        /*
120         * split the outer TryStatment's try block
121         * OuterTryBegin      ----        OuterTryBegin         ----
122         *               outerTry |                        outerTry |
123         *     InnerTryBegin --   |           InnerTryBegin --  ----
124         *            innerTry |  |  ==>             innerTry |
125         *     InnerTryEnd   --   |           InnerTryEnd   --  ----
126         *                        |                        outerTry |
127         * OuterTryEnd        ----        OuterTryEnd           ----
128         */
129        if (this.outer) {
130            let labelPairs: LabelPair[] = catchTable.getLabelPairs();
131            this.outer.catchTable.splitLabelPair(labelPairs[labelPairs.length - 1]);
132        }
133        this.stmt = stmt;
134        this.catchTable = catchTable;
135        if (trybuilder) {
136            this.trybuilder = trybuilder;
137        }
138
139        TryStatement.currentTryStatement = this;
140    }
141
142    destroy(): void {
143        TryStatement.currentTryStatementDepth--;
144        TryStatement.currentTryStatement = this.outer;
145    }
146
147    static setCurrentTryStatement(tryStatement: TryStatement | undefined): void {
148        TryStatement.currentTryStatement = tryStatement;
149    }
150
151    static getCurrentTryStatement(): TryStatement {
152        return TryStatement.currentTryStatement;
153    }
154
155    static getcurrentTryStatementDepth(): number {
156        return TryStatement.currentTryStatementDepth;
157    }
158
159    getOuterTryStatement(): TryStatement {
160        return this.outer;
161    }
162
163    getStatement(): ts.Statement {
164        return this.stmt;
165    }
166
167    getCatchTable(): CatchTable {
168        return this.catchTable;
169    }
170
171    getLoopEnvLevel(): number {
172        return this.loopEnvLevel;
173    }
174
175    increaseLoopEnvLevel(): void {
176        this.loopEnvLevel += 1;
177    }
178
179    decreaseLoopEnvLevel(): void {
180        this.loopEnvLevel -= 1;
181    }
182}
183
184export abstract class TryBuilderBase {
185    protected compiler: Compiler;
186    protected pandaGen: PandaGen;
187    protected stmt: ts.Statement;
188    protected tryStatement: TryStatement | undefined;
189
190    constructor(compiler: Compiler, pandaGen: PandaGen, Stmt: ts.Statement) {
191        this.compiler = compiler;
192        this.pandaGen = pandaGen;
193        this.stmt = Stmt;
194    }
195
196    abstract compileTryBlock(catchTable: CatchTable): void;
197    abstract compileFinallyBlockIfExisted(): void;
198    abstract compileExceptionHandler(): void;
199    abstract compileFinalizer(cfc: ControlFlowChange, continueTargetLabel: Label | undefined): void;
200}
201
202// generate the bytecode for TryStatement
203// compiler just handle the basic controlFlow
204export class TryBuilder extends TryBuilderBase {
205
206    constructor(compiler: Compiler, pandaGen: PandaGen, tryStmt: ts.TryStatement) {
207        super(compiler, pandaGen, tryStmt);
208    }
209
210    compileTryBlock(catchTable: CatchTable): void {
211        if ((<ts.TryStatement>this.stmt).finallyBlock) {
212            this.tryStatement = new TryStatement(this.stmt, catchTable, this);
213        } else {
214            this.tryStatement = new TryStatement(this.stmt, catchTable);
215        }
216
217        this.compiler.compileStatement((<ts.TryStatement>this.stmt).tryBlock);
218
219        // when compiler is out of TryBlock, reset tryStatement
220        this.tryStatement.destroy();
221    }
222
223    compileFinallyBlockIfExisted(): void {
224        if ((<ts.TryStatement>this.stmt).finallyBlock) {
225            this.compiler.compileStatement((<ts.TryStatement>this.stmt).finallyBlock!);
226        }
227    }
228
229    compileExceptionHandler(): void {
230        let catchClause = (<ts.TryStatement>this.stmt).catchClause;
231        if (catchClause) {
232            this.compiler.pushScope(catchClause);
233            compileCatchClauseVariableDeclaration(this.compiler, catchClause.variableDeclaration);
234            let catchBlock = catchClause.block;
235            this.compiler.pushScope(catchBlock);
236            catchBlock.statements.forEach((stmt) => this.compiler.compileStatement(stmt));
237            this.compiler.popScope();
238            this.compiler.popScope();
239        } else {
240            // finallyBlock rethrow exception when it catch the exception
241            let exceptionVreg = this.pandaGen.getTemp();
242            this.pandaGen.storeAccumulator(this.stmt, exceptionVreg);
243            this.compiler.compileStatement((<ts.TryStatement>this.stmt).finallyBlock!);
244            this.pandaGen.loadAccumulator(this.stmt, exceptionVreg);
245            this.pandaGen.throw(this.stmt);
246            this.pandaGen.freeTemps(exceptionVreg);
247        }
248    }
249
250    // @ts-ignore
251    compileFinalizer(cfc: ControlFlowChange, continueTargetLabel: Label): void {
252        this.compiler.compileStatement((<ts.TryStatement>this.stmt).finallyBlock!);
253    }
254}
255
256export class TryBuilderWithForOf extends TryBuilderBase {
257    private labelTarget: LabelTarget;
258    private doneReg: VReg;
259    private iterator: IteratorRecord;
260    private hasLoopEnv: boolean;
261    private loopEnv: VReg | undefined;
262
263    constructor(compiler: Compiler, pandaGen: PandaGen, forOfStmt: ts.ForOfStatement, doneReg: VReg, iterator: IteratorRecord,
264                labelTarget: LabelTarget, hasLoopEnv: boolean, loopEnv?: VReg) {
265        super(compiler, pandaGen, forOfStmt);
266
267        this.labelTarget = labelTarget;
268        this.doneReg = doneReg;
269        this.iterator = iterator;
270        this.hasLoopEnv = hasLoopEnv;
271        this.loopEnv = loopEnv ? loopEnv : undefined;
272    }
273
274    compileTryBlock(catchTable: CatchTable): void {
275        let stmt = <ts.ForOfStatement>this.stmt;
276        let compiler = <Compiler>this.compiler;
277        let pandaGen = this.pandaGen;
278        this.tryStatement = new TryStatement(stmt, catchTable, this);
279
280        let resultReg = this.pandaGen.getTemp();
281        let isDeclaration: boolean = false;
282
283        let loopScope = <LoopScope>compiler.getRecorder().getScopeOfNode(stmt);
284
285        pandaGen.loadAccumulator(stmt, getVregisterCache(pandaGen, CacheList.TRUE));
286        pandaGen.storeAccumulator(stmt, this.doneReg);
287
288        pandaGen.label(stmt, this.labelTarget.getContinueTargetLabel()!);
289        if (this.hasLoopEnv) {
290            pandaGen.createLexEnv(stmt, loopScope);
291        }
292
293        this.compileIteratorNext(stmt, pandaGen, this.iterator, resultReg);
294
295        pandaGen.loadObjProperty(stmt, resultReg, "done");
296        pandaGen.jumpIfTrue(stmt, this.labelTarget.getBreakTargetLabel());
297
298        pandaGen.loadObjProperty(stmt, resultReg, "value");
299        pandaGen.storeAccumulator(stmt, resultReg);
300
301        pandaGen.loadAccumulator(stmt, getVregisterCache(pandaGen, CacheList.FALSE));
302        pandaGen.storeAccumulator(stmt, this.doneReg);
303
304        let lref = LReference.generateLReference(this.compiler, stmt.initializer, isDeclaration);
305        pandaGen.loadAccumulator(stmt, resultReg);
306        lref.setValue();
307
308        this.compiler.compileStatement(stmt.statement);
309        this.tryStatement.destroy();
310        pandaGen.freeTemps(resultReg);
311    }
312
313    compileFinallyBlockIfExisted(): void { }
314
315    compileExceptionHandler(): void {
316        let pandaGen = this.pandaGen;
317        let noReturn = new Label();
318        let exceptionVreg = pandaGen.getTemp();
319        pandaGen.storeAccumulator(this.stmt, exceptionVreg);
320
321        pandaGen.loadAccumulator(this.stmt, this.doneReg);
322        pandaGen.condition(
323            (<ts.ForOfStatement>this.stmt).expression,
324            ts.SyntaxKind.ExclamationEqualsEqualsToken,
325            getVregisterCache(pandaGen, CacheList.TRUE),
326            noReturn);
327        // Iterator Close
328        pandaGen.loadObjProperty(this.stmt, this.iterator.getObject(), "return");
329        pandaGen.storeAccumulator(this.stmt, this.iterator.getNextMethod());
330        pandaGen.condition(this.stmt, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(pandaGen, CacheList.UNDEFINED), noReturn);
331        pandaGen.call(this.stmt, [this.iterator.getNextMethod(), this.iterator.getObject()], true);
332        if (this.iterator.getType() === IteratorType.Async) {
333            (<AsyncGeneratorFunctionBuilder>(this.compiler.getFuncBuilder())).await(this.stmt);
334        }
335
336        pandaGen.label(this.stmt, noReturn);
337        pandaGen.loadAccumulator(this.stmt, exceptionVreg);
338        pandaGen.throw(this.stmt);
339
340        pandaGen.freeTemps(exceptionVreg);
341    }
342
343    compileFinalizer(cfc: ControlFlowChange, continueTargetLabel: Label): void {
344        if (cfc === ControlFlowChange.Break || continueTargetLabel != this.labelTarget.getContinueTargetLabel()) {
345            let noReturn = new Label();
346            let innerResult = this.pandaGen.getTemp();
347            this.pandaGen.loadObjProperty(this.stmt, this.iterator.getObject(), "return");
348            this.pandaGen.storeAccumulator(this.stmt, this.iterator.getNextMethod());
349            this.pandaGen.condition(this.stmt, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(this.pandaGen, CacheList.UNDEFINED), noReturn);
350            this.pandaGen.call(this.stmt, [this.iterator.getNextMethod(), this.iterator.getObject()], true);
351
352            this.pandaGen.storeAccumulator(this.stmt, innerResult);
353            this.pandaGen.throwIfNotObject(this.stmt, innerResult);
354
355            this.pandaGen.label(this.stmt, noReturn);
356            this.pandaGen.freeTemps(innerResult);
357        }
358    }
359
360    private compileIteratorNext(stmt: ts.ForOfStatement, pandagen: PandaGen, iterator: IteratorRecord, resultObj: VReg): void {
361        pandagen.call(stmt, [iterator.getNextMethod(), iterator.getObject()], true);
362        if (iterator.getType() === IteratorType.Async) {
363            (<AsyncGeneratorFunctionBuilder>(this.compiler.getFuncBuilder())).await(this.stmt);
364        }
365        pandagen.storeAccumulator(stmt, resultObj);
366        pandagen.throwIfNotObject(stmt, resultObj);
367    }
368}
369
370function compileCatchClauseVariableDeclaration(compiler: Compiler, param: ts.VariableDeclaration | undefined): void {
371    if (param) {
372        compiler.compileVariableDeclaration(param);
373    }
374}
375
376export function updateCatchTables(inlinedTry: TryStatement | undefined, targetTry: TryStatement, inlinedLabelPair: LabelPair): void {
377    for (; inlinedTry != targetTry; inlinedTry = inlinedTry?.getOuterTryStatement()) {
378        inlinedTry!.getCatchTable().splitLabelPair(inlinedLabelPair);
379    }
380    targetTry.getCatchTable().splitLabelPair(inlinedLabelPair);
381}
382
383export function generateCatchTables(catchMap: Map<Label, CatchTable>): CatchTable[] {
384    let catchTableList: CatchTable[] = [];
385    catchMap.forEach((catchTable) => {
386        catchTableList.push(catchTable);
387    });
388    catchTableList.sort((a, b) => (b.getDepth() - a.getDepth()));
389    return catchTableList;
390}
391