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 ---