8067139: Finally blocks inlined incorrectly
authorattila
Wed, 28 Jan 2015 17:58:08 +0100
changeset 28690 78317797ab62
parent 28597 b2f9702efbe9
child 28691 bdc353778a28
8067139: Finally blocks inlined incorrectly Reviewed-by: hannesw, lagergren
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/WeighNodes.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockLexicalContext.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockStatement.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BreakNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ContinueNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpStatement.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpToInlinedFinally.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContextNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/OptimisticLexicalContext.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/PrintVisitor.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
nashorn/test/script/basic/JDK-8067139.js
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Jan 28 17:58:08 2015 +0100
@@ -926,9 +926,7 @@
     @Override
     public Node leaveTryNode(final TryNode tryNode) {
         tryNode.setException(exceptionSymbol());
-        if (tryNode.getFinallyBody() != null) {
-            tryNode.setFinallyCatchAll(exceptionSymbol());
-        }
+        assert tryNode.getFinallyBody() == null;
 
         end(tryNode);
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Jan 28 17:58:08 2015 +0100
@@ -85,7 +85,6 @@
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.BlockStatement;
 import jdk.nashorn.internal.ir.BreakNode;
-import jdk.nashorn.internal.ir.BreakableNode;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
@@ -102,6 +101,7 @@
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LexicalContextNode;
@@ -1110,7 +1110,14 @@
 
     @Override
     public boolean enterBlock(final Block block) {
-        method.label(block.getEntryLabel());
+        final Label entryLabel = block.getEntryLabel();
+        if (entryLabel.isBreakTarget()) {
+            // Entry label is a break target only for an inlined finally block.
+            assert !method.isReachable();
+            method.breakLabel(entryLabel, lc.getUsedSlotCount());
+        } else {
+            method.label(entryLabel);
+        }
         if(!method.isReachable()) {
             return false;
         }
@@ -1240,6 +1247,11 @@
         return enterJumpStatement(breakNode);
     }
 
+    @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterJumpStatement(jumpToInlinedFinally);
+    }
+
     private boolean enterJumpStatement(final JumpStatement jump) {
         if(!method.isReachable()) {
             return false;
@@ -1247,9 +1259,8 @@
         enterStatement(jump);
 
         method.beforeJoinPoint(jump);
-        final BreakableNode target = jump.getTarget(lc);
-        popScopesUntil(target);
-        final Label targetLabel = jump.getTargetLabel(target);
+        popScopesUntil(jump.getPopScopeLimit(lc));
+        final Label targetLabel = jump.getTargetLabel(lc);
         targetLabel.markAsBreakTarget();
         method._goto(targetLabel);
 
@@ -3053,6 +3064,14 @@
         if (method.isReachable()) {
             method._goto(skip);
         }
+
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            TryNode.getLabelledInlinedFinallyBlock(inlinedFinally).accept(this);
+            // All inlined finallies end with a jump or a return
+            assert !method.isReachable();
+        }
+
+
         method._catch(recovery);
         method.store(vmException, EXCEPTION_TYPE);
 
@@ -3112,15 +3131,14 @@
             catchBody.accept(this);
             leaveBlock(catchBlock);
             lc.pop(catchBlock);
-            if(method.isReachable()) {
-                method._goto(afterCatch);
-            }
             if(nextCatch != null) {
+                if(method.isReachable()) {
+                    method._goto(afterCatch);
+                }
                 method.breakLabel(nextCatch, lc.getUsedSlotCount());
             }
         }
 
-        assert !method.isReachable();
         // afterCatch could be the same as skip, except that we need to establish that the vmException is dead.
         method.label(afterCatch);
         if(method.isReachable()) {
@@ -3129,6 +3147,8 @@
         method.label(skip);
 
         // Finally body is always inlined elsewhere so it doesn't need to be emitted
+        assert tryNode.getFinallyBody() == null;
+
         return false;
     }
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
@@ -74,7 +74,7 @@
     /** size of next free slot vector */
     private int nextFreeSlotsSize;
 
-    private boolean isWithBoundary(final LexicalContextNode node) {
+    private boolean isWithBoundary(final Object node) {
         return node instanceof Block && !isEmpty() && peek() instanceof WithNode;
     }
 
@@ -102,7 +102,7 @@
     }
 
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         final T popped = super.pop(node);
         if (isWithBoundary(node)) {
             dynamicScopeCount--;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Jan 28 17:58:08 2015 +0100
@@ -62,6 +62,7 @@
 import jdk.nashorn.internal.ir.JoinPredecessor;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LexicalContextNode;
@@ -529,8 +530,7 @@
             return false;
         }
         assertTypeStackIsEmpty();
