54 import jdk.nashorn.internal.ir.FunctionNode.CompilationState; |
54 import jdk.nashorn.internal.ir.FunctionNode.CompilationState; |
55 import jdk.nashorn.internal.ir.IdentNode; |
55 import jdk.nashorn.internal.ir.IdentNode; |
56 import jdk.nashorn.internal.ir.IfNode; |
56 import jdk.nashorn.internal.ir.IfNode; |
57 import jdk.nashorn.internal.ir.IndexNode; |
57 import jdk.nashorn.internal.ir.IndexNode; |
58 import jdk.nashorn.internal.ir.JumpStatement; |
58 import jdk.nashorn.internal.ir.JumpStatement; |
|
59 import jdk.nashorn.internal.ir.JumpToInlinedFinally; |
59 import jdk.nashorn.internal.ir.LabelNode; |
60 import jdk.nashorn.internal.ir.LabelNode; |
60 import jdk.nashorn.internal.ir.LexicalContext; |
61 import jdk.nashorn.internal.ir.LexicalContext; |
61 import jdk.nashorn.internal.ir.LiteralNode; |
62 import jdk.nashorn.internal.ir.LiteralNode; |
|
63 import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode; |
62 import jdk.nashorn.internal.ir.LoopNode; |
64 import jdk.nashorn.internal.ir.LoopNode; |
63 import jdk.nashorn.internal.ir.Node; |
65 import jdk.nashorn.internal.ir.Node; |
64 import jdk.nashorn.internal.ir.ReturnNode; |
66 import jdk.nashorn.internal.ir.ReturnNode; |
65 import jdk.nashorn.internal.ir.RuntimeNode; |
67 import jdk.nashorn.internal.ir.RuntimeNode; |
66 import jdk.nashorn.internal.ir.Statement; |
68 import jdk.nashorn.internal.ir.Statement; |
365 private IdentNode compilerConstant(final CompilerConstants cc) { |
373 private IdentNode compilerConstant(final CompilerConstants cc) { |
366 final FunctionNode functionNode = lc.getCurrentFunction(); |
374 final FunctionNode functionNode = lc.getCurrentFunction(); |
367 return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); |
375 return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); |
368 } |
376 } |
369 |
377 |
370 private static boolean isTerminal(final List<Statement> statements) { |
378 private static boolean isTerminalFinally(final Block finallyBlock) { |
371 return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags(); |
379 return finallyBlock.getLastStatement().hasTerminalFlags(); |
372 } |
380 } |
373 |
381 |
374 /** |
382 /** |
375 * Splice finally code into all endpoints of a trynode |
383 * Splice finally code into all endpoints of a trynode |
376 * @param tryNode the try node |
384 * @param tryNode the try node |
377 * @param rethrows list of rethrowing throw nodes from synthetic catch blocks |
385 * @param rethrow the rethrowing throw nodes from the synthetic catch block |
378 * @param finallyBody the code in the original finally block |
386 * @param finallyBody the code in the original finally block |
379 * @return new try node after splicing finally code (same if nop) |
387 * @return new try node after splicing finally code (same if nop) |
380 */ |
388 */ |
381 private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) { |
389 private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) { |
382 assert tryNode.getFinallyBody() == null; |
390 assert tryNode.getFinallyBody() == null; |
383 |
391 |
|
392 final Block finallyBlock = createFinallyBlock(finallyBody); |
|
393 final ArrayList<Block> inlinedFinallies = new ArrayList<>(); |
|
394 final FunctionNode fn = lc.getCurrentFunction(); |
384 final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
395 final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
385 final List<Node> insideTry = new ArrayList<>(); |
|
386 |
|
387 @Override |
|
388 public boolean enterDefault(final Node node) { |
|
389 insideTry.add(node); |
|
390 return true; |
|
391 } |
|
392 |
396 |
393 @Override |
397 @Override |
394 public boolean enterFunctionNode(final FunctionNode functionNode) { |
398 public boolean enterFunctionNode(final FunctionNode functionNode) { |
395 // do not enter function nodes - finally code should not be inlined into them |
399 // do not enter function nodes - finally code should not be inlined into them |
396 return false; |
400 return false; |
397 } |
401 } |
398 |
402 |
399 @Override |
403 @Override |
400 public Node leaveThrowNode(final ThrowNode throwNode) { |
404 public Node leaveThrowNode(final ThrowNode throwNode) { |
401 if (rethrows.contains(throwNode)) { |
405 if (rethrow == throwNode) { |
402 final List<Statement> newStatements = copyFinally(finallyBody); |
406 return new BlockStatement(prependFinally(finallyBlock, throwNode)); |
403 if (!isTerminal(newStatements)) { |
|
404 newStatements.add(throwNode); |
|
405 } |
|
406 return BlockStatement.createReplacement(throwNode, newStatements); |
|
407 } |
407 } |
408 return throwNode; |
408 return throwNode; |
409 } |
409 } |
410 |
410 |
411 @Override |
411 @Override |
417 public Node leaveContinueNode(final ContinueNode continueNode) { |
417 public Node leaveContinueNode(final ContinueNode continueNode) { |
418 return leaveJumpStatement(continueNode); |
418 return leaveJumpStatement(continueNode); |
419 } |
419 } |
420 |
420 |
421 private Node leaveJumpStatement(final JumpStatement jump) { |
421 private Node leaveJumpStatement(final JumpStatement jump) { |
422 return copy(jump, (Node)jump.getTarget(Lower.this.lc)); |
422 // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and |
|
423 // continue are edited. JTIF nodes should not be changed, rather the surroundings of |
|
424 // break/continue/return that were moved into the inlined finally block itself will be changed. |
|
425 |
|
426 // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block. |
|
427 if (jump.getTarget(lc) == null) { |
|
428 return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump)); |
|
429 } |
|
430 return jump; |
423 } |
431 } |
424 |
432 |
425 @Override |
433 @Override |
426 public Node leaveReturnNode(final ReturnNode returnNode) { |
434 public Node leaveReturnNode(final ReturnNode returnNode) { |
427 final Expression expr = returnNode.getExpression(); |
435 final Expression expr = returnNode.getExpression(); |
428 final List<Statement> newStatements = new ArrayList<>(); |
436 if (isTerminalFinally(finallyBlock)) { |
429 |
437 if (expr == null) { |
430 final Expression resultNode; |
438 // Terminal finally; no return expression. |
431 if (expr != null) { |
439 return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)); |
432 //we need to evaluate the result of the return in case it is complex while |
440 } |
433 //still in the try block, store it in a result value and return it afterwards |
441 // Terminal finally; has a return expression. |
434 resultNode = new IdentNode(Lower.this.compilerConstant(RETURN)); |
442 final List<Statement> newStatements = new ArrayList<>(2); |
435 newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); |
443 final int retLineNumber = returnNode.getLineNumber(); |
|
444 final long retToken = returnNode.getToken(); |
|
445 // Expression is evaluated for side effects. |
|
446 newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr)); |
|
447 newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock))); |
|
448 return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements)); |
|
449 } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) { |
|
450 // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return. |
|
451 // Just move the return expression into the finally block. |
|
452 return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode)); |
436 } else { |
453 } else { |
437 resultNode = null; |
454 // We need to evaluate the result of the return in case it is complex while still in the try block, |
|
455 // store it in :return, and return it afterwards. |
|
456 final List<Statement> newStatements = new ArrayList<>(); |
|
457 final int retLineNumber = returnNode.getLineNumber(); |
|
458 final long retToken = returnNode.getToken(); |
|
459 final int retFinish = returnNode.getFinish(); |
|
460 final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName()); |
|
461 // ":return = <expr>;" |
|
462 newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); |
|
463 // inline finally and end it with "return :return;" |
|
464 newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode)))); |
|
465 return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements)); |
438 } |
466 } |
439 |
|
440 newStatements.addAll(copyFinally(finallyBody)); |
|
441 if (!isTerminal(newStatements)) { |
|
442 newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode)); |
|
443 } |
|
444 |
|
445 return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements); |
|
446 } |
|
447 |
|
448 private Node copy(final Statement endpoint, final Node targetNode) { |
|
449 if (!insideTry.contains(targetNode)) { |
|
450 final List<Statement> newStatements = copyFinally(finallyBody); |
|
451 if (!isTerminal(newStatements)) { |
|
452 newStatements.add(endpoint); |
|
453 } |
|
454 return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements); |
|
455 } |
|
456 return endpoint; |
|
457 } |
467 } |
458 }); |
468 }); |
459 |
469 addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies)); |
460 addStatement(newTryNode); |
470 // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here. |
461 for (final Node statement : finallyBody.getStatements()) { |
471 addStatement(new BlockStatement(finallyBlock)); |
462 addStatement((Statement)statement); |
|
463 } |
|
464 |
472 |
465 return newTryNode; |
473 return newTryNode; |
|
474 } |
|
475 |
|
476 private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) { |
|
477 final String labelName = fn.uniqueName(":finally"); |
|
478 final long token = finallyBlock.getToken(); |
|
479 final int finish = finallyBlock.getFinish(); |
|
480 inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(), |
|
481 token, finish, labelName, finallyBlock))); |
|
482 return new JumpToInlinedFinally(labelName); |
|
483 } |
|
484 |
|
485 private static Block prependFinally(final Block finallyBlock, final Statement statement) { |
|
486 final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock); |
|
487 if (isTerminalFinally(finallyBlock)) { |
|
488 return inlinedFinally; |
|
489 } |
|
490 final List<Statement> stmts = inlinedFinally.getStatements(); |
|
491 final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1); |
|
492 newStmts.addAll(stmts); |
|
493 newStmts.add(statement); |
|
494 return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts); |
466 } |
495 } |
467 |
496 |
468 @Override |
497 @Override |
469 public Node leaveTryNode(final TryNode tryNode) { |
498 public Node leaveTryNode(final TryNode tryNode) { |
470 final Block finallyBody = tryNode.getFinallyBody(); |
499 final Block finallyBody = tryNode.getFinallyBody(); |
471 |
500 TryNode newTryNode = tryNode.setFinallyBody(lc, null); |
472 if (finallyBody == null) { |
501 |
473 return addStatement(ensureUnconditionalCatch(tryNode)); |
502 // No finally or empty finally |
|
503 if (finallyBody == null || finallyBody.getStatementCount() == 0) { |
|
504 final List<CatchNode> catches = newTryNode.getCatches(); |
|
505 if (catches == null || catches.isEmpty()) { |
|
506 // A completely degenerate try block: empty finally, no catches. Replace it with try body. |
|
507 return addStatement(new BlockStatement(tryNode.getBody())); |
|
508 } |
|
509 return addStatement(ensureUnconditionalCatch(newTryNode)); |
474 } |
510 } |
475 |
511 |
476 /* |
512 /* |
477 * create a new trynode |
513 * create a new trynode |
478 * if we have catches: |
514 * if we have catches: |
494 * |
530 * |
495 * |
531 * |
496 * now splice in finally code wherever needed |
532 * now splice in finally code wherever needed |
497 * |
533 * |
498 */ |
534 */ |
499 TryNode newTryNode; |
|
500 |
|
501 final Block catchAll = catchAllBlock(tryNode); |
535 final Block catchAll = catchAllBlock(tryNode); |
502 |
536 |
503 final List<ThrowNode> rethrows = new ArrayList<>(); |
537 final List<ThrowNode> rethrows = new ArrayList<>(1); |
504 catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
538 catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
505 @Override |
539 @Override |
506 public boolean enterThrowNode(final ThrowNode throwNode) { |
540 public boolean enterThrowNode(final ThrowNode throwNode) { |
507 rethrows.add(throwNode); |
541 rethrows.add(throwNode); |
508 return true; |
542 return true; |
509 } |
543 } |
510 }); |
544 }); |
511 assert rethrows.size() == 1; |
545 assert rethrows.size() == 1; |
512 |
546 |
513 if (tryNode.getCatchBlocks().isEmpty()) { |
547 if (!tryNode.getCatchBlocks().isEmpty()) { |
514 newTryNode = tryNode.setFinallyBody(null); |
548 final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode)); |
515 } else { |
549 newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null); |
516 final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null))); |
550 } |
517 newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null); |
551 |
518 } |
552 newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll)); |
519 |
|
520 newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null); |
|
521 |
553 |
522 /* |
554 /* |
523 * Now that the transform is done, we have to go into the try and splice |
555 * Now that the transform is done, we have to go into the try and splice |
524 * the finally block in front of any statement that is outside the try |
556 * the finally block in front of any statement that is outside the try |
525 */ |
557 */ |
526 return spliceFinally(newTryNode, rethrows, finallyBody); |
558 return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody)); |
527 } |
559 } |
528 |
560 |
529 private TryNode ensureUnconditionalCatch(final TryNode tryNode) { |
561 private TryNode ensureUnconditionalCatch(final TryNode tryNode) { |
530 final List<CatchNode> catches = tryNode.getCatches(); |
562 final List<CatchNode> catches = tryNode.getCatches(); |
531 if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) { |
563 if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) { |