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