8067139: Finally blocks inlined incorrectly

Wed, 28 Jan 2015 17:58:08 +0100

author
attila
date
Wed, 28 Jan 2015 17:58:08 +0100
changeset 1226
8b3f832bea55
parent 1225
aa847b71612a
child 1227
a4dc8b13c9fd

8067139: Finally blocks inlined incorrectly
Reviewed-by: hannesw, lagergren

src/jdk/nashorn/internal/codegen/AssignSymbols.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/CodeGenerator.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/Lower.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/SplitIntoFunctions.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/codegen/WeighNodes.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/Block.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/BlockLexicalContext.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/BlockStatement.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/BreakNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/ContinueNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/JumpStatement.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/JumpToInlinedFinally.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/LexicalContext.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/LexicalContextNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/TryNode.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/debug/PrintVisitor.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java file | annotate | diff | comparison | revisions
test/script/basic/JDK-8067139.js file | annotate | diff | comparison | revisions
     1.1 --- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Dec 03 16:31:39 2014 +0100
     1.2 +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Jan 28 17:58:08 2015 +0100
     1.3 @@ -919,9 +919,7 @@
     1.4      @Override
     1.5      public Node leaveTryNode(final TryNode tryNode) {
     1.6          tryNode.setException(exceptionSymbol());
     1.7 -        if (tryNode.getFinallyBody() != null) {
     1.8 -            tryNode.setFinallyCatchAll(exceptionSymbol());
     1.9 -        }
    1.10 +        assert tryNode.getFinallyBody() == null;
    1.11  
    1.12          end(tryNode);
    1.13  
     2.1 --- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Dec 03 16:31:39 2014 +0100
     2.2 +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Jan 28 17:58:08 2015 +0100
     2.3 @@ -85,7 +85,6 @@
     2.4  import jdk.nashorn.internal.ir.Block;
     2.5  import jdk.nashorn.internal.ir.BlockStatement;
     2.6  import jdk.nashorn.internal.ir.BreakNode;
     2.7 -import jdk.nashorn.internal.ir.BreakableNode;
     2.8  import jdk.nashorn.internal.ir.CallNode;
     2.9  import jdk.nashorn.internal.ir.CaseNode;
    2.10  import jdk.nashorn.internal.ir.CatchNode;
    2.11 @@ -102,6 +101,7 @@
    2.12  import jdk.nashorn.internal.ir.IndexNode;
    2.13  import jdk.nashorn.internal.ir.JoinPredecessorExpression;
    2.14  import jdk.nashorn.internal.ir.JumpStatement;
    2.15 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
    2.16  import jdk.nashorn.internal.ir.LabelNode;
    2.17  import jdk.nashorn.internal.ir.LexicalContext;
    2.18  import jdk.nashorn.internal.ir.LexicalContextNode;
    2.19 @@ -1110,7 +1110,14 @@
    2.20  
    2.21      @Override
    2.22      public boolean enterBlock(final Block block) {
    2.23 -        method.label(block.getEntryLabel());
    2.24 +        final Label entryLabel = block.getEntryLabel();
    2.25 +        if (entryLabel.isBreakTarget()) {
    2.26 +            // Entry label is a break target only for an inlined finally block.
    2.27 +            assert !method.isReachable();
    2.28 +            method.breakLabel(entryLabel, lc.getUsedSlotCount());
    2.29 +        } else {
    2.30 +            method.label(entryLabel);
    2.31 +        }
    2.32          if(!method.isReachable()) {
    2.33              return false;
    2.34          }
    2.35 @@ -1240,6 +1247,11 @@
    2.36          return enterJumpStatement(breakNode);
    2.37      }
    2.38  
    2.39 +    @Override
    2.40 +    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
    2.41 +        return enterJumpStatement(jumpToInlinedFinally);
    2.42 +    }
    2.43 +
    2.44      private boolean enterJumpStatement(final JumpStatement jump) {
    2.45          if(!method.isReachable()) {
    2.46              return false;
    2.47 @@ -1247,9 +1259,8 @@
    2.48          enterStatement(jump);
    2.49  
    2.50          method.beforeJoinPoint(jump);
    2.51 -        final BreakableNode target = jump.getTarget(lc);
    2.52 -        popScopesUntil(target);
    2.53 -        final Label targetLabel = jump.getTargetLabel(target);
    2.54 +        popScopesUntil(jump.getPopScopeLimit(lc));
    2.55 +        final Label targetLabel = jump.getTargetLabel(lc);
    2.56          targetLabel.markAsBreakTarget();
    2.57          method._goto(targetLabel);
    2.58  
    2.59 @@ -3053,6 +3064,14 @@
    2.60          if (method.isReachable()) {
    2.61              method._goto(skip);
    2.62          }
    2.63 +
    2.64 +        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
    2.65 +            TryNode.getLabelledInlinedFinallyBlock(inlinedFinally).accept(this);
    2.66 +            // All inlined finallies end with a jump or a return
    2.67 +            assert !method.isReachable();
    2.68 +        }
    2.69 +
    2.70 +
    2.71          method._catch(recovery);
    2.72          method.store(vmException, EXCEPTION_TYPE);
    2.73  
    2.74 @@ -3112,15 +3131,14 @@
    2.75              catchBody.accept(this);
    2.76              leaveBlock(catchBlock);
    2.77              lc.pop(catchBlock);
    2.78 -            if(method.isReachable()) {
    2.79 -                method._goto(afterCatch);
    2.80 -            }
    2.81              if(nextCatch != null) {
    2.82 +                if(method.isReachable()) {
    2.83 +                    method._goto(afterCatch);
    2.84 +                }
    2.85                  method.breakLabel(nextCatch, lc.getUsedSlotCount());
    2.86              }
    2.87          }
    2.88  
    2.89 -        assert !method.isReachable();
    2.90          // afterCatch could be the same as skip, except that we need to establish that the vmException is dead.
    2.91          method.label(afterCatch);
    2.92          if(method.isReachable()) {
    2.93 @@ -3129,6 +3147,8 @@
    2.94          method.label(skip);
    2.95  
    2.96          // Finally body is always inlined elsewhere so it doesn't need to be emitted
    2.97 +        assert tryNode.getFinallyBody() == null;
    2.98 +
    2.99          return false;
   2.100      }
   2.101  
     3.1 --- a/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Dec 03 16:31:39 2014 +0100
     3.2 +++ b/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
     3.3 @@ -74,7 +74,7 @@
     3.4      /** size of next free slot vector */
     3.5      private int nextFreeSlotsSize;
     3.6  
     3.7 -    private boolean isWithBoundary(final LexicalContextNode node) {
     3.8 +    private boolean isWithBoundary(final Object node) {
     3.9          return node instanceof Block && !isEmpty() && peek() instanceof WithNode;
    3.10      }
    3.11  
    3.12 @@ -102,7 +102,7 @@
    3.13      }
    3.14  
    3.15      @Override
    3.16 -    public <T extends LexicalContextNode> T pop(final T node) {
    3.17 +    public <T extends Node> T pop(final T node) {
    3.18          final T popped = super.pop(node);
    3.19          if (isWithBoundary(node)) {
    3.20              dynamicScopeCount--;
     4.1 --- a/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Dec 03 16:31:39 2014 +0100
     4.2 +++ b/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Jan 28 17:58:08 2015 +0100
     4.3 @@ -62,6 +62,7 @@
     4.4  import jdk.nashorn.internal.ir.JoinPredecessor;
     4.5  import jdk.nashorn.internal.ir.JoinPredecessorExpression;
     4.6  import jdk.nashorn.internal.ir.JumpStatement;
     4.7 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
     4.8  import jdk.nashorn.internal.ir.LabelNode;
     4.9  import jdk.nashorn.internal.ir.LexicalContext;
    4.10  import jdk.nashorn.internal.ir.LexicalContextNode;
    4.11 @@ -529,8 +530,7 @@
    4.12              return false;
    4.13          }
    4.14          assertTypeStackIsEmpty();
    4.15 -        final BreakableNode target = jump.getTarget(lc);
    4.16 -        jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target));
    4.17 +        jumpToLabel(jump, jump.getTargetLabel(lc), getBreakTargetTypes(jump.getPopScopeLimit(lc)));
    4.18          doesNotContinueSequentially();
    4.19          return false;
    4.20      }
    4.21 @@ -784,6 +784,11 @@
    4.22      }
    4.23  
    4.24      @Override
    4.25 +    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
    4.26 +        return enterJumpStatement(jumpToInlinedFinally);
    4.27 +    }
    4.28 +
    4.29 +    @Override
    4.30      public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
    4.31          if (literalNode instanceof ArrayLiteralNode) {
    4.32              final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions();
    4.33 @@ -1042,6 +1047,17 @@
    4.34          }
    4.35          doesNotContinueSequentially();
    4.36  
    4.37 +        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
    4.38 +            final Block finallyBody = TryNode.getLabelledInlinedFinallyBlock(inlinedFinally);
    4.39 +            joinOnLabel(finallyBody.getEntryLabel());
    4.40 +            // NOTE: the jump to inlined finally can end up in dead code, so it is not necessarily reachable.
    4.41 +            if (reachable) {
    4.42 +                finallyBody.accept(this);
    4.43 +                // All inlined finallies end with a jump or a return
    4.44 +                assert !reachable;
    4.45 +            }
    4.46 +        }
    4.47 +
    4.48          joinOnLabel(catchLabel);
    4.49          for(final CatchNode catchNode: tryNode.getCatches()) {
    4.50              final IdentNode exception = catchNode.getException();
    4.51 @@ -1125,7 +1141,7 @@
    4.52          return false;
    4.53      };
    4.54  
    4.55 -    private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) {
    4.56 +    private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode target) {
    4.57          // Remove symbols defined in the the blocks that are being broken out of.
    4.58          Map<Symbol, LvarType> types = localVariableTypes;
    4.59          for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
    4.60 @@ -1380,7 +1396,11 @@
    4.61                  if(node instanceof JoinPredecessor) {
    4.62                      final JoinPredecessor original = joinPredecessors.pop();
    4.63                      assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName();
    4.64 -                    return (Node)setLocalVariableConversion(original, (JoinPredecessor)node);
    4.65 +                    final JoinPredecessor newNode = setLocalVariableConversion(original, (JoinPredecessor)node);
    4.66 +                    if (newNode instanceof LexicalContextNode) {
    4.67 +                        lc.replace((LexicalContextNode)node, (LexicalContextNode)newNode);
    4.68 +                    }
    4.69 +                    return (Node)newNode;
    4.70                  }
    4.71                  return node;
    4.72              }
     5.1 --- a/src/jdk/nashorn/internal/codegen/Lower.java	Wed Dec 03 16:31:39 2014 +0100
     5.2 +++ b/src/jdk/nashorn/internal/codegen/Lower.java	Wed Jan 28 17:58:08 2015 +0100
     5.3 @@ -56,9 +56,11 @@
     5.4  import jdk.nashorn.internal.ir.IfNode;
     5.5  import jdk.nashorn.internal.ir.IndexNode;
     5.6  import jdk.nashorn.internal.ir.JumpStatement;
     5.7 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
     5.8  import jdk.nashorn.internal.ir.LabelNode;
     5.9  import jdk.nashorn.internal.ir.LexicalContext;
    5.10  import jdk.nashorn.internal.ir.LiteralNode;
    5.11 +import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
    5.12  import jdk.nashorn.internal.ir.LoopNode;
    5.13  import jdk.nashorn.internal.ir.Node;
    5.14  import jdk.nashorn.internal.ir.ReturnNode;
    5.15 @@ -115,7 +117,7 @@
    5.16                  for (final Statement statement : statements) {
    5.17                      if (!terminated) {
    5.18                          newStatements.add(statement);
    5.19 -                        if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
    5.20 +                        if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why?
    5.21                              terminated = true;
    5.22                          }
    5.23                      } else {
    5.24 @@ -183,6 +185,12 @@
    5.25      }
    5.26  
    5.27      @Override
    5.28 +    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
    5.29 +        addStatement(jumpToInlinedFinally);
    5.30 +        return false;
    5.31 +    }
    5.32 +
    5.33 +    @Override
    5.34      public boolean enterEmptyNode(final EmptyNode emptyNode) {
    5.35          return false;
    5.36      }
    5.37 @@ -318,8 +326,8 @@
    5.38          return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
    5.39      }
    5.40  
    5.41 -    private static Node ensureUniqueNamesIn(final Node node) {
    5.42 -        return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    5.43 +    private static <T extends Node> T ensureUniqueNamesIn(final T node) {
    5.44 +        return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    5.45              @Override
    5.46              public Node leaveFunctionNode(final FunctionNode functionNode) {
    5.47                  final String name = functionNode.getName();
    5.48 @@ -333,15 +341,15 @@
    5.49          });
    5.50      }
    5.51  
    5.52 -    private static List<Statement> copyFinally(final Block finallyBody) {
    5.53 +    private static Block createFinallyBlock(final Block finallyBody) {
    5.54          final List<Statement> newStatements = new ArrayList<>();
    5.55          for (final Statement statement : finallyBody.getStatements()) {
    5.56 -            newStatements.add((Statement)ensureUniqueNamesIn(statement));
    5.57 +            newStatements.add(statement);
    5.58              if (statement.hasTerminalFlags()) {
    5.59 -                return newStatements;
    5.60 +                break;
    5.61              }
    5.62          }
    5.63 -        return newStatements;
    5.64 +        return finallyBody.setStatements(null, newStatements);
    5.65      }
    5.66  
    5.67      private Block catchAllBlock(final TryNode tryNode) {
    5.68 @@ -367,28 +375,24 @@
    5.69          return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
    5.70      }
    5.71  
    5.72 -    private static boolean isTerminal(final List<Statement> statements) {
    5.73 -        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
    5.74 +    private static boolean isTerminalFinally(final Block finallyBlock) {
    5.75 +        return finallyBlock.getLastStatement().hasTerminalFlags();
    5.76      }
    5.77  
    5.78      /**
    5.79       * Splice finally code into all endpoints of a trynode
    5.80       * @param tryNode the try node
    5.81 -     * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
    5.82 +     * @param rethrow the rethrowing throw nodes from the synthetic catch block
    5.83       * @param finallyBody the code in the original finally block
    5.84       * @return new try node after splicing finally code (same if nop)
    5.85       */
    5.86 -    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
    5.87 +    private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) {
    5.88          assert tryNode.getFinallyBody() == null;
    5.89  
    5.90 +        final Block finallyBlock = createFinallyBlock(finallyBody);
    5.91 +        final ArrayList<Block> inlinedFinallies = new ArrayList<>();
    5.92 +        final FunctionNode fn = lc.getCurrentFunction();
    5.93          final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    5.94 -            final List<Node> insideTry = new ArrayList<>();
    5.95 -
    5.96 -            @Override
    5.97 -            public boolean enterDefault(final Node node) {
    5.98 -                insideTry.add(node);
    5.99 -                return true;
   5.100 -            }
   5.101  
   5.102              @Override
   5.103              public boolean enterFunctionNode(final FunctionNode functionNode) {
   5.104 @@ -398,12 +402,8 @@
   5.105  
   5.106              @Override
   5.107              public Node leaveThrowNode(final ThrowNode throwNode) {
   5.108 -                if (rethrows.contains(throwNode)) {
   5.109 -                    final List<Statement> newStatements = copyFinally(finallyBody);
   5.110 -                    if (!isTerminal(newStatements)) {
   5.111 -                        newStatements.add(throwNode);
   5.112 -                    }
   5.113 -                    return BlockStatement.createReplacement(throwNode, newStatements);
   5.114 +                if (rethrow == throwNode) {
   5.115 +                    return new BlockStatement(prependFinally(finallyBlock, throwNode));
   5.116                  }
   5.117                  return throwNode;
   5.118              }
   5.119 @@ -419,58 +419,94 @@
   5.120              }
   5.121  
   5.122              private Node leaveJumpStatement(final JumpStatement jump) {
   5.123 -                return copy(jump, (Node)jump.getTarget(Lower.this.lc));
   5.124 +                // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and
   5.125 +                // continue are edited. JTIF nodes should not be changed, rather the surroundings of
   5.126 +                // break/continue/return that were moved into the inlined finally block itself will be changed.
   5.127 +
   5.128 +                // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block.
   5.129 +                if (jump.getTarget(lc) == null) {
   5.130 +                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump));
   5.131 +                }
   5.132 +                return jump;
   5.133              }
   5.134  
   5.135              @Override
   5.136              public Node leaveReturnNode(final ReturnNode returnNode) {
   5.137 -                final Expression expr  = returnNode.getExpression();
   5.138 -                final List<Statement> newStatements = new ArrayList<>();
   5.139 -
   5.140 -                final Expression resultNode;
   5.141 -                if (expr != null) {
   5.142 -                    //we need to evaluate the result of the return in case it is complex while
   5.143 -                    //still in the try block, store it in a result value and return it afterwards
   5.144 -                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
   5.145 -                    newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
   5.146 +                final Expression expr = returnNode.getExpression();
   5.147 +                if (isTerminalFinally(finallyBlock)) {
   5.148 +                    if (expr == null) {
   5.149 +                        // Terminal finally; no return expression.
   5.150 +                        return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock));
   5.151 +                    }
   5.152 +                    // Terminal finally; has a return expression.
   5.153 +                    final List<Statement> newStatements = new ArrayList<>(2);
   5.154 +                    final int retLineNumber = returnNode.getLineNumber();
   5.155 +                    final long retToken = returnNode.getToken();
   5.156 +                    // Expression is evaluated for side effects.
   5.157 +                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr));
   5.158 +                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)));
   5.159 +                    return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements));
   5.160 +                } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) {
   5.161 +                    // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return.
   5.162 +                    // Just move the return expression into the finally block.
   5.163 +                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode));
   5.164                  } else {
   5.165 -                    resultNode = null;
   5.166 +                    // We need to evaluate the result of the return in case it is complex while still in the try block,
   5.167 +                    // store it in :return, and return it afterwards.
   5.168 +                    final List<Statement> newStatements = new ArrayList<>();
   5.169 +                    final int retLineNumber = returnNode.getLineNumber();
   5.170 +                    final long retToken = returnNode.getToken();
   5.171 +                    final int retFinish = returnNode.getFinish();
   5.172 +                    final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName());
   5.173 +                    // ":return = <expr>;"
   5.174 +                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
   5.175 +                    // inline finally and end it with "return :return;"
   5.176 +                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode))));
   5.177 +                    return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements));
   5.178                  }
   5.179 -
   5.180 -                newStatements.addAll(copyFinally(finallyBody));
   5.181 -                if (!isTerminal(newStatements)) {
   5.182 -                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
   5.183 -                }
   5.184 -
   5.185 -                return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
   5.186 -            }
   5.187 -
   5.188 -            private Node copy(final Statement endpoint, final Node targetNode) {
   5.189 -                if (!insideTry.contains(targetNode)) {
   5.190 -                    final List<Statement> newStatements = copyFinally(finallyBody);
   5.191 -                    if (!isTerminal(newStatements)) {
   5.192 -                        newStatements.add(endpoint);
   5.193 -                    }
   5.194 -                    return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
   5.195 -                }
   5.196 -                return endpoint;
   5.197              }
   5.198          });
   5.199 -
   5.200 -        addStatement(newTryNode);
   5.201 -        for (final Node statement : finallyBody.getStatements()) {
   5.202 -            addStatement((Statement)statement);
   5.203 -        }
   5.204 +        addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies));
   5.205 +        // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here.
   5.206 +        addStatement(new BlockStatement(finallyBlock));
   5.207  
   5.208          return newTryNode;
   5.209      }
   5.210  
   5.211 +    private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) {
   5.212 +        final String labelName = fn.uniqueName(":finally");
   5.213 +        final long token = finallyBlock.getToken();
   5.214 +        final int finish = finallyBlock.getFinish();
   5.215 +        inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(),
   5.216 +                token, finish, labelName, finallyBlock)));
   5.217 +        return new JumpToInlinedFinally(labelName);
   5.218 +    }
   5.219 +
   5.220 +    private static Block prependFinally(final Block finallyBlock, final Statement statement) {
   5.221 +        final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock);
   5.222 +        if (isTerminalFinally(finallyBlock)) {
   5.223 +            return inlinedFinally;
   5.224 +        }
   5.225 +        final List<Statement> stmts = inlinedFinally.getStatements();
   5.226 +        final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1);
   5.227 +        newStmts.addAll(stmts);
   5.228 +        newStmts.add(statement);
   5.229 +        return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts);
   5.230 +    }
   5.231 +
   5.232      @Override
   5.233      public Node leaveTryNode(final TryNode tryNode) {
   5.234          final Block finallyBody = tryNode.getFinallyBody();
   5.235 +        TryNode newTryNode = tryNode.setFinallyBody(lc, null);
   5.236  
   5.237 -        if (finallyBody == null) {
   5.238 -            return addStatement(ensureUnconditionalCatch(tryNode));
   5.239 +        // No finally or empty finally
   5.240 +        if (finallyBody == null || finallyBody.getStatementCount() == 0) {
   5.241 +            final List<CatchNode> catches = newTryNode.getCatches();
   5.242 +            if (catches == null || catches.isEmpty()) {
   5.243 +                // A completely degenerate try block: empty finally, no catches. Replace it with try body.
   5.244 +                return addStatement(new BlockStatement(tryNode.getBody()));
   5.245 +            }
   5.246 +            return addStatement(ensureUnconditionalCatch(newTryNode));
   5.247          }
   5.248  
   5.249          /*
   5.250 @@ -496,11 +532,9 @@
   5.251           *   now splice in finally code wherever needed
   5.252           *
   5.253           */
   5.254 -        TryNode newTryNode;
   5.255 -
   5.256          final Block catchAll = catchAllBlock(tryNode);
   5.257  
   5.258 -        final List<ThrowNode> rethrows = new ArrayList<>();
   5.259 +        final List<ThrowNode> rethrows = new ArrayList<>(1);
   5.260          catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   5.261              @Override
   5.262              public boolean enterThrowNode(final ThrowNode throwNode) {
   5.263 @@ -510,20 +544,18 @@
   5.264          });
   5.265          assert rethrows.size() == 1;
   5.266  
   5.267 -        if (tryNode.getCatchBlocks().isEmpty()) {
   5.268 -            newTryNode = tryNode.setFinallyBody(null);
   5.269 -        } else {
   5.270 -            final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
   5.271 -            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
   5.272 +        if (!tryNode.getCatchBlocks().isEmpty()) {
   5.273 +            final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode));
   5.274 +            newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null);
   5.275          }
   5.276  
   5.277 -        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
   5.278 +        newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll));
   5.279  
   5.280          /*
   5.281           * Now that the transform is done, we have to go into the try and splice
   5.282           * the finally block in front of any statement that is outside the try
   5.283           */
   5.284 -        return spliceFinally(newTryNode, rethrows, finallyBody);
   5.285 +        return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody));
   5.286      }
   5.287  
   5.288      private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
   5.289 @@ -535,7 +567,7 @@
   5.290          final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
   5.291  
   5.292          newCatchBlocks.add(catchAllBlock(tryNode));
   5.293 -        return tryNode.setCatchBlocks(newCatchBlocks);
   5.294 +        return tryNode.setCatchBlocks(lc, newCatchBlocks);
   5.295      }
   5.296  
   5.297      @Override
     6.1 --- a/src/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Dec 03 16:31:39 2014 +0100
     6.2 +++ b/src/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Jan 28 17:58:08 2015 +0100
     6.3 @@ -51,6 +51,7 @@
     6.4  import jdk.nashorn.internal.ir.IdentNode;
     6.5  import jdk.nashorn.internal.ir.IfNode;
     6.6  import jdk.nashorn.internal.ir.JumpStatement;
     6.7 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
     6.8  import jdk.nashorn.internal.ir.LiteralNode;
     6.9  import jdk.nashorn.internal.ir.Node;
    6.10  import jdk.nashorn.internal.ir.ReturnNode;
    6.11 @@ -355,6 +356,11 @@
    6.12          return leaveJumpNode(continueNode);
    6.13      }
    6.14  
    6.15 +    @Override
    6.16 +    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
    6.17 +        return leaveJumpNode(jumpToInlinedFinally);
    6.18 +    }
    6.19 +
    6.20      private JumpStatement leaveJumpNode(final JumpStatement jump) {
    6.21          if (inSplitNode()) {
    6.22              final SplitState splitState = getCurrentSplitState();
     7.1 --- a/src/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Dec 03 16:31:39 2014 +0100
     7.2 +++ b/src/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Jan 28 17:58:08 2015 +0100
     7.3 @@ -40,6 +40,7 @@
     7.4  import jdk.nashorn.internal.ir.IdentNode;
     7.5  import jdk.nashorn.internal.ir.IfNode;
     7.6  import jdk.nashorn.internal.ir.IndexNode;
     7.7 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
     7.8  import jdk.nashorn.internal.ir.LexicalContext;
     7.9  import jdk.nashorn.internal.ir.LiteralNode;
    7.10  import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
    7.11 @@ -197,6 +198,12 @@
    7.12          return indexNode;
    7.13      }
    7.14  
    7.15 +    @Override
    7.16 +    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
    7.17 +        weight += BREAK_WEIGHT;
    7.18 +        return jumpToInlinedFinally;
    7.19 +    }
    7.20 +
    7.21      @SuppressWarnings("rawtypes")
    7.22      @Override
    7.23      public boolean enterLiteralNode(final LiteralNode literalNode) {
     8.1 --- a/src/jdk/nashorn/internal/ir/Block.java	Wed Dec 03 16:31:39 2014 +0100
     8.2 +++ b/src/jdk/nashorn/internal/ir/Block.java	Wed Jan 28 17:58:08 2015 +0100
     8.3 @@ -298,6 +298,14 @@
     8.4      }
     8.5  
     8.6      /**
     8.7 +     * Returns the last statement in the block.
     8.8 +     * @return the last statement in the block, or null if the block has no statements.
     8.9 +     */
    8.10 +    public Statement getLastStatement() {
    8.11 +        return statements.isEmpty() ? null : statements.get(statements.size() - 1);
    8.12 +    }
    8.13 +
    8.14 +    /**
    8.15       * Reset the statement list for this block
    8.16       *
    8.17       * @param lc lexical context
     9.1 --- a/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Dec 03 16:31:39 2014 +0100
     9.2 +++ b/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
     9.3 @@ -74,7 +74,7 @@
     9.4  
     9.5      @SuppressWarnings("unchecked")
     9.6      @Override
     9.7 -    public <T extends LexicalContextNode> T pop(final T node) {
     9.8 +    public <T extends Node> T pop(final T node) {
     9.9          T expected = node;
    9.10          if (node instanceof Block) {
    9.11              final List<Statement> newStatements = popStatements();
    10.1 --- a/src/jdk/nashorn/internal/ir/BlockStatement.java	Wed Dec 03 16:31:39 2014 +0100
    10.2 +++ b/src/jdk/nashorn/internal/ir/BlockStatement.java	Wed Jan 28 17:58:08 2015 +0100
    10.3 @@ -40,6 +40,15 @@
    10.4      /**
    10.5       * Constructor
    10.6       *
    10.7 +     * @param block the block to execute
    10.8 +     */
    10.9 +    public BlockStatement(final Block block) {
   10.10 +        this(block.getFirstStatementLineNumber(), block);
   10.11 +    }
   10.12 +
   10.13 +    /**
   10.14 +     * Constructor
   10.15 +     *
   10.16       * @param lineNumber line number
   10.17       * @param block the block to execute
   10.18       */
    11.1 --- a/src/jdk/nashorn/internal/ir/BreakNode.java	Wed Dec 03 16:31:39 2014 +0100
    11.2 +++ b/src/jdk/nashorn/internal/ir/BreakNode.java	Wed Jan 28 17:58:08 2015 +0100
    11.3 @@ -77,7 +77,7 @@
    11.4      }
    11.5  
    11.6      @Override
    11.7 -    public Label getTargetLabel(final BreakableNode target) {
    11.8 +    Label getTargetLabel(final BreakableNode target) {
    11.9          return target.getBreakLabel();
   11.10      }
   11.11  }
    12.1 --- a/src/jdk/nashorn/internal/ir/ContinueNode.java	Wed Dec 03 16:31:39 2014 +0100
    12.2 +++ b/src/jdk/nashorn/internal/ir/ContinueNode.java	Wed Jan 28 17:58:08 2015 +0100
    12.3 @@ -78,7 +78,7 @@
    12.4      }
    12.5  
    12.6      @Override
    12.7 -    public Label getTargetLabel(final BreakableNode target) {
    12.8 +    Label getTargetLabel(final BreakableNode target) {
    12.9          return ((LoopNode)target).getContinueLabel();
   12.10      }
   12.11  }
    13.1 --- a/src/jdk/nashorn/internal/ir/JumpStatement.java	Wed Dec 03 16:31:39 2014 +0100
    13.2 +++ b/src/jdk/nashorn/internal/ir/JumpStatement.java	Wed Jan 28 17:58:08 2015 +0100
    13.3 @@ -101,7 +101,26 @@
    13.4       * @throws ClassCastException if invoked on the kind of breakable node that this jump statement is not prepared to
    13.5       * handle.
    13.6       */
    13.7 -    public abstract Label getTargetLabel(final BreakableNode target);
    13.8 +    abstract Label getTargetLabel(final BreakableNode target);
    13.9 +
   13.10 +    /**
   13.11 +     * Returns the label this jump statement targets.
   13.12 +     * @param lc the lexical context
   13.13 +     * @return the label this jump statement targets.
   13.14 +     */
   13.15 +    public Label getTargetLabel(final LexicalContext lc) {
   13.16 +        return getTargetLabel(getTarget(lc));
   13.17 +    }
   13.18 +
   13.19 +    /**
   13.20 +     * Returns the limit node for popping scopes when this jump statement is effected.
   13.21 +     * @param lc the current lexical context
   13.22 +     * @return the limit node for popping scopes when this jump statement is effected.
   13.23 +     */
   13.24 +    public LexicalContextNode getPopScopeLimit(final LexicalContext lc) {
   13.25 +        // In most cases (break and continue) this is equal to the target.
   13.26 +        return getTarget(lc);
   13.27 +    }
   13.28  
   13.29      @Override
   13.30      public JumpStatement setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/src/jdk/nashorn/internal/ir/JumpToInlinedFinally.java	Wed Jan 28 17:58:08 2015 +0100
    14.3 @@ -0,0 +1,90 @@
    14.4 +/*
    14.5 + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
    14.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    14.7 + *
    14.8 + * This code is free software; you can redistribute it and/or modify it
    14.9 + * under the terms of the GNU General Public License version 2 only, as
   14.10 + * published by the Free Software Foundation.  Oracle designates this
   14.11 + * particular file as subject to the "Classpath" exception as provided
   14.12 + * by Oracle in the LICENSE file that accompanied this code.
   14.13 + *
   14.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
   14.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   14.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14.17 + * version 2 for more details (a copy is included in the LICENSE file that
   14.18 + * accompanied this code).
   14.19 + *
   14.20 + * You should have received a copy of the GNU General Public License version
   14.21 + * 2 along with this work; if not, write to the Free Software Foundation,
   14.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   14.23 + *
   14.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   14.25 + * or visit www.oracle.com if you need additional information or have any
   14.26 + * questions.
   14.27 + */
   14.28 +
   14.29 +package jdk.nashorn.internal.ir;
   14.30 +
   14.31 +import java.util.Objects;
   14.32 +import jdk.nashorn.internal.codegen.Label;
   14.33 +import jdk.nashorn.internal.ir.annotations.Immutable;
   14.34 +import jdk.nashorn.internal.ir.visitor.NodeVisitor;
   14.35 +
   14.36 +/**
   14.37 + * IR representation for synthetic jump into an inlined finally statement.
   14.38 + */
   14.39 +@Immutable
   14.40 +public final class JumpToInlinedFinally extends JumpStatement {
   14.41 +    private static final long serialVersionUID = 1L;
   14.42 +
   14.43 +    /**
   14.44 +     * Constructor
   14.45 +     *
   14.46 +     * @param labelName  label name for inlined finally block
   14.47 +     */
   14.48 +    public JumpToInlinedFinally(final String labelName) {
   14.49 +        super(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, Objects.requireNonNull(labelName));
   14.50 +    }
   14.51 +
   14.52 +    private JumpToInlinedFinally(final JumpToInlinedFinally breakNode, final LocalVariableConversion conversion) {
   14.53 +        super(breakNode, conversion);
   14.54 +    }
   14.55 +
   14.56 +    @Override
   14.57 +    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
   14.58 +        if (visitor.enterJumpToInlinedFinally(this)) {
   14.59 +            return visitor.leaveJumpToInlinedFinally(this);
   14.60 +        }
   14.61 +
   14.62 +        return this;
   14.63 +    }
   14.64 +
   14.65 +    @Override
   14.66 +    JumpStatement createNewJumpStatement(final LocalVariableConversion conversion) {
   14.67 +        return new JumpToInlinedFinally(this, conversion);
   14.68 +    }
   14.69 +
   14.70 +    @Override
   14.71 +    String getStatementName() {
   14.72 +        return ":jumpToInlinedFinally";
   14.73 +    }
   14.74 +
   14.75 +    @Override
   14.76 +    public Block getTarget(final LexicalContext lc) {
   14.77 +        return lc.getInlinedFinally(getLabelName());
   14.78 +    }
   14.79 +
   14.80 +    @Override
   14.81 +    public TryNode getPopScopeLimit(final LexicalContext lc) {
   14.82 +        // Returns the try node to which this jump's target belongs. This will make scope popping also pop the scope
   14.83 +        // for the body of the try block, if it needs scope.
   14.84 +        return lc.getTryNodeForInlinedFinally(getLabelName());
   14.85 +    }
   14.86 +
   14.87 +    @Override
   14.88 +    Label getTargetLabel(final BreakableNode target) {
   14.89 +        assert target != null;
   14.90 +        // We're jumping to the entry of the inlined finally block
   14.91 +        return ((Block)target).getEntryLabel();
   14.92 +    }
   14.93 +}
    15.1 --- a/src/jdk/nashorn/internal/ir/LexicalContext.java	Wed Dec 03 16:31:39 2014 +0100
    15.2 +++ b/src/jdk/nashorn/internal/ir/LexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
    15.3 @@ -190,7 +190,7 @@
    15.4       * @return the node that was popped
    15.5       */
    15.6      @SuppressWarnings("unchecked")
    15.7 -    public <T extends LexicalContextNode> T pop(final T node) {
    15.8 +    public <T extends Node> T pop(final T node) {
    15.9          --sp;
   15.10          final LexicalContextNode popped = stack[sp];
   15.11          stack[sp] = null;
   15.12 @@ -469,7 +469,7 @@
   15.13       * scopes that need to be explicitly popped in order to perform a break or continue jump within the current bytecode
   15.14       * method. For this reason, the method returns 0 if it encounters a {@code SplitNode} between the current location
   15.15       * and the break/continue target.
   15.16 -     * @param until node to stop counting at. Must be within the current  function
   15.17 +     * @param until node to stop counting at. Must be within the current function
   15.18       * @return number of with scopes encountered in the context
   15.19       */
   15.20      public int getScopeNestingLevelTo(final LexicalContextNode until) {
   15.21 @@ -565,11 +565,41 @@
   15.22      }
   15.23  
   15.24      /**
   15.25 +     * Find the inlined finally block node corresponding to this label.
   15.26 +     * @param labelName label name to search for. Must not be null.
   15.27 +     * @return closest inlined finally block with the given label
   15.28 +     */
   15.29 +    public Block getInlinedFinally(final String labelName) {
   15.30 +        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
   15.31 +            final Block inlinedFinally = iter.next().getInlinedFinally(labelName);
   15.32 +            if (inlinedFinally != null) {
   15.33 +                return inlinedFinally;
   15.34 +            }
   15.35 +        }
   15.36 +        return null;
   15.37 +    }
   15.38 +
   15.39 +    /**
   15.40 +     * Find the try node for an inlined finally block corresponding to this label.
   15.41 +     * @param labelName label name to search for. Must not be null.
   15.42 +     * @return the try node to which the labelled inlined finally block belongs.
   15.43 +     */
   15.44 +    public TryNode getTryNodeForInlinedFinally(final String labelName) {
   15.45 +        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
   15.46 +            final TryNode tryNode = iter.next();
   15.47 +            if (tryNode.getInlinedFinally(labelName) != null) {
   15.48 +                return tryNode;
   15.49 +            }
   15.50 +        }
   15.51 +        return null;
   15.52 +    }
   15.53 +
   15.54 +    /**
   15.55       * Check the lexical context for a given label node by name
   15.56       * @param name name of the label
   15.57       * @return LabelNode if found, null otherwise
   15.58       */
   15.59 -    public LabelNode findLabel(final String name) {
   15.60 +    private LabelNode findLabel(final String name) {
   15.61          for (final Iterator<LabelNode> iter = new NodeIterator<>(LabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
   15.62              final LabelNode next = iter.next();
   15.63              if (next.getLabelName().equals(name)) {
   15.64 @@ -592,6 +622,12 @@
   15.65                  return true;
   15.66              } else if (next == target) {
   15.67                  return false;
   15.68 +            } else if (next instanceof TryNode) {
   15.69 +                for(final Block inlinedFinally: ((TryNode)next).getInlinedFinallies()) {
   15.70 +                    if (TryNode.getLabelledInlinedFinallyBlock(inlinedFinally) == target) {
   15.71 +                        return false;
   15.72 +                    }
   15.73 +                }
   15.74              }
   15.75          }
   15.76          throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't");
    16.1 --- a/src/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Dec 03 16:31:39 2014 +0100
    16.2 +++ b/src/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Jan 28 17:58:08 2015 +0100
    16.3 @@ -54,8 +54,8 @@
    16.4          static Node accept(final LexicalContextNode node, final NodeVisitor<? extends LexicalContext> visitor) {
    16.5              final LexicalContext lc = visitor.getLexicalContext();
    16.6              lc.push(node);
    16.7 -            final LexicalContextNode newNode = (LexicalContextNode)node.accept(lc, visitor);
    16.8 -            return (Node)lc.pop(newNode);
    16.9 +            final Node newNode = node.accept(lc, visitor);
   16.10 +            return lc.pop(newNode);
   16.11          }
   16.12      }
   16.13  }
    17.1 --- a/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Dec 03 16:31:39 2014 +0100
    17.2 +++ b/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
    17.3 @@ -115,7 +115,7 @@
    17.4      }
    17.5  
    17.6      @Override
    17.7 -    public <T extends LexicalContextNode> T pop(final T node) {
    17.8 +    public <T extends Node> T pop(final T node) {
    17.9          final T popped = super.pop(node);
   17.10          if (isEnabled) {
   17.11              if(node instanceof FunctionNode) {
    18.1 --- a/src/jdk/nashorn/internal/ir/TryNode.java	Wed Dec 03 16:31:39 2014 +0100
    18.2 +++ b/src/jdk/nashorn/internal/ir/TryNode.java	Wed Jan 28 17:58:08 2015 +0100
    18.3 @@ -27,7 +27,9 @@
    18.4  
    18.5  import java.util.ArrayList;
    18.6  import java.util.Collections;
    18.7 +import java.util.HashSet;
    18.8  import java.util.List;
    18.9 +import java.util.Set;
   18.10  import jdk.nashorn.internal.ir.annotations.Immutable;
   18.11  import jdk.nashorn.internal.ir.visitor.NodeVisitor;
   18.12  
   18.13 @@ -35,7 +37,7 @@
   18.14   * IR representation of a TRY statement.
   18.15   */
   18.16  @Immutable
   18.17 -public final class TryNode extends Statement implements JoinPredecessor {
   18.18 +public final class TryNode extends LexicalContextStatement implements JoinPredecessor {
   18.19      private static final long serialVersionUID = 1L;
   18.20  
   18.21      /** Try statements. */
   18.22 @@ -47,12 +49,24 @@
   18.23      /** Finally clause. */
   18.24      private final Block finallyBody;
   18.25  
   18.26 +    /**
   18.27 +     * List of inlined finally blocks. The structure of every inlined finally is:
   18.28 +     * Block(LabelNode(label, Block(finally-statements, (JumpStatement|ReturnNode)?))).
   18.29 +     * That is, the block has a single LabelNode statement with the label and a block containing the
   18.30 +     * statements of the inlined finally block with the jump or return statement appended (if the finally
   18.31 +     * block was not terminal; the original jump/return is simply ignored if the finally block itself
   18.32 +     * terminates). The reason for this somewhat strange arrangement is that we didn't want to create a
   18.33 +     * separate class for the (label, BlockStatement pair) but rather reused the already available LabelNode.
   18.34 +     * However, if we simply used List<LabelNode> without wrapping the label nodes in an additional Block,
   18.35 +     * that would've thrown off visitors relying on BlockLexicalContext -- same reason why we never use
   18.36 +     * Statement as the type of bodies of e.g. IfNode, WhileNode etc. but rather blockify them even when they're
   18.37 +     * single statements.
   18.38 +     */
   18.39 +    private final List<Block> inlinedFinallies;
   18.40 +
   18.41      /** Exception symbol. */
   18.42      private Symbol exception;
   18.43  
   18.44 -    /** Catchall exception for finally expansion, where applicable */
   18.45 -    private Symbol finallyCatchAll;
   18.46 -
   18.47      private final LocalVariableConversion conversion;
   18.48  
   18.49      /**
   18.50 @@ -71,21 +85,23 @@
   18.51          this.catchBlocks = catchBlocks;
   18.52          this.finallyBody = finallyBody;
   18.53          this.conversion  = null;
   18.54 +        this.inlinedFinallies = Collections.emptyList();
   18.55      }
   18.56  
   18.57 -    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion) {
   18.58 +    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
   18.59          super(tryNode);
   18.60          this.body        = body;
   18.61          this.catchBlocks = catchBlocks;
   18.62          this.finallyBody = finallyBody;
   18.63          this.conversion  = conversion;
   18.64 +        this.inlinedFinallies = inlinedFinallies;
   18.65          this.exception = tryNode.exception;
   18.66      }
   18.67  
   18.68      @Override
   18.69      public Node ensureUniqueLabels(final LexicalContext lc) {
   18.70          //try nodes are never in lex context
   18.71 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
   18.72 +        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
   18.73      }
   18.74  
   18.75      @Override
   18.76 @@ -106,16 +122,16 @@
   18.77       * @param visitor IR navigating visitor.
   18.78       */
   18.79      @Override
   18.80 -    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
   18.81 +    public Node accept(final LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) {
   18.82          if (visitor.enterTryNode(this)) {
   18.83              // Need to do finallybody first for termination analysis. TODO still necessary?
   18.84              final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
   18.85              final Block newBody        = (Block)body.accept(visitor);
   18.86              return visitor.leaveTryNode(
   18.87 -                setBody(newBody).
   18.88 -                setFinallyBody(newFinallyBody).
   18.89 -                setCatchBlocks(Node.accept(visitor, catchBlocks)).
   18.90 -                setFinallyCatchAll(finallyCatchAll));
   18.91 +                setBody(lc, newBody).
   18.92 +                setFinallyBody(lc, newFinallyBody).
   18.93 +                setCatchBlocks(lc, Node.accept(visitor, catchBlocks)).
   18.94 +                setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies)));
   18.95          }
   18.96  
   18.97          return this;
   18.98 @@ -136,14 +152,15 @@
   18.99  
  18.100      /**
  18.101       * Reset the body of this try block
  18.102 +     * @param lc current lexical context
  18.103       * @param body new body
  18.104       * @return new TryNode or same if unchanged
  18.105       */
  18.106 -    public TryNode setBody(final Block body) {
  18.107 +    public TryNode setBody(final LexicalContext lc, final Block body) {
  18.108          if (this.body == body) {
  18.109              return this;
  18.110          }
  18.111 -        return new TryNode(this,  body, catchBlocks, finallyBody, conversion);
  18.112 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies));
  18.113      }
  18.114  
  18.115      /**
  18.116 @@ -172,14 +189,15 @@
  18.117  
  18.118      /**
  18.119       * Set the catch blocks of this try
  18.120 +     * @param lc current lexical context
  18.121       * @param catchBlocks list of catch blocks
  18.122       * @return new TryNode or same if unchanged
  18.123       */
  18.124 -    public TryNode setCatchBlocks(final List<Block> catchBlocks) {
  18.125 +    public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) {
  18.126          if (this.catchBlocks == catchBlocks) {
  18.127              return this;
  18.128          }
  18.129 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
  18.130 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
  18.131      }
  18.132  
  18.133      /**
  18.134 @@ -200,27 +218,6 @@
  18.135      }
  18.136  
  18.137      /**
  18.138 -     * Get the catch all symbol for this try block
  18.139 -     * @return catch all symbol
  18.140 -     */
  18.141 -    public Symbol getFinallyCatchAll() {
  18.142 -        return this.finallyCatchAll;
  18.143 -    }
  18.144 -
  18.145 -    /**
  18.146 -     * If a finally block exists, the synthetic catchall needs another symbol to
  18.147 -     * store its throwable
  18.148 -     * @param finallyCatchAll a symbol for the finally catch all exception
  18.149 -     * @return new TryNode or same if unchanged
  18.150 -     *
  18.151 -     * TODO can this still be stateful?
  18.152 -     */
  18.153 -    public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) {
  18.154 -        this.finallyCatchAll = finallyCatchAll;
  18.155 -        return this;
  18.156 -    }
  18.157 -
  18.158 -    /**
  18.159       * Get the body of the finally clause for this try
  18.160       * @return finally body, or null if no finally
  18.161       */
  18.162 @@ -229,15 +226,87 @@
  18.163      }
  18.164  
  18.165      /**
  18.166 +     * Get the inlined finally block with the given label name. This returns the actual finally block in the
  18.167 +     * {@link LabelNode}, not the outer wrapper block for the {@link LabelNode}.
  18.168 +     * @param labelName the name of the inlined finally's label
  18.169 +     * @return the requested finally block, or null if no finally block's label matches the name.
  18.170 +     */
  18.171 +    public Block getInlinedFinally(final String labelName) {
  18.172 +        for(final Block inlinedFinally: inlinedFinallies) {
  18.173 +            final LabelNode labelNode = getInlinedFinallyLabelNode(inlinedFinally);
  18.174 +            if (labelNode.getLabelName().equals(labelName)) {
  18.175 +                return labelNode.getBody();
  18.176 +            }
  18.177 +        }
  18.178 +        return null;
  18.179 +    }
  18.180 +
  18.181 +    private static LabelNode getInlinedFinallyLabelNode(final Block inlinedFinally) {
  18.182 +        return (LabelNode)inlinedFinally.getStatements().get(0);
  18.183 +    }
  18.184 +
  18.185 +    /**
  18.186 +     * Given an outer wrapper block for the {@link LabelNode} as returned by {@link #getInlinedFinallies()},
  18.187 +     * returns its actual inlined finally block.
  18.188 +     * @param inlinedFinally the outer block for inlined finally, as returned as an element of
  18.189 +     * {@link #getInlinedFinallies()}.
  18.190 +     * @return the block contained in the {@link LabelNode} contained in the passed block.
  18.191 +     */
  18.192 +    public static Block getLabelledInlinedFinallyBlock(final Block inlinedFinally) {
  18.193 +        return getInlinedFinallyLabelNode(inlinedFinally).getBody();
  18.194 +    }
  18.195 +
  18.196 +    /**
  18.197 +     * Returns a list of inlined finally blocks. Note that this returns a list of {@link Block}s such that each one of
  18.198 +     * them has a single {@link LabelNode}, which in turn contains the label name for the finally block and the
  18.199 +     * actual finally block. To safely extract the actual finally block, use
  18.200 +     * {@link #getLabelledInlinedFinallyBlock(Block)}.
  18.201 +     * @return a list of inlined finally blocks.
  18.202 +     */
  18.203 +    public List<Block> getInlinedFinallies() {
  18.204 +        return Collections.unmodifiableList(inlinedFinallies);
  18.205 +    }
  18.206 +
  18.207 +    /**
  18.208       * Set the finally body of this try
  18.209 +     * @param lc current lexical context
  18.210       * @param finallyBody new finally body
  18.211       * @return new TryNode or same if unchanged
  18.212       */
  18.213 -    public TryNode setFinallyBody(final Block finallyBody) {
  18.214 +    public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) {
  18.215          if (this.finallyBody == finallyBody) {
  18.216              return this;
  18.217          }
  18.218 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
  18.219 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
  18.220 +    }
  18.221 +
  18.222 +    /**
  18.223 +     * Set the inlined finally blocks of this try. Each element should be a block with a single statement that is a
  18.224 +     * {@link LabelNode} with a unique label, and the block within the label node should contain the actual inlined
  18.225 +     * finally block.
  18.226 +     * @param lc current lexical context
  18.227 +     * @param inlinedFinallies list of inlined finally blocks
  18.228 +     * @return new TryNode or same if unchanged
  18.229 +     */
  18.230 +    public TryNode setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies) {
  18.231 +        if (this.inlinedFinallies == inlinedFinallies) {
  18.232 +            return this;
  18.233 +        }
  18.234 +        assert checkInlinedFinallies(inlinedFinallies);
  18.235 +        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
  18.236 +    }
  18.237 +
  18.238 +    private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
  18.239 +        if (!inlinedFinallies.isEmpty()) {
  18.240 +            final Set<String> labels = new HashSet<>();
  18.241 +            for (final Block inlinedFinally : inlinedFinallies) {
  18.242 +                final List<Statement> stmts = inlinedFinally.getStatements();
  18.243 +                assert stmts.size() == 1;
  18.244 +                final LabelNode ln = getInlinedFinallyLabelNode(inlinedFinally);
  18.245 +                assert labels.add(ln.getLabelName()); // unique label
  18.246 +            }
  18.247 +        }
  18.248 +        return true;
  18.249      }
  18.250  
  18.251      @Override
  18.252 @@ -245,7 +314,7 @@
  18.253          if(this.conversion == conversion) {
  18.254              return this;
  18.255          }
  18.256 -        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
  18.257 +        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
  18.258      }
  18.259  
  18.260      @Override
    19.1 --- a/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Dec 03 16:31:39 2014 +0100
    19.2 +++ b/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Jan 28 17:58:08 2015 +0100
    19.3 @@ -391,6 +391,9 @@
    19.4              finallyBody.accept(this);
    19.5          }
    19.6  
    19.7 +        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
    19.8 +            inlinedFinally.accept(this);
    19.9 +        }
   19.10          return false;
   19.11      }
   19.12  
    20.1 --- a/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Dec 03 16:31:39 2014 +0100
    20.2 +++ b/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Jan 28 17:58:08 2015 +0100
    20.3 @@ -43,6 +43,7 @@
    20.4  import jdk.nashorn.internal.ir.IfNode;
    20.5  import jdk.nashorn.internal.ir.IndexNode;
    20.6  import jdk.nashorn.internal.ir.JoinPredecessorExpression;
    20.7 +import jdk.nashorn.internal.ir.JumpToInlinedFinally;
    20.8  import jdk.nashorn.internal.ir.LabelNode;
    20.9  import jdk.nashorn.internal.ir.LexicalContext;
   20.10  import jdk.nashorn.internal.ir.LiteralNode;
   20.11 @@ -473,6 +474,26 @@
   20.12      }
   20.13  
   20.14      /**
   20.15 +     * Callback for entering a JumpToInlinedFinally
   20.16 +     *
   20.17 +     * @param  jumpToInlinedFinally the node
   20.18 +     * @return true if traversal should continue and node children be traversed, false otherwise
   20.19 +     */
   20.20 +    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
   20.21 +        return enterDefault(jumpToInlinedFinally);
   20.22 +    }
   20.23 +
   20.24 +    /**
   20.25 +     * Callback for leaving a JumpToInlinedFinally
   20.26 +     *
   20.27 +     * @param  jumpToInlinedFinally the node
   20.28 +     * @return processed node, which will replace the original one, or the original node
   20.29 +     */
   20.30 +    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
   20.31 +        return leaveDefault(jumpToInlinedFinally);
   20.32 +    }
   20.33 +
   20.34 +    /**
   20.35       * Callback for entering a LabelNode
   20.36       *
   20.37       * @param  labelNode the node
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/test/script/basic/JDK-8067139.js	Wed Jan 28 17:58:08 2015 +0100
    21.3 @@ -0,0 +1,92 @@
    21.4 +/*
    21.5 + * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
    21.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    21.7 + * 
    21.8 + * This code is free software; you can redistribute it and/or modify it
    21.9 + * under the terms of the GNU General Public License version 2 only, as
   21.10 + * published by the Free Software Foundation.
   21.11 + * 
   21.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
   21.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   21.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   21.15 + * version 2 for more details (a copy is included in the LICENSE file that
   21.16 + * accompanied this code).
   21.17 + * 
   21.18 + * You should have received a copy of the GNU General Public License version
   21.19 + * 2 along with this work; if not, write to the Free Software Foundation,
   21.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   21.21 + * 
   21.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   21.23 + * or visit www.oracle.com if you need additional information or have any
   21.24 + * questions.
   21.25 + */
   21.26 +
   21.27 +/**
   21.28 + * JDK-8067139: Finally blocks inlined incorrectly
   21.29 + *
   21.30 + * @test
   21.31 + * @run
   21.32 + */
   21.33 +
   21.34 +// Test case for JDK-8067139
   21.35 +// as well as for JDK-8030198 which is a duplicate.
   21.36 +(function(){
   21.37 +    var catchCount = 0; 
   21.38 +    try {
   21.39 +        (function (){
   21.40 +            try { 
   21.41 +                return; 
   21.42 +            } catch(x) { 
   21.43 +                ++catchCount;
   21.44 +            } finally { 
   21.45 +                throw 0; 
   21.46 +            } 
   21.47 +        })();
   21.48 +        Assert.fail(); // must throw
   21.49 +    } catch(e) {
   21.50 +        Assert.assertEquals(e, 0); // threw 0
   21.51 +        Assert.assertEquals(catchCount, 0); // inner catch never executed
   21.52 +    }
   21.53 +})();
   21.54 +
   21.55 +// Test case for JDK-8048862 which is a duplicate of this bug
   21.56 +var ret = (function(o) { 
   21.57 +    try{
   21.58 +        with(o) {
   21.59 +            return x;
   21.60 +        }
   21.61 +    } finally {
   21.62 +        try { 
   21.63 +            return x;
   21.64 +        } catch(e) {
   21.65 +            Assert.assertTrue(e instanceof ReferenceError);
   21.66 +            return 2;
   21.67 +        }
   21.68 +    }
   21.69 +})({x: 1});
   21.70 +Assert.assertEquals(ret, 2); // executed the catch block
   21.71 +
   21.72 +// Test cases for JDK-8066231 that is a duplicate of this bug
   21.73 +// Case 1
   21.74 +(function (){ try { Object; } catch(x if x >>>=0) { throw x2; } finally { } })();
   21.75 +// Case 2
   21.76 +try {
   21.77 +    (function (){ try { return; } catch(x) { return x ^= 0; } finally { throw 0; } })();
   21.78 +    Assert.fail();
   21.79 +} catch(e) {
   21.80 +    Assert.assertEquals(e, 0); // threw 0
   21.81 +}
   21.82 +// Case 3
   21.83 +try {
   21.84 +    (function (){ try { return; } catch(x) { return x ^= Object; } finally { throw Object; } })();
   21.85 +    Assert.fail();
   21.86 +} catch(e) {
   21.87 +    Assert.assertEquals(e, Object); // threw Object
   21.88 +}
   21.89 +// Case from comment
   21.90 +try {
   21.91 +    (function () { try { Object } catch(x) { (x=y); return; } finally { throw Object; } })();
   21.92 +    Assert.fail();
   21.93 +} catch(e) {
   21.94 +    Assert.assertEquals(e, Object); // threw Object
   21.95 +}

mercurial