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 }