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