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