-        final BreakableNode target = jump.getTarget(lc);
-        jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target));
+        jumpToLabel(jump, jump.getTargetLabel(lc), getBreakTargetTypes(jump.getPopScopeLimit(lc)));
         doesNotContinueSequentially();
         return false;
     }
@@ -784,6 +784,11 @@
     }
 
     @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterJumpStatement(jumpToInlinedFinally);
+    }
+
+    @Override
     public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
         if (literalNode instanceof ArrayLiteralNode) {
             final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions();
@@ -1042,6 +1047,17 @@
         }
         doesNotContinueSequentially();
 
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            final Block finallyBody = TryNode.getLabelledInlinedFinallyBlock(inlinedFinally);
+            joinOnLabel(finallyBody.getEntryLabel());
+            // NOTE: the jump to inlined finally can end up in dead code, so it is not necessarily reachable.
+            if (reachable) {
+                finallyBody.accept(this);
+                // All inlined finallies end with a jump or a return
+                assert !reachable;
+            }
+        }
+
         joinOnLabel(catchLabel);
         for(final CatchNode catchNode: tryNode.getCatches()) {
             final IdentNode exception = catchNode.getException();
@@ -1125,7 +1141,7 @@
         return false;
     };
 
-    private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) {
+    private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode target) {
         // Remove symbols defined in the the blocks that are being broken out of.
         Map<Symbol, LvarType> types = localVariableTypes;
         for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
@@ -1380,7 +1396,11 @@
                 if(node instanceof JoinPredecessor) {
                     final JoinPredecessor original = joinPredecessors.pop();
                     assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName();
-                    return (Node)setLocalVariableConversion(original, (JoinPredecessor)node);
+                    final JoinPredecessor newNode = setLocalVariableConversion(original, (JoinPredecessor)node);
+                    if (newNode instanceof LexicalContextNode) {
+                        lc.replace((LexicalContextNode)node, (LexicalContextNode)newNode);
+                    }
+                    return (Node)newNode;
                 }
                 return node;
             }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Wed Jan 28 17:58:08 2015 +0100
@@ -56,9 +56,11 @@
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
 import jdk.nashorn.internal.ir.LoopNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ReturnNode;
@@ -115,7 +117,7 @@
                 for (final Statement statement : statements) {
                     if (!terminated) {
                         newStatements.add(statement);
-                        if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
+                        if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why?
                             terminated = true;
                         }
                     } else {
@@ -183,6 +185,12 @@
     }
 
     @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        addStatement(jumpToInlinedFinally);
+        return false;
+    }
+
+    @Override
     public boolean enterEmptyNode(final EmptyNode emptyNode) {
         return false;
     }
@@ -318,8 +326,8 @@
         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
     }
 
-    private static Node ensureUniqueNamesIn(final Node node) {
-        return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+    private static <T extends Node> T ensureUniqueNamesIn(final T node) {
+        return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
             @Override
             public Node leaveFunctionNode(final FunctionNode functionNode) {
                 final String name = functionNode.getName();
@@ -333,15 +341,15 @@
         });
     }
 
