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