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 jdk.nashorn.internal.ir.BaseNode;
  38 import jdk.nashorn.internal.ir.BinaryNode;
  39 import jdk.nashorn.internal.ir.Block;
  40 import jdk.nashorn.internal.ir.BlockLexicalContext;
  41 import jdk.nashorn.internal.ir.BlockStatement;
  42 import jdk.nashorn.internal.ir.BreakNode;
  43 import jdk.nashorn.internal.ir.CallNode;
  44 import jdk.nashorn.internal.ir.CaseNode;
  45 import jdk.nashorn.internal.ir.CatchNode;
  46 import jdk.nashorn.internal.ir.ContinueNode;
  47 import jdk.nashorn.internal.ir.EmptyNode;
  48 import jdk.nashorn.internal.ir.Expression;
  49 import jdk.nashorn.internal.ir.ExpressionStatement;
  50 import jdk.nashorn.internal.ir.ForNode;
  51 import jdk.nashorn.internal.ir.FunctionNode;
  52 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
  53 import jdk.nashorn.internal.ir.IdentNode;
  54 import jdk.nashorn.internal.ir.IfNode;
  55 import jdk.nashorn.internal.ir.JumpStatement;
  56 import jdk.nashorn.internal.ir.LabelNode;
  57 import jdk.nashorn.internal.ir.LexicalContext;
  58 import jdk.nashorn.internal.ir.LiteralNode;
  59 import jdk.nashorn.internal.ir.LoopNode;
  60 import jdk.nashorn.internal.ir.Node;
  61 import jdk.nashorn.internal.ir.ReturnNode;
  62 import jdk.nashorn.internal.ir.RuntimeNode;
  63 import jdk.nashorn.internal.ir.Statement;
  64 import jdk.nashorn.internal.ir.SwitchNode;
  65 import jdk.nashorn.internal.ir.Symbol;
  66 import jdk.nashorn.internal.ir.ThrowNode;
  67 import jdk.nashorn.internal.ir.TryNode;
  68 import jdk.nashorn.internal.ir.VarNode;
  69 import jdk.nashorn.internal.ir.WhileNode;
  70 import jdk.nashorn.internal.ir.WithNode;
  71 import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
  72 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  73 import jdk.nashorn.internal.parser.Token;
  74 import jdk.nashorn.internal.parser.TokenType;
  75 import jdk.nashorn.internal.runtime.Context;
  76 import jdk.nashorn.internal.runtime.JSType;
  77 import jdk.nashorn.internal.runtime.Source;
  78 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  79 import jdk.nashorn.internal.runtime.logging.Loggable;
  80 import jdk.nashorn.internal.runtime.logging.Logger;
  81 
  82 /**
  83  * Lower to more primitive operations. After lowering, an AST still has no symbols
  84  * and types, but several nodes have been turned into more low level constructs
  85  * and control flow termination criteria have been computed.
  86  *
  87  * We do things like code copying/inlining of finallies here, as it is much
  88  * harder and context dependent to do any code copying after symbols have been
  89  * finalized.
  90  */
  91 @Logger(name="lower")
  92 final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Loggable {
  93 
  94     private final DebugLogger log;
  95 
  96     /**
  97      * Constructor.
  98      */
  99     Lower(final Compiler compiler) {
 100         super(new BlockLexicalContext() {
 101 
 102             @Override
 103             public List<Statement> popStatements() {
 104                 final List<Statement> newStatements = new ArrayList<>();
 105                 boolean terminated = false;
 106 
 107                 final List<Statement> statements = super.popStatements();
 108                 for (final Statement statement : statements) {
 109                     if (!terminated) {
 110                         newStatements.add(statement);
 111                         if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
 112                             terminated = true;
 113                         }
 114                     } else {
 115                         statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 116                             @Override
 117                             public boolean enterVarNode(final VarNode varNode) {
 118                                 newStatements.add(varNode.setInit(null));
 119                                 return false;
 120                             }
 121                         });
 122                     }
 123                 }
 124                 return newStatements;
 125             }
 126 
 127             @Override
 128             protected Block afterSetStatements(final Block block) {
 129                 final List<Statement> stmts = block.getStatements();
 130                 for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) {
 131                     final Statement stmt = li.previous();
 132                     // popStatements() guarantees that the only thing after a terminal statement are uninitialized
 133                     // VarNodes. We skip past those, and set the terminal state of the block to the value of the
 134                     // terminal state of the first statement that is not an uninitialized VarNode.
 135                     if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) {
 136                         return block.setIsTerminal(this, stmt.isTerminal());
 137                     }
 138                 }
 139                 return block.setIsTerminal(this, false);
 140             }
 141         });
 142 
 143         this.log       = initLogger(compiler.getContext());
 144     }
 145 
 146     @Override
 147     public DebugLogger getLogger() {
 148         return log;
 149     }
 150 
 151     @Override
 152     public DebugLogger initLogger(final Context context) {
 153         return context.getLogger(this.getClass());
 154     }
 155 
 156     @Override
 157     public boolean enterBreakNode(final BreakNode breakNode) {
 158         addStatement(breakNode);
 159         return false;
 160     }
 161 
 162     @Override
 163     public Node leaveCallNode(final CallNode callNode) {
 164         return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
 165     }
 166 
 167     @Override
 168     public Node leaveCatchNode(final CatchNode catchNode) {
 169         return addStatement(catchNode);
 170     }
 171 
 172     @Override
 173     public boolean enterContinueNode(final ContinueNode continueNode) {
 174         addStatement(continueNode);
 175         return false;
 176     }
 177 
 178     @Override
 179     public boolean enterEmptyNode(final EmptyNode emptyNode) {
 180         return false;
 181     }
 182 
 183     @Override
 184     public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
 185         final Expression expr = expressionStatement.getExpression();
 186         ExpressionStatement node = expressionStatement;
 187 
 188         final FunctionNode currentFunction = lc.getCurrentFunction();
 189 
 190         if (currentFunction.isProgram()) {
 191             if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
 192                 node = expressionStatement.setExpression(
 193                     new BinaryNode(
 194                         Token.recast(
 195                             expressionStatement.getToken(),
 196                             TokenType.ASSIGN),
 197                         compilerConstant(RETURN),
 198                     expr));
 199             }
 200         }
 201 
 202         return addStatement(node);
 203     }
 204 
 205     @Override
 206     public Node leaveBlockStatement(final BlockStatement blockStatement) {
 207         return addStatement(blockStatement);
 208     }
 209 
 210     @Override
 211     public Node leaveForNode(final ForNode forNode) {
 212         ForNode newForNode = forNode;
 213 
 214         final Expression test = forNode.getTest();
 215         if (!forNode.isForIn() && isAlwaysTrue(test)) {
 216             newForNode = forNode.setTest(lc, null);
 217         }
 218 
 219         newForNode = checkEscape(newForNode);
 220         if(newForNode.isForIn()) {
 221             // Wrap it in a block so its internally created iterator is restricted in scope
 222             addStatementEnclosedInBlock(newForNode);
 223         } else {
 224             addStatement(newForNode);
 225         }
 226         return newForNode;
 227     }
 228 
 229     @Override
 230     public Node leaveFunctionNode(final FunctionNode functionNode) {
 231         log.info("END FunctionNode: ", functionNode.getName());
 232         return functionNode.setState(lc, CompilationState.LOWERED);
 233     }
 234 
 235     @Override
 236     public Node leaveIfNode(final IfNode ifNode) {
 237         return addStatement(ifNode);
 238     }
 239 
 240     @Override
 241     public Node leaveIN(final BinaryNode binaryNode) {
 242         return new RuntimeNode(binaryNode);
 243     }
 244 
 245     @Override
 246     public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
 247         return new RuntimeNode(binaryNode);
 248     }
 249 
 250     @Override
 251     public Node leaveLabelNode(final LabelNode labelNode) {
 252         return addStatement(labelNode);
 253     }
 254 
 255     @Override
 256     public Node leaveReturnNode(final ReturnNode returnNode) {
 257         addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
 258         return returnNode;
 259     }
 260 
 261     @Override
 262     public Node leaveCaseNode(final CaseNode caseNode) {
 263         // Try to represent the case test as an integer
 264         final Node test = caseNode.getTest();
 265         if (test instanceof LiteralNode) {
 266             final LiteralNode<?> lit = (LiteralNode<?>)test;
 267             if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
 268                 if (JSType.isRepresentableAsInt(lit.getNumber())) {
 269                     return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
 270                 }
 271             }
 272         }
 273         return caseNode;
 274     }
 275 
 276     @Override
 277     public Node leaveSwitchNode(final SwitchNode switchNode) {
 278         if(!switchNode.isInteger()) {
 279             // Wrap it in a block so its internally created tag is restricted in scope
 280             addStatementEnclosedInBlock(switchNode);
 281         } else {
 282             addStatement(switchNode);
 283         }
 284         return switchNode;
 285     }
 286 
 287     @Override
 288     public Node leaveThrowNode(final ThrowNode throwNode) {
 289         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
 290     }
 291 
 292     private static Node ensureUniqueNamesIn(final Node node) {
 293         return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 294             @Override
 295             public Node leaveFunctionNode(final FunctionNode functionNode) {
 296                 final String name = functionNode.getName();
 297                 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
 298             }
 299 
 300             @Override
 301             public Node leaveDefault(final Node labelledNode) {
 302                 return labelledNode.ensureUniqueLabels(lc);
 303             }
 304         });
 305     }
 306 
 307     private static List<Statement> copyFinally(final Block finallyBody) {
 308         final List<Statement> newStatements = new ArrayList<>();
 309         for (final Statement statement : finallyBody.getStatements()) {
 310             newStatements.add((Statement)ensureUniqueNamesIn(statement));
 311             if (statement.hasTerminalFlags()) {
 312                 return newStatements;
 313             }
 314         }
 315         return newStatements;
 316     }
 317 
 318     private Block catchAllBlock(final TryNode tryNode) {
 319         final int  lineNumber = tryNode.getLineNumber();
 320         final long token      = tryNode.getToken();
 321         final int  finish     = tryNode.getFinish();
 322 
 323         final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
 324 
 325         final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true));
 326         assert catchBody.isTerminal(); //ends with throw, so terminal
 327 
 328         final CatchNode catchAllNode  = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true);
 329         final Block     catchAllBlock = new Block(token, finish, catchAllNode);
 330 
 331         //catchallblock -> catchallnode (catchnode) -> exception -> throw
 332 
 333         return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
 334     }
 335 
 336     private IdentNode compilerConstant(final CompilerConstants cc) {
 337         final FunctionNode functionNode = lc.getCurrentFunction();
 338         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
 339     }
 340 
 341     private static boolean isTerminal(final List<Statement> statements) {
 342         return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
 343     }
 344 
 345     /**
 346      * Splice finally code into all endpoints of a trynode
 347      * @param tryNode the try node
 348      * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
 349      * @param finallyBody the code in the original finally block
 350      * @return new try node after splicing finally code (same if nop)
 351      */
 352     private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
 353         assert tryNode.getFinallyBody() == null;
 354 
 355         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 356             final List<Node> insideTry = new ArrayList<>();
 357 
 358             @Override
 359             public boolean enterDefault(final Node node) {
 360                 insideTry.add(node);
 361                 return true;
 362             }
 363 
 364             @Override
 365             public boolean enterFunctionNode(final FunctionNode functionNode) {
 366                 // do not enter function nodes - finally code should not be inlined into them
 367                 return false;
 368             }
 369 
 370             @Override
 371             public Node leaveThrowNode(final ThrowNode throwNode) {
 372                 if (rethrows.contains(throwNode)) {
 373                     final List<Statement> newStatements = copyFinally(finallyBody);
 374                     if (!isTerminal(newStatements)) {
 375                         newStatements.add(throwNode);
 376                     }
 377                     return BlockStatement.createReplacement(throwNode, newStatements);
 378                 }
 379                 return throwNode;
 380             }
 381 
 382             @Override
 383             public Node leaveBreakNode(final BreakNode breakNode) {
 384                 return leaveJumpStatement(breakNode);
 385             }
 386 
 387             @Override
 388             public Node leaveContinueNode(final ContinueNode continueNode) {
 389                 return leaveJumpStatement(continueNode);
 390             }
 391 
 392             private Node leaveJumpStatement(final JumpStatement jump) {
 393                 return copy(jump, (Node)jump.getTarget(Lower.this.lc));
 394             }
 395 
 396             @Override
 397             public Node leaveReturnNode(final ReturnNode returnNode) {
 398                 final Expression expr  = returnNode.getExpression();
 399                 final List<Statement> newStatements = new ArrayList<>();
 400 
 401                 final Expression resultNode;
 402                 if (expr != null) {
 403                     //we need to evaluate the result of the return in case it is complex while
 404                     //still in the try block, store it in a result value and return it afterwards
 405                     resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
 406                     newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
 407                 } else {
 408                     resultNode = null;
 409                 }
 410 
 411                 newStatements.addAll(copyFinally(finallyBody));
 412                 if (!isTerminal(newStatements)) {
 413                     newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
 414                 }
 415 
 416                 return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
 417             }
 418 
 419             private Node copy(final Statement endpoint, final Node targetNode) {
 420                 if (!insideTry.contains(targetNode)) {
 421                     final List<Statement> newStatements = copyFinally(finallyBody);
 422                     if (!isTerminal(newStatements)) {
 423                         newStatements.add(endpoint);
 424                     }
 425                     return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
 426                 }
 427                 return endpoint;
 428             }
 429         });
 430 
 431         addStatement(newTryNode);
 432         for (final Node statement : finallyBody.getStatements()) {
 433             addStatement((Statement)statement);
 434         }
 435 
 436         return newTryNode;
 437     }
 438 
 439     @Override
 440     public Node leaveTryNode(final TryNode tryNode) {
 441         final Block finallyBody = tryNode.getFinallyBody();
 442 
 443         if (finallyBody == null) {
 444             return addStatement(ensureUnconditionalCatch(tryNode));
 445         }
 446 
 447         /*
 448          * create a new trynode
 449          *    if we have catches:
 450          *
 451          *    try            try
 452          *       x              try
 453          *    catch               x
 454          *       y              catch
 455          *    finally z           y
 456          *                   catchall
 457          *                        rethrow
 458          *
 459          *   otheriwse
 460          *
 461          *   try              try
 462          *      x               x
 463          *   finally          catchall
 464          *      y               rethrow
 465          *
 466          *
 467          *   now splice in finally code wherever needed
 468          *
 469          */
 470         TryNode newTryNode;
 471 
 472         final Block catchAll = catchAllBlock(tryNode);
 473 
 474         final List<ThrowNode> rethrows = new ArrayList<>();
 475         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 476             @Override
 477             public boolean enterThrowNode(final ThrowNode throwNode) {
 478                 rethrows.add(throwNode);
 479                 return true;
 480             }
 481         });
 482         assert rethrows.size() == 1;
 483 
 484         if (tryNode.getCatchBlocks().isEmpty()) {
 485             newTryNode = tryNode.setFinallyBody(null);
 486         } else {
 487             final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
 488             newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
 489         }
 490 
 491         newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
 492 
 493         /*
 494          * Now that the transform is done, we have to go into the try and splice
 495          * the finally block in front of any statement that is outside the try
 496          */
 497         return spliceFinally(newTryNode, rethrows, finallyBody);
 498     }
 499 
 500     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
 501         final List<CatchNode> catches = tryNode.getCatches();
 502         if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
 503             return tryNode;
 504         }
 505         // If the last catch block is conditional, add an unconditional rethrow block
 506         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
 507 
 508         newCatchBlocks.add(catchAllBlock(tryNode));
 509         return tryNode.setCatchBlocks(newCatchBlocks);
 510     }
 511 
 512     @Override
 513     public Node leaveVarNode(final VarNode varNode) {
 514         addStatement(varNode);
 515         if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) {
 516             new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
 517         }
 518         return varNode;
 519     }
 520 
 521     @Override
 522     public Node leaveWhileNode(final WhileNode whileNode) {
 523         final Expression test = whileNode.getTest();
 524         final Block body = whileNode.getBody();
 525 
 526         if (isAlwaysTrue(test)) {
 527             //turn it into a for node without a test.
 528             final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this);
 529             lc.replace(whileNode, forNode);
 530             return forNode;
 531         }
 532 
 533          return addStatement(checkEscape(whileNode));
 534     }
 535 
 536     @Override
 537     public Node leaveWithNode(final WithNode withNode) {
 538         return addStatement(withNode);
 539     }
 540 
 541     /**
 542      * Given a function node that is a callee in a CallNode, replace it with
 543      * the appropriate marker function. This is used by {@link CodeGenerator}
 544      * for fast scope calls
 545      *
 546      * @param function function called by a CallNode
 547      * @return transformed node to marker function or identity if not ident/access/indexnode
 548      */
 549     private static Expression markerFunction(final Expression function) {
 550         if (function instanceof IdentNode) {
 551             return ((IdentNode)function).setIsFunction();
 552         } else if (function instanceof BaseNode) {
 553             return ((BaseNode)function).setIsFunction();
 554         }
 555         return function;
 556     }
 557 
 558     /**
 559      * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval>
 560      * @param node a node
 561      * @return eval location
 562      */
 563     private String evalLocation(final IdentNode node) {
 564         final Source source = lc.getCurrentFunction().getSource();
 565         final int pos = node.position();
 566         return new StringBuilder().
 567             append(source.getName()).
 568             append('#').
 569             append(source.getLine(pos)).
 570             append(':').
 571             append(source.getColumn(pos)).
 572             append("<eval>").
 573             toString();
 574     }
 575 
 576     /**
 577      * Check whether a call node may be a call to eval. In that case we
 578      * clone the args in order to create the following construct in
 579      * {@link CodeGenerator}
 580      *
 581      * <pre>
 582      * if (calledFuntion == buildInEval) {
 583      *    eval(cloned arg);
 584      * } else {
 585      *    cloned arg;
 586      * }
 587      * </pre>
 588      *
 589      * @param callNode call node to check if it's an eval
 590      */
 591     private CallNode checkEval(final CallNode callNode) {
 592         if (callNode.getFunction() instanceof IdentNode) {
 593 
 594             final List<Expression> args = callNode.getArgs();
 595             final IdentNode callee = (IdentNode)callNode.getFunction();
 596 
 597             // 'eval' call with at least one argument
 598             if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
 599                 final List<Expression> evalArgs = new ArrayList<>(args.size());
 600                 for(final Expression arg: args) {
 601                     evalArgs.add((Expression)ensureUniqueNamesIn(arg).accept(this));
 602                 }
 603                 return callNode.setEvalArgs(new CallNode.EvalArgs(evalArgs, evalLocation(callee)));
 604             }
 605         }
 606 
 607         return callNode;
 608     }
 609 
 610     /**
 611      * Helper that given a loop body makes sure that it is not terminal if it
 612      * has a continue that leads to the loop header or to outer loops' loop
 613      * headers. This means that, even if the body ends with a terminal
 614      * statement, we cannot tag it as terminal
 615      *
 616      * @param loopBody the loop body to check
 617      * @return true if control flow may escape the loop
 618      */
 619     private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
 620         final List<Node> escapes = new ArrayList<>();
 621 
 622         loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
 623             @Override
 624             public Node leaveBreakNode(final BreakNode node) {
 625                 escapes.add(node);
 626                 return node;
 627             }
 628 
 629             @Override
 630             public Node leaveContinueNode(final ContinueNode node) {
 631                 // all inner loops have been popped.
 632                 if (lex.contains(node.getTarget(lex))) {
 633                     escapes.add(node);
 634                 }
 635                 return node;
 636             }
 637         });
 638 
 639         return !escapes.isEmpty();
 640     }
 641 
 642     @SuppressWarnings("unchecked")
 643     private <T extends LoopNode> T checkEscape(final T loopNode) {
 644         final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
 645         if (escapes) {
 646             return (T)loopNode.
 647                 setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
 648                 setControlFlowEscapes(lc, escapes);
 649         }
 650         return loopNode;
 651     }
 652 
 653 
 654     private Node addStatement(final Statement statement) {
 655         lc.appendStatement(statement);
 656         return statement;
 657     }
 658 
 659     private void addStatementEnclosedInBlock(final Statement stmt) {
 660         BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt));
 661         if(stmt.isTerminal()) {
 662             b = b.setBlock(b.getBlock().setIsTerminal(null, true));
 663         }
 664         addStatement(b);
 665     }
 666 
 667     /**
 668      * An internal expression has a symbol that is tagged internal. Check if
 669      * this is such a node
 670      *
 671      * @param expression expression to check for internal symbol
 672      * @return true if internal, false otherwise
 673      */
 674     private static boolean isInternalExpression(final Expression expression) {
 675         if (!(expression instanceof IdentNode)) {
 676             return false;
 677         }
 678         final Symbol symbol = ((IdentNode)expression).getSymbol();
 679         return symbol != null && symbol.isInternal();
 680     }
 681 
 682     /**
 683      * Is this an assignment to the special variable that hosts scripting eval
 684      * results, i.e. __return__?
 685      *
 686      * @param expression expression to check whether it is $evalresult = X
 687      * @return true if an assignment to eval result, false otherwise
 688      */
 689     private static boolean isEvalResultAssignment(final Node expression) {
 690         final Node e = expression;
 691         if (e instanceof BinaryNode) {
 692             final Node lhs = ((BinaryNode)e).lhs();
 693             if (lhs instanceof IdentNode) {
 694                 return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
 695             }
 696         }
 697         return false;
 698     }
 699 }