-    private static List<Statement> copyFinally(final Block finallyBody) {
+    private static Block createFinallyBlock(final Block finallyBody) {
         final List<Statement> newStatements = new ArrayList<>();
         for (final Statement statement : finallyBody.getStatements()) {
-            newStatements.add((Statement)ensureUniqueNamesIn(statement));
+            newStatements.add(statement);
             if (statement.hasTerminalFlags()) {
-                return newStatements;
+                break;
             }
         }
-        return newStatements;
+        return finallyBody.setStatements(null, newStatements);
     }
 
     private Block catchAllBlock(final TryNode tryNode) {
@@ -367,28 +375,24 @@
         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
     }
 
-    private static boolean isTerminal(final List<Statement> statements) {
-        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
+    private static boolean isTerminalFinally(final Block finallyBlock) {
+        return finallyBlock.getLastStatement().hasTerminalFlags();
     }
 
     /**
      * Splice finally code into all endpoints of a trynode
      * @param tryNode the try node
-     * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
+     * @param rethrow the rethrowing throw nodes from the synthetic catch block
      * @param finallyBody the code in the original finally block
      * @return new try node after splicing finally code (same if nop)
      */
-    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
+    private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) {
         assert tryNode.getFinallyBody() == null;
 
+        final Block finallyBlock = createFinallyBlock(finallyBody);
+        final ArrayList<Block> inlinedFinallies = new ArrayList<>();
+        final FunctionNode fn = lc.getCurrentFunction();
         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
-            final List<Node> insideTry = new ArrayList<>();
-
-            @Override
-            public boolean enterDefault(final Node node) {
-                insideTry.add(node);
-                return true;
-            }
 
             @Override
             public boolean enterFunctionNode(final FunctionNode functionNode) {
@@ -398,12 +402,8 @@
 
             @Override
             public Node leaveThrowNode(final ThrowNode throwNode) {
-                if (rethrows.contains(throwNode)) {
-                    final List<Statement> newStatements = copyFinally(finallyBody);
-                    if (!isTerminal(newStatements)) {
-                        newStatements.add(throwNode);
-                    }
-                    return BlockStatement.createReplacement(throwNode, newStatements);
+                if (rethrow == throwNode) {
+                    return new BlockStatement(prependFinally(finallyBlock, throwNode));
                 }
                 return throwNode;
             }
@@ -419,58 +419,94 @@
             }
 
             private Node leaveJumpStatement(final JumpStatement jump) {
-                return copy(jump, (Node)jump.getTarget(Lower.this.lc));
+                // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and
+                // continue are edited. JTIF nodes should not be changed, rather the surroundings of
+                // break/continue/return that were moved into the inlined finally block itself will be changed.
+
+                // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block.
+                if (jump.getTarget(lc) == null) {
+                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump));
+                }
+                return jump;
             }
 
             @Override
             public Node leaveReturnNode(final ReturnNode returnNode) {
-                final Expression expr  = returnNode.getExpression();
-                final List<Statement> newStatements = new ArrayList<>();
-
-                final Expression resultNode;
-                if (expr != null) {
-                    //we need to evaluate the result of the return in case it is complex while
-                    //still in the try block, store it in a result value and return it afterwards
-                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
-                    newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
+                final Expression expr = returnNode.getExpression();
+                if (isTerminalFinally(finallyBlock)) {
+                    if (expr == null) {
+                        // Terminal finally; no return expression.
+                        return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock));
+                    }
+                    // Terminal finally; has a return expression.
+                    final List<Statement> newStatements = new ArrayList<>(2);
+                    final int retLineNumber = returnNode.getLineNumber();
+                    final long retToken = returnNode.getToken();
+                    // Expression is evaluated for side effects.
+                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr));
+                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)));
+                    return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements));
+                } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) {
+                    // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return.
+                    // Just move the return expression into the finally block.
+                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode));
                 } else {
-                    resultNode = null;
+                    // We need to evaluate the result of the return in case it is complex while still in the try block,
+                    // store it in :return, and return it afterwards.
+                    final List<Statement> newStatements = new ArrayList<>();
+                    final int retLineNumber = returnNode.getLineNumber();
+                    final long retToken = returnNode.getToken();
+                    final int retFinish = returnNode.getFinish();
+                    final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName());
+                    // ":return = <expr>;"
+                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
+                    // inline finally and end it with "return :return;"
+                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode))));
+                    return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements));
                 }
-
-                newStatements.addAll(copyFinally(finallyBody));
-                if (!isTerminal(newStatements)) {
-                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
-                }
-
-                return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
-            }
-
-            private Node copy(final Statement endpoint, final Node targetNode) {
-                if (!insideTry.contains(targetNode)) {
-                    final List<Statement> newStatements = copyFinally(finallyBody);
-                    if (!isTerminal(newStatements)) {
-                        newStatements.add(endpoint);
-                    }
-                    return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
-                }
-                return endpoint;
             }
         });
-
-        addStatement(newTryNode);
-        for (final Node statement : finallyBody.getStatements()) {
-            addStatement((Statement)statement);
-        }
+        addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies));
+        // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here.
+        addStatement(new BlockStatement(finallyBlock));
 
         return newTryNode;
     }
 
