# HG changeset patch # User attila # Date 1422464288 -3600 # Node ID 78317797ab62198bf37bc2384a0d16f29b3003e4 # Parent b2f9702efbe95527ea3a991474fda23987ff1c5c 8067139: Finally blocks inlined incorrectly Reviewed-by: hannesw, lagergren diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java --- 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); diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- 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; } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java --- 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 pop(final T node) { + public T pop(final T node) { final T popped = super.pop(node); if (isWithBoundary(node)) { dynamicScopeCount--; diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java --- 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 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 getBreakTargetTypes(final BreakableNode target) { + private Map getBreakTargetTypes(final LexicalContextNode target) { // Remove symbols defined in the the blocks that are being broken out of. Map types = localVariableTypes; for(final Iterator 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; } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java --- 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(new LexicalContext()) { + private static T ensureUniqueNamesIn(final T node) { + return (T)node.accept(new NodeVisitor(new LexicalContext()) { @Override public Node leaveFunctionNode(final FunctionNode functionNode) { final String name = functionNode.getName(); @@ -333,15 +341,15 @@ }); } - private static List copyFinally(final Block finallyBody) { + private static Block createFinallyBlock(final Block finallyBody) { final List 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 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 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 inlinedFinallies = new ArrayList<>(); + final FunctionNode fn = lc.getCurrentFunction(); final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor(new LexicalContext()) { - final List 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 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 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 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 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 = ;" + 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 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 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 stmts = inlinedFinally.getStatements(); + final List 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 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 rethrows = new ArrayList<>(); + final List rethrows = new ArrayList<>(1); catchAll.accept(new NodeVisitor(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 newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks()); newCatchBlocks.add(catchAllBlock(tryNode)); - return tryNode.setCatchBlocks(newCatchBlocks); + return tryNode.setCatchBlocks(lc, newCatchBlocks); } @Override diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java --- 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(); diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/WeighNodes.java --- 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) { diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java --- 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 diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockLexicalContext.java --- 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 pop(final T node) { + public T pop(final T node) { T expected = node; if (node instanceof Block) { final List newStatements = popStatements(); diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BlockStatement.java --- 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 */ diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BreakNode.java --- 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(); } } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ContinueNode.java --- 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(); } } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpStatement.java --- 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) { diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JumpToInlinedFinally.java --- /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 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(); + } +} diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java --- 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 pop(final T node) { + public 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 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 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 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"); diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContextNode.java --- 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 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); } } } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/OptimisticLexicalContext.java --- 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 pop(final T node) { + public T pop(final T node) { final T popped = super.pop(node); if (isEnabled) { if(node instanceof FunctionNode) { diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TryNode.java --- 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 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 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 catchBlocks, final Block finallyBody, final LocalVariableConversion conversion) { + private TryNode(final TryNode tryNode, final Block body, final List catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List 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 visitor) { + public Node accept(final LexicalContext lc, NodeVisitor 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 catchBlocks) { + public TryNode setCatchBlocks(final LexicalContext lc, final List 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 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 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 inlinedFinallies) { + if (!inlinedFinallies.isEmpty()) { + final Set labels = new HashSet<>(); + for (final Block inlinedFinally : inlinedFinallies) { + final List 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 diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/PrintVisitor.java --- 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; } diff -r b2f9702efbe9 -r 78317797ab62 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/visitor/NodeVisitor.java --- 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 diff -r b2f9702efbe9 -r 78317797ab62 nashorn/test/script/basic/JDK-8067139.js --- /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 +}