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