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