+    private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) {
+        final String labelName = fn.uniqueName(":finally");
+        final long token = finallyBlock.getToken();
+        final int finish = finallyBlock.getFinish();
+        inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(),
+                token, finish, labelName, finallyBlock)));
+        return new JumpToInlinedFinally(labelName);
+    }
+
+    private static Block prependFinally(final Block finallyBlock, final Statement statement) {
+        final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock);
+        if (isTerminalFinally(finallyBlock)) {
+            return inlinedFinally;
+        }
+        final List<Statement> stmts = inlinedFinally.getStatements();
+        final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1);
+        newStmts.addAll(stmts);
+        newStmts.add(statement);
+        return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts);
+    }
+
     @Override
     public Node leaveTryNode(final TryNode tryNode) {
         final Block finallyBody = tryNode.getFinallyBody();
+        TryNode newTryNode = tryNode.setFinallyBody(lc, null);
 
-        if (finallyBody == null) {
-            return addStatement(ensureUnconditionalCatch(tryNode));
+        // No finally or empty finally
+        if (finallyBody == null || finallyBody.getStatementCount() == 0) {
+            final List<CatchNode> catches = newTryNode.getCatches();
+            if (catches == null || catches.isEmpty()) {
+                // A completely degenerate try block: empty finally, no catches. Replace it with try body.
+                return addStatement(new BlockStatement(tryNode.getBody()));
+            }
+            return addStatement(ensureUnconditionalCatch(newTryNode));
         }
 
         /*
@@ -496,11 +532,9 @@
          *   now splice in finally code wherever needed
          *
          */
-        TryNode newTryNode;
-
         final Block catchAll = catchAllBlock(tryNode);
 
-        final List<ThrowNode> rethrows = new ArrayList<>();
+        final List<ThrowNode> rethrows = new ArrayList<>(1);
         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
             @Override
             public boolean enterThrowNode(final ThrowNode throwNode) {
@@ -510,20 +544,18 @@
         });
         assert rethrows.size() == 1;
 
-        if (tryNode.getCatchBlocks().isEmpty()) {
-            newTryNode = tryNode.setFinallyBody(null);
-        } else {
-            final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
-            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
+        if (!tryNode.getCatchBlocks().isEmpty()) {
+            final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode));
+            newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null);
         }
 
-        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
+        newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll));
 
         /*
          * Now that the transform is done, we have to go into the try and splice
          * the finally block in front of any statement that is outside the try
          */
-        return spliceFinally(newTryNode, rethrows, finallyBody);
+        return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody));
     }
 
     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
@@ -535,7 +567,7 @@
         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
 
         newCatchBlocks.add(catchAllBlock(tryNode));
-        return tryNode.setCatchBlocks(newCatchBlocks);
+        return tryNode.setCatchBlocks(lc, newCatchBlocks);
     }
 
     @Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Jan 28 17:58:08 2015 +0100
@@ -52,6 +52,7 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ReturnNode;
@@ -359,6 +360,11 @@
         return leaveJumpNode(continueNode);
     }
 
+    @Override
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return leaveJumpNode(jumpToInlinedFinally);
+    }
+
     private JumpStatement leaveJumpNode(final JumpStatement jump) {
         if (inSplitNode()) {
             final SplitState splitState = getCurrentSplitState();
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Jan 28 17:58:08 2015 +0100
@@ -40,6 +40,7 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
@@ -197,6 +198,12 @@
         return indexNode;
     }
 
+    @Override
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        weight += BREAK_WEIGHT;
+        return jumpToInlinedFinally;
+    }
+
     @SuppressWarnings("rawtypes")
     @Override
     public boolean enterLiteralNode(final LiteralNode literalNode) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java	Wed Jan 28 17:58:08 2015 +0100
@@ -322,6 +322,14 @@
     }
 
     /**
+     * Returns the last statement in the block.
+     * @return the last statement in the block, or null if the block has no statements.
+     */
+    public Statement getLastStatement() {
+        return statements.isEmpty() ? null : statements.get(statements.size() - 1);
+    }
+
+    /**
      * Reset the statement list for this block
      *
      * @param lc lexical context
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
@@ -74,7 +74,7 @@
 
     @SuppressWarnings("unchecked")
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         T expected = node;
         if (node instanceof Block) {
             final List<Statement> newStatements = popStatements();
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockStatement.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockStatement.java	Wed Jan 28 17:58:08 2015 +0100
@@ -40,6 +40,15 @@
     /**
      * Constructor
      *
+     * @param block the block to execute
+     */
+    public BlockStatement(final Block block) {
+        this(block.getFirstStatementLineNumber(), block);
+    }
+
+    /**
+     * Constructor
+     *
      * @param lineNumber line number
      * @param block the block to execute
      */
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BreakNode.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BreakNode.java	Wed Jan 28 17:58:08 2015 +0100
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public Label getTargetLabel(final BreakableNode target) {
+    Label getTargetLabel(final BreakableNode target) {
         return target.getBreakLabel();
     }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ContinueNode.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ContinueNode.java	Wed Jan 28 17:58:08 2015 +0100
@@ -78,7 +78,7 @@
     }
 
     @Override
