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