• 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() {
85        return this.labelPairs;
86    }
87
88    getCatchBeginLabel() {
89        return this.catchBeginLabel;
90    }
91
92    getDepth() {
93        return this.depth;
94    }
95
96    // split the last labelPair after inline finally.
97    splitLabelPair(inlinedLabelPair: LabelPair) {
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() {
143        TryStatement.currentTryStatementDepth--;
144        TryStatement.currentTryStatement = this.outer;
145    }
146
147    static setCurrentTryStatement(tryStatement: TryStatement | undefined) {
148        TryStatement.currentTryStatement = tryStatement;
149    }
150
151    static getCurrentTryStatement() {
152        return TryStatement.currentTryStatement;
153    }
154
155    static getcurrentTryStatementDepth() {
156        return TryStatement.currentTryStatementDepth;
157    }
158
159    getOuterTryStatement() {
160        return this.outer;
161    }
162
163    getStatement() {
164        return this.stmt;
165    }
166
167    getCatchTable() {
168        return this.catchTable;
169    }
170
171    getLoopEnvLevel() {
172        return this.loopEnvLevel;
173    }
174
175    increaseLoopEnvLevel() {
176        this.loopEnvLevel += 1;
177    }
178
179    decreaseLoopEnvLevel() {
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) {
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() {
224        if ((<ts.TryStatement>this.stmt).finallyBlock) {
225            this.compiler.compileStatement((<ts.TryStatement>this.stmt).finallyBlock!);
226        }
227    }
228
229    compileExceptionHandler() {
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) {
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, labelTarget: LabelTarget, hasLoopEnv: boolean, loopEnv?: VReg) {
264        super(compiler, pandaGen, forOfStmt);
265
266        this.labelTarget = labelTarget;
267        this.doneReg = doneReg;
268        this.iterator = iterator;
269        this.hasLoopEnv = hasLoopEnv;
270        this.loopEnv = loopEnv ? loopEnv : undefined;
271    }
272
273    compileTryBlock(catchTable: CatchTable) {
274        let stmt = <ts.ForOfStatement>this.stmt;
275        let compiler = <Compiler>this.compiler;
276        let pandaGen = this.pandaGen;
277        this.tryStatement = new TryStatement(stmt, catchTable, this);
278
279        let resultReg = this.pandaGen.getTemp();
280        let isDeclaration: boolean = false;
281
282        let loopScope = <LoopScope>compiler.getRecorder().getScopeOfNode(stmt);
283
284        pandaGen.loadAccumulator(stmt, getVregisterCache(pandaGen, CacheList.True));
285        pandaGen.storeAccumulator(stmt, this.doneReg);
286
287        pandaGen.label(stmt, this.labelTarget.getContinueTargetLabel()!);
288        if (this.hasLoopEnv) {
289            pandaGen.createLexEnv(stmt, loopScope);
290        }
291
292        this.compileIteratorNext(stmt, pandaGen, this.iterator, resultReg);
293
294        pandaGen.loadObjProperty(stmt, resultReg, "done");
295        pandaGen.jumpIfTrue(stmt, this.labelTarget.getBreakTargetLabel());
296
297        pandaGen.loadObjProperty(stmt, resultReg, "value");
298        pandaGen.storeAccumulator(stmt, resultReg);
299
300        pandaGen.loadAccumulator(stmt, getVregisterCache(pandaGen, CacheList.False));
301        pandaGen.storeAccumulator(stmt, this.doneReg);
302
303        let lref = LReference.generateLReference(this.compiler, stmt.initializer, isDeclaration);
304        pandaGen.loadAccumulator(stmt, resultReg);
305        lref.setValue();
306
307        this.compiler.compileStatement(stmt.statement);
308        this.tryStatement.destroy();
309        pandaGen.freeTemps(resultReg);
310    }
311
312    compileFinallyBlockIfExisted() { }
313
314    compileExceptionHandler() {
315        let pandaGen = this.pandaGen;
316        let noReturn = new Label();
317        let exceptionVreg = pandaGen.getTemp();
318        pandaGen.storeAccumulator(this.stmt, exceptionVreg);
319
320        pandaGen.loadAccumulator(this.stmt, this.doneReg);
321        pandaGen.condition(
322            (<ts.ForOfStatement>this.stmt).expression,
323            ts.SyntaxKind.ExclamationEqualsEqualsToken,
324            getVregisterCache(pandaGen, CacheList.True),
325            noReturn);
326        // Iterator Close
327        pandaGen.loadObjProperty(this.stmt, this.iterator.getObject(), "return");
328        pandaGen.storeAccumulator(this.stmt, this.iterator.getNextMethod());
329        pandaGen.condition(this.stmt, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(pandaGen, CacheList.undefined), noReturn);
330        pandaGen.call(this.stmt, [this.iterator.getNextMethod(), this.iterator.getObject()], true);
331        if (this.iterator.getType() == IteratorType.Async) {
332            (<AsyncGeneratorFunctionBuilder>(this.compiler.getFuncBuilder())).await(this.stmt);
333        }
334
335        pandaGen.label(this.stmt, noReturn);
336        pandaGen.loadAccumulator(this.stmt, exceptionVreg);
337        pandaGen.throw(this.stmt);
338
339        pandaGen.freeTemps(exceptionVreg);
340    }
341
342    compileFinalizer(cfc: ControlFlowChange, continueTargetLabel: Label) {
343        if (cfc == ControlFlowChange.Break || continueTargetLabel != this.labelTarget.getContinueTargetLabel()) {
344            let noReturn = new Label();
345            let innerResult = this.pandaGen.getTemp();
346            this.pandaGen.loadObjProperty(this.stmt, this.iterator.getObject(), "return");
347            this.pandaGen.storeAccumulator(this.stmt, this.iterator.getNextMethod());
348            this.pandaGen.condition(this.stmt, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(this.pandaGen, CacheList.undefined), noReturn);
349            this.pandaGen.call(this.stmt, [this.iterator.getNextMethod(), this.iterator.getObject()], true);
350
351            this.pandaGen.storeAccumulator(this.stmt, innerResult);
352            this.pandaGen.throwIfNotObject(this.stmt, innerResult);
353
354            this.pandaGen.label(this.stmt, noReturn);
355            this.pandaGen.freeTemps(innerResult);
356        }
357    }
358
359    private compileIteratorNext(stmt: ts.ForOfStatement, pandagen: PandaGen, iterator: IteratorRecord, resultObj: VReg) {
360        pandagen.call(stmt, [iterator.getNextMethod(), iterator.getObject()], true);
361        if (iterator.getType() == IteratorType.Async) {
362            (<AsyncGeneratorFunctionBuilder>(this.compiler.getFuncBuilder())).await(this.stmt);
363        }
364        pandagen.storeAccumulator(stmt, resultObj);
365        pandagen.throwIfNotObject(stmt, resultObj);
366    }
367}
368
369function compileCatchClauseVariableDeclaration(compiler: Compiler, param: ts.VariableDeclaration | undefined) {
370    if (param) {
371        compiler.compileVariableDeclaration(param);
372    }
373}
374
375export function updateCatchTables(inlinedTry: TryStatement | undefined, targetTry: TryStatement, inlinedLabelPair: LabelPair) {
376    for (; inlinedTry != targetTry; inlinedTry = inlinedTry?.getOuterTryStatement()) {
377        inlinedTry!.getCatchTable().splitLabelPair(inlinedLabelPair);
378    }
379    targetTry.getCatchTable().splitLabelPair(inlinedLabelPair);
380}
381
382export function generateCatchTables(catchMap: Map<Label, CatchTable>): CatchTable[] {
383    let catchTableList: CatchTable[] = [];
384    catchMap.forEach((catchTable) => {
385        catchTableList.push(catchTable)
386    });
387    catchTableList.sort((a, b) => (b.getDepth() - a.getDepth()));
388    return catchTableList;
389}
390