-    public Label getTargetLabel(final BreakableNode target) {
+    Label getTargetLabel(final BreakableNode target) {
         return ((LoopNode)target).getContinueLabel();
     }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpStatement.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpStatement.java	Wed Jan 28 17:58:08 2015 +0100
@@ -101,7 +101,26 @@
      * @throws ClassCastException if invoked on the kind of breakable node that this jump statement is not prepared to
      * handle.
      */
-    public abstract Label getTargetLabel(final BreakableNode target);
+    abstract Label getTargetLabel(final BreakableNode target);
+
+    /**
+     * Returns the label this jump statement targets.
+     * @param lc the lexical context
+     * @return the label this jump statement targets.
+     */
+    public Label getTargetLabel(final LexicalContext lc) {
+        return getTargetLabel(getTarget(lc));
+    }
+
+    /**
+     * Returns the limit node for popping scopes when this jump statement is effected.
+     * @param lc the current lexical context
+     * @return the limit node for popping scopes when this jump statement is effected.
+     */
+    public LexicalContextNode getPopScopeLimit(final LexicalContext lc) {
+        // In most cases (break and continue) this is equal to the target.
+        return getTarget(lc);
+    }
 
     @Override
     public JumpStatement setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpToInlinedFinally.java	Wed Jan 28 17:58:08 2015 +0100
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.ir;
+
+import java.util.Objects;
+import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+
+/**
+ * IR representation for synthetic jump into an inlined finally statement.
+ */
+@Immutable
+public final class JumpToInlinedFinally extends JumpStatement {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructor
+     *
+     * @param labelName  label name for inlined finally block
+     */
+    public JumpToInlinedFinally(final String labelName) {
+        super(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, Objects.requireNonNull(labelName));
+    }
+
+    private JumpToInlinedFinally(final JumpToInlinedFinally breakNode, final LocalVariableConversion conversion) {
+        super(breakNode, conversion);
+    }
+
+    @Override
+    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
+        if (visitor.enterJumpToInlinedFinally(this)) {
+            return visitor.leaveJumpToInlinedFinally(this);
+        }
+
+        return this;
+    }
+
+    @Override
+    JumpStatement createNewJumpStatement(final LocalVariableConversion conversion) {
+        return new JumpToInlinedFinally(this, conversion);
+    }
+
+    @Override
+    String getStatementName() {
+        return ":jumpToInlinedFinally";
+    }
+
+    @Override
+    public Block getTarget(final LexicalContext lc) {
+        return lc.getInlinedFinally(getLabelName());
+    }
+
+    @Override
+    public TryNode getPopScopeLimit(final LexicalContext lc) {
+        // Returns the try node to which this jump's target belongs. This will make scope popping also pop the scope
+        // for the body of the try block, if it needs scope.
+        return lc.getTryNodeForInlinedFinally(getLabelName());
+    }
+
+    @Override
+    Label getTargetLabel(final BreakableNode target) {
+        assert target != null;
+        // We're jumping to the entry of the inlined finally block
+        return ((Block)target).getEntryLabel();
+    }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
@@ -190,7 +190,7 @@
      * @return the node that was popped
      */
     @SuppressWarnings("unchecked")
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         --sp;
         final LexicalContextNode popped = stack[sp];
         stack[sp] = null;
@@ -469,7 +469,7 @@
      * scopes that need to be explicitly popped in order to perform a break or continue jump within the current bytecode
      * method. For this reason, the method returns 0 if it encounters a {@code SplitNode} between the current location
      * and the break/continue target.
-     * @param until node to stop counting at. Must be within the current  function
+     * @param until node to stop counting at. Must be within the current function
      * @return number of with scopes encountered in the context
      */
     public int getScopeNestingLevelTo(final LexicalContextNode until) {
@@ -565,11 +565,41 @@
     }
 
     /**
+     * Find the inlined finally block node corresponding to this label.
+     * @param labelName label name to search for. Must not be null.
+     * @return closest inlined finally block with the given label
+     */
+    public Block getInlinedFinally(final String labelName) {
+        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
+            final Block inlinedFinally = iter.next().getInlinedFinally(labelName);
+            if (inlinedFinally != null) {
+                return inlinedFinally;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Find the try node for an inlined finally block corresponding to this label.
+     * @param labelName label name to search for. Must not be null.
+     * @return the try node to which the labelled inlined finally block belongs.
+     */
+    public TryNode getTryNodeForInlinedFinally(final String labelName) {
+        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
+            final TryNode tryNode = iter.next();
+            if (tryNode.getInlinedFinally(labelName) != null) {
+                return tryNode;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Check the lexical context for a given label node by name
      * @param name name of the label
      * @return LabelNode if found, null otherwise
      */
-    public LabelNode findLabel(final String name) {
+    private LabelNode findLabel(final String name) {
         for (final Iterator<LabelNode> iter = new NodeIterator<>(LabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
             final LabelNode next = iter.next();
             if (next.getLabelName().equals(name)) {
@@ -592,6 +622,12 @@
                 return true;
             } else if (next == target) {
                 return false;
+            } else if (next instanceof TryNode) {
+                for(final Block inlinedFinally: ((TryNode)next).getInlinedFinallies()) {
+                    if (TryNode.getLabelledInlinedFinallyBlock(inlinedFinally) == target) {
+                        return false;
+                    }
+                }
             }
         }
         throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't");
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Jan 28 17:58:08 2015 +0100
@@ -54,8 +54,8 @@
         static Node accept(final LexicalContextNode node, final NodeVisitor<? extends LexicalContext> visitor) {
             final LexicalContext lc = visitor.getLexicalContext();
             lc.push(node);
-            final LexicalContextNode newNode = (LexicalContextNode)node.accept(lc, visitor);
-            return (Node)lc.pop(newNode);
+            final Node newNode = node.accept(lc, visitor);
+            return lc.pop(newNode);
         }
     }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Jan 28 17:58:08 2015 +0100
@@ -115,7 +115,7 @@
     }
 
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         final T popped = super.pop(node);
         if (isEnabled) {
             if(node instanceof FunctionNode) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java	Wed Jan 28 17:58:08 2015 +0100
@@ -27,7 +27,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 
@@ -35,7 +37,7 @@
  * IR representation of a TRY statement.
  */
 @Immutable
-public final class TryNode extends Statement implements JoinPredecessor {
+public final class TryNode extends LexicalContextStatement implements JoinPredecessor {
     private static final long serialVersionUID = 1L;
 
     /** Try statements. */
@@ -47,12 +49,24 @@
     /** Finally clause. */
     private final Block finallyBody;
 
+    /**
+     * List of inlined finally blocks. The structure of every inlined finally is:
+     * Block(LabelNode(label, Block(finally-statements, (JumpStatement|ReturnNode)?))).
+     * That is, the block has a single LabelNode statement with the label and a block containing the
+     * statements of the inlined finally block with the jump or return statement appended (if the finally
+     * block was not terminal; the original jump/return is simply ignored if the finally block itself
+     * terminates). The reason for this somewhat strange arrangement is that we didn't want to create a
+     * separate class for the (label, BlockStatement pair) but rather reused the already available LabelNode.
+     * However, if we simply used List<LabelNode> without wrapping the label nodes in an additional Block,
+     * that would've thrown off visitors relying on BlockLexicalContext -- same reason why we never use
+     * Statement as the type of bodies of e.g. IfNode, WhileNode etc. but rather blockify them even when they're
+     * single statements.
+     */
+    private final List<Block> inlinedFinallies;
+
     /** Exception symbol. */
     private Symbol exception;
 
-    /** Catchall exception for finally expansion, where applicable */
-    private Symbol finallyCatchAll;
-
     private final LocalVariableConversion conversion;
 
     /**
@@ -71,21 +85,23 @@
         this.catchBlocks = catchBlocks;
         this.finallyBody = finallyBody;
         this.conversion  = null;
+        this.inlinedFinallies = Collections.emptyList();
     }
 
-    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion) {
+    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
         super(tryNode);
         this.body        = body;
         this.catchBlocks = catchBlocks;
         this.finallyBody = finallyBody;
         this.conversion  = conversion;
+        this.inlinedFinallies = inlinedFinallies;
         this.exception = tryNode.exception;
     }
 
     @Override
     public Node ensureUniqueLabels(final LexicalContext lc) {
         //try nodes are never in lex context
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
     }
 
     @Override
@@ -106,16 +122,16 @@
      * @param visitor IR navigating visitor.
      */
     @Override
-    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
+    public Node accept(final LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) {
         if (visitor.enterTryNode(this)) {
             // Need to do finallybody first for termination analysis. TODO still necessary?
             final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
             final Block newBody        = (Block)body.accept(visitor);
             return visitor.leaveTryNode(
-                setBody(newBody).
-                setFinallyBody(newFinallyBody).
-                setCatchBlocks(Node.accept(visitor, catchBlocks)).
-                setFinallyCatchAll(finallyCatchAll));
+                setBody(lc, newBody).
+                setFinallyBody(lc, newFinallyBody).
+                setCatchBlocks(lc, Node.accept(visitor, catchBlocks)).
+                setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies)));
         }
 
         return this;
@@ -136,14 +152,15 @@
 
     /**
      * Reset the body of this try block
+     * @param lc current lexical context
      * @param body new body
      * @return new TryNode or same if unchanged
      */
-    public TryNode setBody(final Block body) {
+    public TryNode setBody(final LexicalContext lc, final Block body) {
         if (this.body == body) {
             return this;
         }
-        return new TryNode(this,  body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies));
     }
 
     /**
@@ -172,14 +189,15 @@
 
     /**
      * Set the catch blocks of this try
+     * @param lc current lexical context
      * @param catchBlocks list of catch blocks
      * @return new TryNode or same if unchanged
      */
-    public TryNode setCatchBlocks(final List<Block> catchBlocks) {
+    public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) {
         if (this.catchBlocks == catchBlocks) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
     }
 
     /**
@@ -200,27 +218,6 @@
     }
 
     /**
-     * Get the catch all symbol for this try block
-     * @return catch all symbol
-     */
-    public Symbol getFinallyCatchAll() {
-        return this.finallyCatchAll;
-    }
-
-    /**
-     * If a finally block exists, the synthetic catchall needs another symbol to
-     * store its throwable
-     * @param finallyCatchAll a symbol for the finally catch all exception
-     * @return new TryNode or same if unchanged
-     *
-     * TODO can this still be stateful?
-     */
-    public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) {
-        this.finallyCatchAll = finallyCatchAll;
-        return this;
-    }
-
-    /**
      * Get the body of the finally clause for this try
      * @return finally body, or null if no finally
      */
@@ -229,15 +226,87 @@
     }
 
     /**
+     * Get the inlined finally block with the given label name. This returns the actual finally block in the
+     * {@link LabelNode}, not the outer wrapper block for the {@link LabelNode}.
+     * @param labelName the name of the inlined finally's label
+     * @return the requested finally block, or null if no finally block's label matches the name.
+     */
+    public Block getInlinedFinally(final String labelName) {
+        for(final Block inlinedFinally: inlinedFinallies) {
+            final LabelNode labelNode = getInlinedFinallyLabelNode(inlinedFinally);
+            if (labelNode.getLabelName().equals(labelName)) {
+                return labelNode.getBody();
+            }
+        }
+        return null;
+    }
+
+    private static LabelNode getInlinedFinallyLabelNode(final Block inlinedFinally) {
+        return (LabelNode)inlinedFinally.getStatements().get(0);
+    }
+
+    /**
+     * Given an outer wrapper block for the {@link LabelNode} as returned by {@link #getInlinedFinallies()},
+     * returns its actual inlined finally block.
+     * @param inlinedFinally the outer block for inlined finally, as returned as an element of
+     * {@link #getInlinedFinallies()}.
+     * @return the block contained in the {@link LabelNode} contained in the passed block.
+     */
+    public static Block getLabelledInlinedFinallyBlock(final Block inlinedFinally) {
+        return getInlinedFinallyLabelNode(inlinedFinally).getBody();
+    }
+
+    /**
+     * Returns a list of inlined finally blocks. Note that this returns a list of {@link Block}s such that each one of
+     * them has a single {@link LabelNode}, which in turn contains the label name for the finally block and the
+     * actual finally block. To safely extract the actual finally block, use
+     * {@link #getLabelledInlinedFinallyBlock(Block)}.
+     * @return a list of inlined finally blocks.
+     */
+    public List<Block> getInlinedFinallies() {
+        return Collections.unmodifiableList(inlinedFinallies);
+    }
+
+    /**
      * Set the finally body of this try
+     * @param lc current lexical context
      * @param finallyBody new finally body
      * @return new TryNode or same if unchanged
      */
-    public TryNode setFinallyBody(final Block finallyBody) {
+    public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) {
         if (this.finallyBody == finallyBody) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+    }
+
+    /**
+     * Set the inlined finally blocks of this try. Each element should be a block with a single statement that is a
+     * {@link LabelNode} with a unique label, and the block within the label node should contain the actual inlined
+     * finally block.
+     * @param lc current lexical context
+     * @param inlinedFinallies list of inlined finally blocks
+     * @return new TryNode or same if unchanged
+     */
+    public TryNode setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies) {
+        if (this.inlinedFinallies == inlinedFinallies) {
+            return this;
+        }
+        assert checkInlinedFinallies(inlinedFinallies);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+    }
+
+    private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
+        if (!inlinedFinallies.isEmpty()) {
+            final Set<String> labels = new HashSet<>();
+            for (final Block inlinedFinally : inlinedFinallies) {
+                final List<Statement> stmts = inlinedFinally.getStatements();
+                assert stmts.size() == 1;
+                final LabelNode ln = getInlinedFinallyLabelNode(inlinedFinally);
+                assert labels.add(ln.getLabelName()); // unique label
+            }
+        }
+        return true;
     }
 
     @Override
@@ -245,7 +314,7 @@
         if(this.conversion == conversion) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
     }
 
     @Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Jan 28 17:58:08 2015 +0100
@@ -391,6 +391,9 @@
             finallyBody.accept(this);
         }
 
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            inlinedFinally.accept(this);
+        }
         return false;
     }
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Jul 05 20:16:23 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Jan 28 17:58:08 2015 +0100
@@ -43,6 +43,7 @@
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
@@ -473,6 +474,26 @@
     }
 
     /**
+     * Callback for entering a JumpToInlinedFinally
+     *
+     * @param  jumpToInlinedFinally the node
+     * @return true if traversal should continue and node children be traversed, false otherwise
+     */
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterDefault(jumpToInlinedFinally);
+    }
+
+    /**
+     * Callback for leaving a JumpToInlinedFinally
+     *
+     * @param  jumpToInlinedFinally the node
+     * @return processed node, which will replace the original one, or the original node
+     */
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return leaveDefault(jumpToInlinedFinally);
+    }
+
+    /**
      * Callback for entering a LabelNode
      *
      * @param  labelNode the node
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8067139.js	Wed Jan 28 17:58:08 2015 +0100
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8067139: Finally blocks inlined incorrectly
+ *
+ * @test
+ * @run
+ */
+
+// Test case for JDK-8067139
+// as well as for JDK-8030198 which is a duplicate.
+(function(){
+    var catchCount = 0; 
+    try {
+        (function (){
+            try { 
+                return; 
+            } catch(x) { 
+                ++catchCount;
+            } finally { 
+                throw 0; 
+            } 
+        })();
+        Assert.fail(); // must throw
+    } catch(e) {
+        Assert.assertEquals(e, 0); // threw 0
+        Assert.assertEquals(catchCount, 0); // inner catch never executed
+    }
+})();
+
+// Test case for JDK-8048862 which is a duplicate of this bug
+var ret = (function(o) { 
+    try{
+        with(o) {
+            return x;
+        }
+    } finally {
+        try { 
+            return x;
+        } catch(e) {
+            Assert.assertTrue(e instanceof ReferenceError);
+            return 2;
+        }
+    }
+})({x: 1});
+Assert.assertEquals(ret, 2); // executed the catch block
+
+// Test cases for JDK-8066231 that is a duplicate of this bug
+// Case 1
+(function (){ try { Object; } catch(x if x >>>=0) { throw x2; } finally { } })();
+// Case 2
+try {
+    (function (){ try { return; } catch(x) { return x ^= 0; } finally { throw 0; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, 0); // threw 0
+}
+// Case 3
+try {
+    (function (){ try { return; } catch(x) { return x ^= Object; } finally { throw Object; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, Object); // threw Object
+}
+// Case from comment
+try {
+    (function () { try { Object } catch(x) { (x=y); return; } finally { throw Object; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, Object); // threw Object
+}