1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.codegen;
  27 
  28 import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
  29 import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
  30 import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
  31 
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.Collections;
  35 import java.util.List;
  36 import java.util.ListIterator;
  37 import java.util.regex.Pattern;
  38 import jdk.nashorn.internal.ir.AccessNode;
  39 import jdk.nashorn.internal.ir.BaseNode;
  40 import jdk.nashorn.internal.ir.BinaryNode;
  41 import jdk.nashorn.internal.ir.Block;
  42 import jdk.nashorn.internal.ir.BlockLexicalContext;
  43 import jdk.nashorn.internal.ir.BlockStatement;
  44 import jdk.nashorn.internal.ir.BreakNode;
  45 import jdk.nashorn.internal.ir.CallNode;
  46 import jdk.nashorn.internal.ir.CaseNode;
  47 import jdk.nashorn.internal.ir.CatchNode;
  48 import jdk.nashorn.internal.ir.DebuggerNode;
  49 import jdk.nashorn.internal.ir.ContinueNode;
  50 import jdk.nashorn.internal.ir.EmptyNode;
  51 import jdk.nashorn.internal.ir.Expression;
  52 import jdk.nashorn.internal.ir.ExpressionStatement;
  53 import jdk.nashorn.internal.ir.ForNode;
  54 import jdk.nashorn.internal.ir.FunctionNode;
  55 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
  56 import jdk.nashorn.internal.ir.IdentNode;
  57 import jdk.nashorn.internal.ir.IfNode;
  58 import jdk.nashorn.internal.ir.IndexNode;
  59 import jdk.nashorn.internal.ir.JumpStatement;
  60 import jdk.nashorn.internal.ir.JumpToInlinedFinally;
  61 import jdk.nashorn.internal.ir.LabelNode;
  62 import jdk.nashorn.internal.ir.LexicalContext;
  63 import jdk.nashorn.internal.ir.LiteralNode;
  64 import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
  65 import jdk.nashorn.internal.ir.LoopNode;
  66 import jdk.nashorn.internal.ir.Node;
  67 import jdk.nashorn.internal.ir.ReturnNode;
  68 import jdk.nashorn.internal.ir.RuntimeNode;
  69 import jdk.nashorn.internal.ir.Statement;
  70 import jdk.nashorn.internal.ir.SwitchNode;
  71 import jdk.nashorn.internal.ir.Symbol;
  72 import jdk.nashorn.internal.ir.ThrowNode;
  73 import jdk.nashorn.internal.ir.TryNode;
  74 import jdk.nashorn.internal.ir.VarNode;
  75 import jdk.nashorn.internal.ir.WhileNode;
  76 import jdk.nashorn.internal.ir.WithNode;
  77 import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
  78 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  79 import jdk.nashorn.internal.parser.Token;
  80 import jdk.nashorn.internal.parser.TokenType;
  81 import jdk.nashorn.internal.runtime.Context;
  82 import jdk.nashorn.internal.runtime.JSType;
  83 import jdk.nashorn.internal.runtime.Source;
  84 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  85 import jdk.nashorn.internal.runtime.logging.Loggable;
  86 import jdk.nashorn.internal.runtime.logging.Logger;
  87 
  88 /**
  89  * Lower to more primitive operations. After lowering, an AST still has no symbols
  90  * and types, but several nodes have been turned into more low level constructs
  91  * and control flow termination criteria have been computed.
  92  *
  93  * We do things like code copying/inlining of finallies here, as it is much
  94  * harder and context dependent to do any code copying after symbols have been
  95  * finalized.
  96  */
  97 @Logger(name="lower")
  98 final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Loggable {
  99 
 100     private final DebugLogger log;
 101 
 102     // Conservative pattern to test if element names consist of characters valid for identifiers.
 103     // This matches any non-zero length alphanumeric string including _ and $ and not starting with a digit.
 104     private static Pattern SAFE_PROPERTY_NAME = Pattern.compile("[a-zA-Z_$][\\w$]*");
 105 
 106     /**
 107      * Constructor.
 108      */
 109     Lower(final Compiler compiler) {
 110         super(new BlockLexicalContext() {
 111 
 112             @Override
 113             public List<Statement> popStatements() {
 114                 final List<Statement> newStatements = new ArrayList<>();
 115                 boolean terminated = false;
 116 
 117                 final List<Statement> statements = super.popStatements();
 118                 for (final Statement statement : statements) {
 119                     if (!terminated) {
 120                         newStatements.add(statement);
 121                         if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why?
 122                             terminated = true;
 123                         }
 124                     } else {
 125                         statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 126                             @Override
 127                             public boolean enterVarNode(final VarNode varNode) {
 128                                 newStatements.add(varNode.setInit(null));
 129                                 return false;
 130                             }
 131                         });
 132                     }
 133                 }
 134                 return newStatements;
 135             }
 136 
 137             @Override
 138             protected Block afterSetStatements(final Block block) {
 139                 final List<Statement> stmts = block.getStatements();
 140                 for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) {
 141                     final Statement stmt = li.previous();
 142                     // popStatements() guarantees that the only thing after a terminal statement are uninitialized
 143                     // VarNodes. We skip past those, and set the terminal state of the block to the value of the
 144                     // terminal state of the first statement that is not an uninitialized VarNode.
 145                     if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) {
 146                         return block.setIsTerminal(this, stmt.isTerminal());
 147                     }
 148                 }
 149                 return block.setIsTerminal(this, false);
 150             }
 151         });
 152 
 153         this.log = initLogger(compiler.getContext());
 154     }
 155 
 156     @Override
 157     public DebugLogger getLogger() {
 158         return log;
 159     }
 160 
 161     @Override
 162     public DebugLogger initLogger(final Context context) {
 163         return context.getLogger(this.getClass());
 164     }
 165 
 166     @Override
 167     public boolean enterBreakNode(final BreakNode breakNode) {
 168         addStatement(breakNode);
 169         return false;
 170     }
 171 
 172     @Override
 173     public Node leaveCallNode(final CallNode callNode) {
 174         return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
 175     }
 176 
 177     @Override
 178     public Node leaveCatchNode(final CatchNode catchNode) {
 179         return addStatement(catchNode);
 180     }
 181 
 182     @Override
 183     public boolean enterContinueNode(final ContinueNode continueNode) {
 184         addStatement(continueNode);
 185         return false;
 186     }
 187 
 188     @Override
 189     public boolean enterDebuggerNode(final DebuggerNode debuggerNode) {
 190         final int line = debuggerNode.getLineNumber();
 191         final long token = debuggerNode.getToken();
 192         final int finish = debuggerNode.getFinish();
 193         addStatement(new ExpressionStatement(line, token, finish, new RuntimeNode(token, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Expression>())));
 194         return false;
 195     }
 196 
 197     @Override
 198     public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
 199         addStatement(jumpToInlinedFinally);
 200         return false;
 201     }
 202 
 203     @Override
 204     public boolean enterEmptyNode(final EmptyNode emptyNode) {
 205         return false;
 206     }
 207 
 208     @Override
 209     public Node leaveIndexNode(final IndexNode indexNode) {
 210         final String name = getConstantPropertyName(indexNode.getIndex());
 211         if (name != null) {
 212             // If index node is a constant property name convert index node to access node.
 213             assert Token.descType(indexNode.getToken()) == TokenType.LBRACKET;
 214             return new AccessNode(indexNode.getToken(), indexNode.getFinish(), indexNode.getBase(), name);
 215         }
 216         return super.leaveIndexNode(indexNode);
 217     }
 218 
 219     // If expression is a primitive literal that is not an array index and does return its string value. Else return null.
 220     private static String getConstantPropertyName(final Expression expression) {
 221         if (expression instanceof LiteralNode.PrimitiveLiteralNode) {
 222             final Object value = ((LiteralNode) expression).getValue();
 223             if (value instanceof String && SAFE_PROPERTY_NAME.matcher((String) value).matches()) {
 224                 return (String) value;
 225             }
 226         }
 227         return null;
 228     }
 229 
 230     @Override
 231     public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
 232         final Expression expr = expressionStatement.getExpression();
 233         ExpressionStatement node = expressionStatement;
 234 
 235         final FunctionNode currentFunction = lc.getCurrentFunction();
 236 
 237         if (currentFunction.isProgram()) {
 238             if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
 239                 node = expressionStatement.setExpression(
 240                     new BinaryNode(
 241                         Token.recast(
 242                             expressionStatement.getToken(),
 243                             TokenType.ASSIGN),
 244                         compilerConstant(RETURN),
 245                     expr));
 246             }
 247         }
 248 
 249         return addStatement(node);
 250     }
 251 
 252     @Override
 253     public Node leaveBlockStatement(final BlockStatement blockStatement) {
 254         return addStatement(blockStatement);
 255     }
 256 
 257     @Override
 258     public Node leaveForNode(final ForNode forNode) {
 259         ForNode newForNode = forNode;
 260 
 261         final Expression test = forNode.getTest();
 262         if (!forNode.isForIn() && isAlwaysTrue(test)) {
 263             newForNode = forNode.setTest(lc, null);
 264         }
 265 
 266         newForNode = checkEscape(newForNode);
 267         if(newForNode.isForIn()) {
 268             // Wrap it in a block so its internally created iterator is restricted in scope
 269             addStatementEnclosedInBlock(newForNode);
 270         } else {
 271             addStatement(newForNode);
 272         }
 273         return newForNode;
 274     }
 275 
 276     @Override
 277     public Node leaveFunctionNode(final FunctionNode functionNode) {
 278         log.info("END FunctionNode: ", functionNode.getName());
 279         return functionNode.setState(lc, CompilationState.LOWERED);
 280     }
 281 
 282     @Override
 283     public Node leaveIfNode(final IfNode ifNode) {
 284         return addStatement(ifNode);
 285     }
 286 
 287     @Override
 288     public Node leaveIN(final BinaryNode binaryNode) {
 289         return new RuntimeNode(binaryNode);
 290     }
 291 
 292     @Override
 293     public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
 294         return new RuntimeNode(binaryNode);
 295     }
 296 
 297     @Override
 298     public Node leaveLabelNode(final LabelNode labelNode) {
 299         return addStatement(labelNode);
 300     }
 301 
 302     @Override
 303     public Node leaveReturnNode(final ReturnNode returnNode) {
 304         addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
 305         return returnNode;
 306     }
 307 
 308     @Override
 309     public Node leaveCaseNode(final CaseNode caseNode) {
 310         // Try to represent the case test as an integer
 311         final Node test = caseNode.getTest();
 312         if (test instanceof LiteralNode) {
 313             final LiteralNode<?> lit = (LiteralNode<?>)test;
 314             if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
 315                 if (JSType.isRepresentableAsInt(lit.getNumber())) {
 316                     return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
 317                 }
 318             }
 319         }
 320         return caseNode;
 321     }
 322 
 323     @Override
 324     public Node leaveSwitchNode(final SwitchNode switchNode) {
 325         if(!switchNode.isUniqueInteger()) {
 326             // Wrap it in a block so its internally created tag is restricted in scope
 327             addStatementEnclosedInBlock(switchNode);
 328         } else {
 329             addStatement(switchNode);
 330         }
 331         return switchNode;
 332     }
 333 
 334     @Override
 335     public Node leaveThrowNode(final ThrowNode throwNode) {
 336         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
 337     }
 338 
 339     @SuppressWarnings("unchecked")
 340     private static <T extends Node> T ensureUniqueNamesIn(final T node) {
 341         return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 342             @Override
 343             public Node leaveFunctionNode(final FunctionNode functionNode) {
 344                 final String name = functionNode.getName();
 345                 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
 346             }
 347 
 348             @Override
 349             public Node leaveDefault(final Node labelledNode) {
 350                 return labelledNode.ensureUniqueLabels(lc);
 351             }
 352         });
 353     }
 354 
 355     private static Block createFinallyBlock(final Block finallyBody) {
 356         final List<Statement> newStatements = new ArrayList<>();
 357         for (final Statement statement : finallyBody.getStatements()) {
 358             newStatements.add(statement);
 359             if (statement.hasTerminalFlags()) {
 360                 break;
 361             }
 362         }
 363         return finallyBody.setStatements(null, newStatements);
 364     }
 365 
 366     private Block catchAllBlock(final TryNode tryNode) {
 367         final int  lineNumber = tryNode.getLineNumber();
 368         final long token      = tryNode.getToken();
 369         final int  finish     = tryNode.getFinish();
 370 
 371         final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
 372 
 373         final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true));
 374         assert catchBody.isTerminal(); //ends with throw, so terminal
 375 
 376         final CatchNode catchAllNode  = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true);
 377         final Block     catchAllBlock = new Block(token, finish, catchAllNode);
 378 
 379         //catchallblock -> catchallnode (catchnode) -> exception -> throw
 380 
 381         return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
 382     }
 383 
 384     private IdentNode compilerConstant(final CompilerConstants cc) {
 385         final FunctionNode functionNode = lc.getCurrentFunction();
 386         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
 387     }
 388 
 389     private static boolean isTerminalFinally(final Block finallyBlock) {
 390         return finallyBlock.getLastStatement().hasTerminalFlags();
 391     }
 392 
 393     /**
 394      * Splice finally code into all endpoints of a trynode
 395      * @param tryNode the try node
 396      * @param rethrow the rethrowing throw nodes from the synthetic catch block
 397      * @param finallyBody the code in the original finally block
 398      * @return new try node after splicing finally code (same if nop)
 399      */
 400     private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) {
 401         assert tryNode.getFinallyBody() == null;
 402 
 403         final Block finallyBlock = createFinallyBlock(finallyBody);
 404         final ArrayList<Block> inlinedFinallies = new ArrayList<>();
 405         final FunctionNode fn = lc.getCurrentFunction();
 406         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 407 
 408             @Override
 409             public boolean enterFunctionNode(final FunctionNode functionNode) {
 410                 // do not enter function nodes - finally code should not be inlined into them
 411                 return false;
 412             }
 413 
 414             @Override
 415             public Node leaveThrowNode(final ThrowNode throwNode) {
 416                 if (rethrow == throwNode) {
 417                     return new BlockStatement(prependFinally(finallyBlock, throwNode));
 418                 }
 419                 return throwNode;
 420             }
 421 
 422             @Override
 423             public Node leaveBreakNode(final BreakNode breakNode) {
 424                 return leaveJumpStatement(breakNode);
 425             }
 426 
 427             @Override
 428             public Node leaveContinueNode(final ContinueNode continueNode) {
 429                 return leaveJumpStatement(continueNode);
 430             }
 431 
 432             private Node leaveJumpStatement(final JumpStatement jump) {
 433                 // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and
 434                 // continue are edited. JTIF nodes should not be changed, rather the surroundings of
 435                 // break/continue/return that were moved into the inlined finally block itself will be changed.
 436 
 437                 // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block.
 438                 if (jump.getTarget(lc) == null) {
 439                     return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump));
 440                 }
 441                 return jump;
 442             }
 443 
 444             @Override
 445             public Node leaveReturnNode(final ReturnNode returnNode) {
 446                 final Expression expr = returnNode.getExpression();
 447                 if (isTerminalFinally(finallyBlock)) {
 448                     if (expr == null) {
 449                         // Terminal finally; no return expression.
 450                         return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock));
 451                     }
 452                     // Terminal finally; has a return expression.
 453                     final List<Statement> newStatements = new ArrayList<>(2);
 454                     final int retLineNumber = returnNode.getLineNumber();
 455                     final long retToken = returnNode.getToken();
 456                     // Expression is evaluated for side effects.
 457                     newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr));
 458                     newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)));
 459                     return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements));
 460                 } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) {
 461                     // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return.
 462                     // Just move the return expression into the finally block.
 463                     return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode));
 464                 } else {
 465                     // We need to evaluate the result of the return in case it is complex while still in the try block,
 466                     // store it in :return, and return it afterwards.
 467                     final List<Statement> newStatements = new ArrayList<>();
 468                     final int retLineNumber = returnNode.getLineNumber();
 469                     final long retToken = returnNode.getToken();
 470                     final int retFinish = returnNode.getFinish();
 471                     final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName());
 472                     // ":return = <expr>;"
 473                     newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
 474                     // inline finally and end it with "return :return;"
 475                     newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode))));
 476                     return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements));
 477                 }
 478             }
 479         });
 480         addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies));
 481         // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here.
 482         addStatement(new BlockStatement(finallyBlock));
 483 
 484         return newTryNode;
 485     }
 486 
 487     private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) {
 488         final String labelName = fn.uniqueName(":finally");
 489         final long token = finallyBlock.getToken();
 490         final int finish = finallyBlock.getFinish();
 491         inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(),
 492                 token, finish, labelName, finallyBlock)));
 493         return new JumpToInlinedFinally(labelName);
 494     }
 495 
 496     private static Block prependFinally(final Block finallyBlock, final Statement statement) {
 497         final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock);
 498         if (isTerminalFinally(finallyBlock)) {
 499             return inlinedFinally;
 500         }
 501         final List<Statement> stmts = inlinedFinally.getStatements();
 502         final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1);
 503         newStmts.addAll(stmts);
 504         newStmts.add(statement);
 505         return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts);
 506     }
 507 
 508     @Override
 509     public Node leaveTryNode(final TryNode tryNode) {
 510         final Block finallyBody = tryNode.getFinallyBody();
 511         TryNode newTryNode = tryNode.setFinallyBody(lc, null);
 512 
 513         // No finally or empty finally
 514         if (finallyBody == null || finallyBody.getStatementCount() == 0) {
 515             final List<CatchNode> catches = newTryNode.getCatches();
 516             if (catches == null || catches.isEmpty()) {
 517                 // A completely degenerate try block: empty finally, no catches. Replace it with try body.
 518                 return addStatement(new BlockStatement(tryNode.getBody()));
 519             }
 520             return addStatement(ensureUnconditionalCatch(newTryNode));
 521         }
 522 
 523         /*
 524          * create a new trynode
 525          *    if we have catches:
 526          *
 527          *    try            try
 528          *       x              try
 529          *    catch               x
 530          *       y              catch
 531          *    finally z           y
 532          *                   catchall
 533          *                        rethrow
 534          *
 535          *   otheriwse
 536          *
 537          *   try              try
 538          *      x               x
 539          *   finally          catchall
 540          *      y               rethrow
 541          *
 542          *
 543          *   now splice in finally code wherever needed
 544          *
 545          */
 546         final Block catchAll = catchAllBlock(tryNode);
 547 
 548         final List<ThrowNode> rethrows = new ArrayList<>(1);
 549         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 550             @Override
 551             public boolean enterThrowNode(final ThrowNode throwNode) {
 552                 rethrows.add(throwNode);
 553                 return true;
 554             }
 555         });
 556         assert rethrows.size() == 1;
 557 
 558         if (!tryNode.getCatchBlocks().isEmpty()) {
 559             final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode));
 560             newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null);
 561         }
 562 
 563         newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll));
 564 
 565         /*
 566          * Now that the transform is done, we have to go into the try and splice
 567          * the finally block in front of any statement that is outside the try
 568          */
 569         return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody));
 570     }
 571 
 572     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
 573         final List<CatchNode> catches = tryNode.getCatches();
 574         if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
 575             return tryNode;
 576         }
 577         // If the last catch block is conditional, add an unconditional rethrow block
 578         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
 579 
 580         newCatchBlocks.add(catchAllBlock(tryNode));
 581         return tryNode.setCatchBlocks(lc, newCatchBlocks);
 582     }
 583 
 584     @Override
 585     public Node leaveVarNode(final VarNode varNode) {
 586         addStatement(varNode);
 587         if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) {
 588             new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
 589         }
 590         return varNode;
 591     }
 592 
 593     @Override
 594     public Node leaveWhileNode(final WhileNode whileNode) {
 595         final Expression test = whileNode.getTest();
 596         final Block body = whileNode.getBody();
 597 
 598         if (isAlwaysTrue(test)) {
 599             //turn it into a for node without a test.
 600             final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this);
 601             lc.replace(whileNode, forNode);
 602             return forNode;
 603         }
 604 
 605          return addStatement(checkEscape(whileNode));
 606     }
 607 
 608     @Override
 609     public Node leaveWithNode(final WithNode withNode) {
 610         return addStatement(withNode);
 611     }
 612 
 613     /**
 614      * Given a function node that is a callee in a CallNode, replace it with
 615      * the appropriate marker function. This is used by {@link CodeGenerator}
 616      * for fast scope calls
 617      *
 618      * @param function function called by a CallNode
 619      * @return transformed node to marker function or identity if not ident/access/indexnode
 620      */
 621     private static Expression markerFunction(final Expression function) {
 622         if (function instanceof IdentNode) {
 623             return ((IdentNode)function).setIsFunction();
 624         } else if (function instanceof BaseNode) {
 625             return ((BaseNode)function).setIsFunction();
 626         }
 627         return function;
 628     }
 629 
 630     /**
 631      * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval>
 632      * @param node a node
 633      * @return eval location
 634      */
 635     private String evalLocation(final IdentNode node) {
 636         final Source source = lc.getCurrentFunction().getSource();
 637         final int pos = node.position();
 638         return new StringBuilder().
 639             append(source.getName()).
 640             append('#').
 641             append(source.getLine(pos)).
 642             append(':').
 643             append(source.getColumn(pos)).
 644             append("<eval>").
 645             toString();
 646     }
 647 
 648     /**
 649      * Check whether a call node may be a call to eval. In that case we
 650      * clone the args in order to create the following construct in
 651      * {@link CodeGenerator}
 652      *
 653      * <pre>
 654      * if (calledFuntion == buildInEval) {
 655      *    eval(cloned arg);
 656      * } else {
 657      *    cloned arg;
 658      * }
 659      * </pre>
 660      *
 661      * @param callNode call node to check if it's an eval
 662      */
 663     private CallNode checkEval(final CallNode callNode) {
 664         if (callNode.getFunction() instanceof IdentNode) {
 665 
 666             final List<Expression> args = callNode.getArgs();
 667             final IdentNode callee = (IdentNode)callNode.getFunction();
 668 
 669             // 'eval' call with at least one argument
 670             if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
 671                 final List<Expression> evalArgs = new ArrayList<>(args.size());
 672                 for(final Expression arg: args) {
 673                     evalArgs.add((Expression)ensureUniqueNamesIn(arg).accept(this));
 674                 }
 675                 return callNode.setEvalArgs(new CallNode.EvalArgs(evalArgs, evalLocation(callee)));
 676             }
 677         }
 678 
 679         return callNode;
 680     }
 681 
 682     /**
 683      * Helper that given a loop body makes sure that it is not terminal if it
 684      * has a continue that leads to the loop header or to outer loops' loop
 685      * headers. This means that, even if the body ends with a terminal
 686      * statement, we cannot tag it as terminal
 687      *
 688      * @param loopBody the loop body to check
 689      * @return true if control flow may escape the loop
 690      */
 691     private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
 692         final List<Node> escapes = new ArrayList<>();
 693 
 694         loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 695             @Override
 696             public Node leaveBreakNode(final BreakNode node) {
 697                 escapes.add(node);
 698                 return node;
 699             }
 700 
 701             @Override
 702             public Node leaveContinueNode(final ContinueNode node) {
 703                 // all inner loops have been popped.
 704                 if (lex.contains(node.getTarget(lex))) {
 705                     escapes.add(node);
 706                 }
 707                 return node;
 708             }
 709         });
 710 
 711         return !escapes.isEmpty();
 712     }
 713 
 714     @SuppressWarnings("unchecked")
 715     private <T extends LoopNode> T checkEscape(final T loopNode) {
 716         final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
 717         if (escapes) {
 718             return (T)loopNode.
 719                 setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
 720                 setControlFlowEscapes(lc, escapes);
 721         }
 722         return loopNode;
 723     }
 724 
 725 
 726     private Node addStatement(final Statement statement) {
 727         lc.appendStatement(statement);
 728         return statement;
 729     }
 730 
 731     private void addStatementEnclosedInBlock(final Statement stmt) {
 732         BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt));
 733         if(stmt.isTerminal()) {
 734             b = b.setBlock(b.getBlock().setIsTerminal(null, true));
 735         }
 736         addStatement(b);
 737     }
 738 
 739     /**
 740      * An internal expression has a symbol that is tagged internal. Check if
 741      * this is such a node
 742      *
 743      * @param expression expression to check for internal symbol
 744      * @return true if internal, false otherwise
 745      */
 746     private static boolean isInternalExpression(final Expression expression) {
 747         if (!(expression instanceof IdentNode)) {
 748             return false;
 749         }
 750         final Symbol symbol = ((IdentNode)expression).getSymbol();
 751         return symbol != null && symbol.isInternal();
 752     }
 753 
 754     /**
 755      * Is this an assignment to the special variable that hosts scripting eval
 756      * results, i.e. __return__?
 757      *
 758      * @param expression expression to check whether it is $evalresult = X
 759      * @return true if an assignment to eval result, false otherwise
 760      */
 761     private static boolean isEvalResultAssignment(final Node expression) {
 762         final Node e = expression;
 763         if (e instanceof BinaryNode) {
 764             final Node lhs = ((BinaryNode)e).lhs();
 765             if (lhs instanceof IdentNode) {
 766                 return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
 767             }
 768         }
 769         return false;
 770     }
 771 }