Lower.java revision 1426:751ada854e5a
1238438Sdteske/* 2238438Sdteske * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3249746Sdteske * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4238438Sdteske * 5238438Sdteske * This code is free software; you can redistribute it and/or modify it 6238438Sdteske * under the terms of the GNU General Public License version 2 only, as 7238438Sdteske * published by the Free Software Foundation. Oracle designates this 8238438Sdteske * particular file as subject to the "Classpath" exception as provided 9238438Sdteske * by Oracle in the LICENSE file that accompanied this code. 10238438Sdteske * 11238438Sdteske * This code is distributed in the hope that it will be useful, but WITHOUT 12238438Sdteske * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13238438Sdteske * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14238438Sdteske * version 2 for more details (a copy is included in the LICENSE file that 15238438Sdteske * accompanied this code). 16238438Sdteske * 17238438Sdteske * You should have received a copy of the GNU General Public License version 18238438Sdteske * 2 along with this work; if not, write to the Free Software Foundation, 19238438Sdteske * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20238438Sdteske * 21238438Sdteske * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22238438Sdteske * or visit www.oracle.com if you need additional information or have any 23238438Sdteske * questions. 24238438Sdteske */ 25238438Sdteske 26238438Sdteskepackage jdk.nashorn.internal.codegen; 27238438Sdteske 28238438Sdteskeimport static jdk.nashorn.internal.codegen.CompilerConstants.EVAL; 29238438Sdteskeimport static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; 30238438Sdteskeimport static jdk.nashorn.internal.ir.Expression.isAlwaysTrue; 31240684Sdteske 32240684Sdteskeimport java.util.ArrayList; 33244675Sdteskeimport java.util.Arrays; 34240684Sdteskeimport java.util.Collections; 35240684Sdteskeimport java.util.List; 36240684Sdteskeimport java.util.ListIterator; 37238438Sdteskeimport java.util.regex.Pattern; 38240684Sdteskeimport jdk.nashorn.internal.ir.AccessNode; 39238438Sdteskeimport jdk.nashorn.internal.ir.BaseNode; 40238438Sdteskeimport jdk.nashorn.internal.ir.BinaryNode; 41243112Sdteskeimport jdk.nashorn.internal.ir.Block; 42238438Sdteskeimport jdk.nashorn.internal.ir.BlockLexicalContext; 43238438Sdteskeimport jdk.nashorn.internal.ir.BlockStatement; 44238438Sdteskeimport jdk.nashorn.internal.ir.BreakNode; 45238438Sdteskeimport jdk.nashorn.internal.ir.CallNode; 46238438Sdteskeimport jdk.nashorn.internal.ir.CaseNode; 47238438Sdteskeimport jdk.nashorn.internal.ir.CatchNode; 48238438Sdteskeimport jdk.nashorn.internal.ir.ContinueNode; 49238438Sdteskeimport jdk.nashorn.internal.ir.DebuggerNode; 50238438Sdteskeimport jdk.nashorn.internal.ir.EmptyNode; 51238438Sdteskeimport jdk.nashorn.internal.ir.Expression; 52238438Sdteskeimport jdk.nashorn.internal.ir.ExpressionStatement; 53251264Sdteskeimport jdk.nashorn.internal.ir.ForNode; 54238438Sdteskeimport jdk.nashorn.internal.ir.FunctionNode; 55238438Sdteskeimport jdk.nashorn.internal.ir.IdentNode; 56238438Sdteskeimport jdk.nashorn.internal.ir.IfNode; 57238438Sdteskeimport jdk.nashorn.internal.ir.IndexNode; 58238438Sdteskeimport jdk.nashorn.internal.ir.JumpStatement; 59238438Sdteskeimport jdk.nashorn.internal.ir.JumpToInlinedFinally; 60238438Sdteskeimport jdk.nashorn.internal.ir.LabelNode; 61251264Sdteskeimport jdk.nashorn.internal.ir.LexicalContext; 62251264Sdteskeimport jdk.nashorn.internal.ir.LiteralNode; 63238438Sdteskeimport jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode; 64251190Sdteskeimport jdk.nashorn.internal.ir.LoopNode; 65251190Sdteskeimport jdk.nashorn.internal.ir.Node; 66251190Sdteskeimport jdk.nashorn.internal.ir.ReturnNode; 67251190Sdteskeimport jdk.nashorn.internal.ir.RuntimeNode; 68251190Sdteskeimport jdk.nashorn.internal.ir.Statement; 69251190Sdteskeimport jdk.nashorn.internal.ir.SwitchNode; 70251190Sdteskeimport jdk.nashorn.internal.ir.Symbol; 71238438Sdteskeimport jdk.nashorn.internal.ir.ThrowNode; 72249751Sdteskeimport jdk.nashorn.internal.ir.TryNode; 73249751Sdteskeimport jdk.nashorn.internal.ir.VarNode; 74249751Sdteskeimport jdk.nashorn.internal.ir.WhileNode; 75249751Sdteskeimport jdk.nashorn.internal.ir.WithNode; 76249751Sdteskeimport jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; 77249751Sdteskeimport jdk.nashorn.internal.ir.visitor.NodeVisitor; 78249751Sdteskeimport jdk.nashorn.internal.parser.Token; 79249751Sdteskeimport jdk.nashorn.internal.parser.TokenType; 80249751Sdteskeimport jdk.nashorn.internal.runtime.Context; 81251236Sdteskeimport jdk.nashorn.internal.runtime.JSType; 82251236Sdteskeimport jdk.nashorn.internal.runtime.Source; 83249751Sdteskeimport jdk.nashorn.internal.runtime.logging.DebugLogger; 84238438Sdteskeimport jdk.nashorn.internal.runtime.logging.Loggable; 85238438Sdteskeimport jdk.nashorn.internal.runtime.logging.Logger; 86238438Sdteske 87238438Sdteske/** 88249751Sdteske * Lower to more primitive operations. After lowering, an AST still has no symbols 89251190Sdteske * and types, but several nodes have been turned into more low level constructs 90251190Sdteske * and control flow termination criteria have been computed. 91238438Sdteske * 92240768Sdteske * We do things like code copying/inlining of finallies here, as it is much 93240768Sdteske * harder and context dependent to do any code copying after symbols have been 94240768Sdteske * finalized. 95251236Sdteske */ 96240768Sdteske@Logger(name="lower") 97238438Sdteskefinal class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Loggable { 98238438Sdteske 99238438Sdteske private final DebugLogger log; 100238438Sdteske 101238438Sdteske // Conservative pattern to test if element names consist of characters valid for identifiers. 102238438Sdteske // This matches any non-zero length alphanumeric string including _ and $ and not starting with a digit. 103238438Sdteske private static final Pattern SAFE_PROPERTY_NAME = Pattern.compile("[a-zA-Z_$][\\w$]*"); 104238438Sdteske 105238438Sdteske /** 106238438Sdteske * Constructor. 107250633Sdteske */ 108238438Sdteske Lower(final Compiler compiler) { 109238438Sdteske super(new BlockLexicalContext() { 110238438Sdteske 111238438Sdteske @Override 112238438Sdteske public List<Statement> popStatements() { 113238438Sdteske final List<Statement> newStatements = new ArrayList<>(); 114238438Sdteske boolean terminated = false; 115238438Sdteske 116238438Sdteske final List<Statement> statements = super.popStatements(); 117238438Sdteske for (final Statement statement : statements) { 118238438Sdteske if (!terminated) { 119238438Sdteske newStatements.add(statement); 120238438Sdteske if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why? 121238438Sdteske terminated = true; 122238438Sdteske } 123238438Sdteske } else { 124238438Sdteske statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 125251236Sdteske @Override 126251236Sdteske public boolean enterVarNode(final VarNode varNode) { 127238438Sdteske newStatements.add(varNode.setInit(null)); 128238438Sdteske return false; 129238438Sdteske } 130238438Sdteske }); 131238438Sdteske } 132238438Sdteske } 133238438Sdteske return newStatements; 134238438Sdteske } 135238438Sdteske 136238438Sdteske @Override 137238438Sdteske protected Block afterSetStatements(final Block block) { 138238438Sdteske final List<Statement> stmts = block.getStatements(); 139238438Sdteske for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) { 140238438Sdteske final Statement stmt = li.previous(); 141238438Sdteske // popStatements() guarantees that the only thing after a terminal statement are uninitialized 142238438Sdteske // VarNodes. We skip past those, and set the terminal state of the block to the value of the 143238438Sdteske // terminal state of the first statement that is not an uninitialized VarNode. 144238438Sdteske if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) { 145238438Sdteske return block.setIsTerminal(this, stmt.isTerminal()); 146238438Sdteske } 147238438Sdteske } 148238438Sdteske return block.setIsTerminal(this, false); 149238438Sdteske } 150238438Sdteske }); 151238438Sdteske 152238438Sdteske this.log = initLogger(compiler.getContext()); 153238438Sdteske } 154238438Sdteske 155238438Sdteske @Override 156 public DebugLogger getLogger() { 157 return log; 158 } 159 160 @Override 161 public DebugLogger initLogger(final Context context) { 162 return context.getLogger(this.getClass()); 163 } 164 165 @Override 166 public boolean enterBreakNode(final BreakNode breakNode) { 167 addStatement(breakNode); 168 return false; 169 } 170 171 @Override 172 public Node leaveCallNode(final CallNode callNode) { 173 return checkEval(callNode.setFunction(markerFunction(callNode.getFunction()))); 174 } 175 176 @Override 177 public Node leaveCatchNode(final CatchNode catchNode) { 178 return addStatement(catchNode); 179 } 180 181 @Override 182 public boolean enterContinueNode(final ContinueNode continueNode) { 183 addStatement(continueNode); 184 return false; 185 } 186 187 @Override 188 public boolean enterDebuggerNode(final DebuggerNode debuggerNode) { 189 final int line = debuggerNode.getLineNumber(); 190 final long token = debuggerNode.getToken(); 191 final int finish = debuggerNode.getFinish(); 192 addStatement(new ExpressionStatement(line, token, finish, new RuntimeNode(token, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Expression>()))); 193 return false; 194 } 195 196 @Override 197 public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) { 198 addStatement(jumpToInlinedFinally); 199 return false; 200 } 201 202 @Override 203 public boolean enterEmptyNode(final EmptyNode emptyNode) { 204 return false; 205 } 206 207 @Override 208 public Node leaveIndexNode(final IndexNode indexNode) { 209 final String name = getConstantPropertyName(indexNode.getIndex()); 210 if (name != null) { 211 // If index node is a constant property name convert index node to access node. 212 assert indexNode.isIndex(); 213 return new AccessNode(indexNode.getToken(), indexNode.getFinish(), indexNode.getBase(), name); 214 } 215 return super.leaveIndexNode(indexNode); 216 } 217 218 // If expression is a primitive literal that is not an array index and does return its string value. Else return null. 219 private static String getConstantPropertyName(final Expression expression) { 220 if (expression instanceof LiteralNode.PrimitiveLiteralNode) { 221 final Object value = ((LiteralNode) expression).getValue(); 222 if (value instanceof String && SAFE_PROPERTY_NAME.matcher((String) value).matches()) { 223 return (String) value; 224 } 225 } 226 return null; 227 } 228 229 @Override 230 public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) { 231 final Expression expr = expressionStatement.getExpression(); 232 ExpressionStatement node = expressionStatement; 233 234 final FunctionNode currentFunction = lc.getCurrentFunction(); 235 236 if (currentFunction.isProgram()) { 237 if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) { 238 node = expressionStatement.setExpression( 239 new BinaryNode( 240 Token.recast( 241 expressionStatement.getToken(), 242 TokenType.ASSIGN), 243 compilerConstant(RETURN), 244 expr)); 245 } 246 } 247 248 return addStatement(node); 249 } 250 251 @Override 252 public Node leaveBlockStatement(final BlockStatement blockStatement) { 253 return addStatement(blockStatement); 254 } 255 256 @Override 257 public Node leaveForNode(final ForNode forNode) { 258 ForNode newForNode = forNode; 259 260 final Expression test = forNode.getTest(); 261 if (!forNode.isForIn() && isAlwaysTrue(test)) { 262 newForNode = forNode.setTest(lc, null); 263 } 264 265 newForNode = checkEscape(newForNode); 266 if(newForNode.isForIn()) { 267 // Wrap it in a block so its internally created iterator is restricted in scope 268 addStatementEnclosedInBlock(newForNode); 269 } else { 270 addStatement(newForNode); 271 } 272 return newForNode; 273 } 274 275 @Override 276 public Node leaveFunctionNode(final FunctionNode functionNode) { 277 log.info("END FunctionNode: ", functionNode.getName()); 278 return functionNode; 279 } 280 281 @Override 282 public Node leaveIfNode(final IfNode ifNode) { 283 return addStatement(ifNode); 284 } 285 286 @Override 287 public Node leaveIN(final BinaryNode binaryNode) { 288 return new RuntimeNode(binaryNode); 289 } 290 291 @Override 292 public Node leaveINSTANCEOF(final BinaryNode binaryNode) { 293 return new RuntimeNode(binaryNode); 294 } 295 296 @Override 297 public Node leaveLabelNode(final LabelNode labelNode) { 298 return addStatement(labelNode); 299 } 300 301 @Override 302 public Node leaveReturnNode(final ReturnNode returnNode) { 303 addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor 304 return returnNode; 305 } 306 307 @Override 308 public Node leaveCaseNode(final CaseNode caseNode) { 309 // Try to represent the case test as an integer 310 final Node test = caseNode.getTest(); 311 if (test instanceof LiteralNode) { 312 final LiteralNode<?> lit = (LiteralNode<?>)test; 313 if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) { 314 if (JSType.isRepresentableAsInt(lit.getNumber())) { 315 return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this)); 316 } 317 } 318 } 319 return caseNode; 320 } 321 322 @Override 323 public Node leaveSwitchNode(final SwitchNode switchNode) { 324 if(!switchNode.isUniqueInteger()) { 325 // Wrap it in a block so its internally created tag is restricted in scope 326 addStatementEnclosedInBlock(switchNode); 327 } else { 328 addStatement(switchNode); 329 } 330 return switchNode; 331 } 332 333 @Override 334 public Node leaveThrowNode(final ThrowNode throwNode) { 335 return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor 336 } 337 338 @SuppressWarnings("unchecked") 339 private static <T extends Node> T ensureUniqueNamesIn(final T node) { 340 return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 341 @Override 342 public Node leaveFunctionNode(final FunctionNode functionNode) { 343 final String name = functionNode.getName(); 344 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name)); 345 } 346 347 @Override 348 public Node leaveDefault(final Node labelledNode) { 349 return labelledNode.ensureUniqueLabels(lc); 350 } 351 }); 352 } 353 354 private static Block createFinallyBlock(final Block finallyBody) { 355 final List<Statement> newStatements = new ArrayList<>(); 356 for (final Statement statement : finallyBody.getStatements()) { 357 newStatements.add(statement); 358 if (statement.hasTerminalFlags()) { 359 break; 360 } 361 } 362 return finallyBody.setStatements(null, newStatements); 363 } 364 365 private Block catchAllBlock(final TryNode tryNode) { 366 final int lineNumber = tryNode.getLineNumber(); 367 final long token = tryNode.getToken(); 368 final int finish = tryNode.getFinish(); 369 370 final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName())); 371 372 final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true)); 373 assert catchBody.isTerminal(); //ends with throw, so terminal 374 375 final CatchNode catchAllNode = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true); 376 final Block catchAllBlock = new Block(token, finish, catchAllNode); 377 378 //catchallblock -> catchallnode (catchnode) -> exception -> throw 379 380 return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower 381 } 382 383 private IdentNode compilerConstant(final CompilerConstants cc) { 384 final FunctionNode functionNode = lc.getCurrentFunction(); 385 return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); 386 } 387 388 private static boolean isTerminalFinally(final Block finallyBlock) { 389 return finallyBlock.getLastStatement().hasTerminalFlags(); 390 } 391 392 /** 393 * Splice finally code into all endpoints of a trynode 394 * @param tryNode the try node 395 * @param rethrow the rethrowing throw nodes from the synthetic catch block 396 * @param finallyBody the code in the original finally block 397 * @return new try node after splicing finally code (same if nop) 398 */ 399 private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) { 400 assert tryNode.getFinallyBody() == null; 401 402 final Block finallyBlock = createFinallyBlock(finallyBody); 403 final ArrayList<Block> inlinedFinallies = new ArrayList<>(); 404 final FunctionNode fn = lc.getCurrentFunction(); 405 final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 406 407 @Override 408 public boolean enterFunctionNode(final FunctionNode functionNode) { 409 // do not enter function nodes - finally code should not be inlined into them 410 return false; 411 } 412 413 @Override 414 public Node leaveThrowNode(final ThrowNode throwNode) { 415 if (rethrow == throwNode) { 416 return new BlockStatement(prependFinally(finallyBlock, throwNode)); 417 } 418 return throwNode; 419 } 420 421 @Override 422 public Node leaveBreakNode(final BreakNode breakNode) { 423 return leaveJumpStatement(breakNode); 424 } 425 426 @Override 427 public Node leaveContinueNode(final ContinueNode continueNode) { 428 return leaveJumpStatement(continueNode); 429 } 430 431 private Node leaveJumpStatement(final JumpStatement jump) { 432 // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and 433 // continue are edited. JTIF nodes should not be changed, rather the surroundings of 434 // break/continue/return that were moved into the inlined finally block itself will be changed. 435 436 // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block. 437 if (jump.getTarget(lc) == null) { 438 return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump)); 439 } 440 return jump; 441 } 442 443 @Override 444 public Node leaveReturnNode(final ReturnNode returnNode) { 445 final Expression expr = returnNode.getExpression(); 446 if (isTerminalFinally(finallyBlock)) { 447 if (expr == null) { 448 // Terminal finally; no return expression. 449 return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)); 450 } 451 // Terminal finally; has a return expression. 452 final List<Statement> newStatements = new ArrayList<>(2); 453 final int retLineNumber = returnNode.getLineNumber(); 454 final long retToken = returnNode.getToken(); 455 // Expression is evaluated for side effects. 456 newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr)); 457 newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock))); 458 return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements)); 459 } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) { 460 // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return. 461 // Just move the return expression into the finally block. 462 return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode)); 463 } else { 464 // We need to evaluate the result of the return in case it is complex while still in the try block, 465 // store it in :return, and return it afterwards. 466 final List<Statement> newStatements = new ArrayList<>(); 467 final int retLineNumber = returnNode.getLineNumber(); 468 final long retToken = returnNode.getToken(); 469 final int retFinish = returnNode.getFinish(); 470 final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName()); 471 // ":return = <expr>;" 472 newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); 473 // inline finally and end it with "return :return;" 474 newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode)))); 475 return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements)); 476 } 477 } 478 }); 479 addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies)); 480 // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here. 481 addStatement(new BlockStatement(finallyBlock)); 482 483 return newTryNode; 484 } 485 486 private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) { 487 final String labelName = fn.uniqueName(":finally"); 488 final long token = finallyBlock.getToken(); 489 final int finish = finallyBlock.getFinish(); 490 inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(), 491 token, finish, labelName, finallyBlock))); 492 return new JumpToInlinedFinally(labelName); 493 } 494 495 private static Block prependFinally(final Block finallyBlock, final Statement statement) { 496 final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock); 497 if (isTerminalFinally(finallyBlock)) { 498 return inlinedFinally; 499 } 500 final List<Statement> stmts = inlinedFinally.getStatements(); 501 final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1); 502 newStmts.addAll(stmts); 503 newStmts.add(statement); 504 return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts); 505 } 506 507 @Override 508 public Node leaveTryNode(final TryNode tryNode) { 509 final Block finallyBody = tryNode.getFinallyBody(); 510 TryNode newTryNode = tryNode.setFinallyBody(lc, null); 511 512 // No finally or empty finally 513 if (finallyBody == null || finallyBody.getStatementCount() == 0) { 514 final List<CatchNode> catches = newTryNode.getCatches(); 515 if (catches == null || catches.isEmpty()) { 516 // A completely degenerate try block: empty finally, no catches. Replace it with try body. 517 return addStatement(new BlockStatement(tryNode.getBody())); 518 } 519 return addStatement(ensureUnconditionalCatch(newTryNode)); 520 } 521 522 /* 523 * create a new try node 524 * if we have catches: 525 * 526 * try try 527 * x try 528 * catch x 529 * y catch 530 * finally z y 531 * catchall 532 * rethrow 533 * 534 * otherwise 535 * 536 * try try 537 * x x 538 * finally catchall 539 * y rethrow 540 * 541 * 542 * now splice in finally code wherever needed 543 * 544 */ 545 final Block catchAll = catchAllBlock(tryNode); 546 547 final List<ThrowNode> rethrows = new ArrayList<>(1); 548 catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 549 @Override 550 public boolean enterThrowNode(final ThrowNode throwNode) { 551 rethrows.add(throwNode); 552 return true; 553 } 554 }); 555 assert rethrows.size() == 1; 556 557 if (!tryNode.getCatchBlocks().isEmpty()) { 558 final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode)); 559 newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null); 560 } 561 562 newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll)); 563 564 /* 565 * Now that the transform is done, we have to go into the try and splice 566 * the finally block in front of any statement that is outside the try 567 */ 568 return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody)); 569 } 570 571 private TryNode ensureUnconditionalCatch(final TryNode tryNode) { 572 final List<CatchNode> catches = tryNode.getCatches(); 573 if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) { 574 return tryNode; 575 } 576 // If the last catch block is conditional, add an unconditional rethrow block 577 final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks()); 578 579 newCatchBlocks.add(catchAllBlock(tryNode)); 580 return tryNode.setCatchBlocks(lc, newCatchBlocks); 581 } 582 583 @Override 584 public Node leaveVarNode(final VarNode varNode) { 585 addStatement(varNode); 586 if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) 587 && lc.getCurrentFunction().isProgram() 588 && ((FunctionNode) varNode.getInit()).isAnonymous()) { 589 new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this); 590 } 591 return varNode; 592 } 593 594 @Override 595 public Node leaveWhileNode(final WhileNode whileNode) { 596 final Expression test = whileNode.getTest(); 597 final Block body = whileNode.getBody(); 598 599 if (isAlwaysTrue(test)) { 600 //turn it into a for node without a test. 601 final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this); 602 lc.replace(whileNode, forNode); 603 return forNode; 604 } 605 606 return addStatement(checkEscape(whileNode)); 607 } 608 609 @Override 610 public Node leaveWithNode(final WithNode withNode) { 611 return addStatement(withNode); 612 } 613 614 /** 615 * Given a function node that is a callee in a CallNode, replace it with 616 * the appropriate marker function. This is used by {@link CodeGenerator} 617 * for fast scope calls 618 * 619 * @param function function called by a CallNode 620 * @return transformed node to marker function or identity if not ident/access/indexnode 621 */ 622 private static Expression markerFunction(final Expression function) { 623 if (function instanceof IdentNode) { 624 return ((IdentNode)function).setIsFunction(); 625 } else if (function instanceof BaseNode) { 626 return ((BaseNode)function).setIsFunction(); 627 } 628 return function; 629 } 630 631 /** 632 * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval> 633 * @param node a node 634 * @return eval location 635 */ 636 private String evalLocation(final IdentNode node) { 637 final Source source = lc.getCurrentFunction().getSource(); 638 final int pos = node.position(); 639 return new StringBuilder(). 640 append(source.getName()). 641 append('#'). 642 append(source.getLine(pos)). 643 append(':'). 644 append(source.getColumn(pos)). 645 append("<eval>"). 646 toString(); 647 } 648 649 /** 650 * Check whether a call node may be a call to eval. In that case we 651 * clone the args in order to create the following construct in 652 * {@link CodeGenerator} 653 * 654 * <pre> 655 * if (calledFuntion == buildInEval) { 656 * eval(cloned arg); 657 * } else { 658 * cloned arg; 659 * } 660 * </pre> 661 * 662 * @param callNode call node to check if it's an eval 663 */ 664 private CallNode checkEval(final CallNode callNode) { 665 if (callNode.getFunction() instanceof IdentNode) { 666 667 final List<Expression> args = callNode.getArgs(); 668 final IdentNode callee = (IdentNode)callNode.getFunction(); 669 670 // 'eval' call with at least one argument 671 if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) { 672 final List<Expression> evalArgs = new ArrayList<>(args.size()); 673 for(final Expression arg: args) { 674 evalArgs.add((Expression)ensureUniqueNamesIn(arg).accept(this)); 675 } 676 return callNode.setEvalArgs(new CallNode.EvalArgs(evalArgs, evalLocation(callee))); 677 } 678 } 679 680 return callNode; 681 } 682 683 /** 684 * Helper that given a loop body makes sure that it is not terminal if it 685 * has a continue that leads to the loop header or to outer loops' loop 686 * headers. This means that, even if the body ends with a terminal 687 * statement, we cannot tag it as terminal 688 * 689 * @param loopBody the loop body to check 690 * @return true if control flow may escape the loop 691 */ 692 private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) { 693 final List<Node> escapes = new ArrayList<>(); 694 695 loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 696 @Override 697 public Node leaveBreakNode(final BreakNode node) { 698 escapes.add(node); 699 return node; 700 } 701 702 @Override 703 public Node leaveContinueNode(final ContinueNode node) { 704 // all inner loops have been popped. 705 if (lex.contains(node.getTarget(lex))) { 706 escapes.add(node); 707 } 708 return node; 709 } 710 }); 711 712 return !escapes.isEmpty(); 713 } 714 715 @SuppressWarnings("unchecked") 716 private <T extends LoopNode> T checkEscape(final T loopNode) { 717 final boolean escapes = controlFlowEscapes(lc, loopNode.getBody()); 718 if (escapes) { 719 return (T)loopNode. 720 setBody(lc, loopNode.getBody().setIsTerminal(lc, false)). 721 setControlFlowEscapes(lc, escapes); 722 } 723 return loopNode; 724 } 725 726 727 private Node addStatement(final Statement statement) { 728 lc.appendStatement(statement); 729 return statement; 730 } 731 732 private void addStatementEnclosedInBlock(final Statement stmt) { 733 BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt)); 734 if(stmt.isTerminal()) { 735 b = b.setBlock(b.getBlock().setIsTerminal(null, true)); 736 } 737 addStatement(b); 738 } 739 740 /** 741 * An internal expression has a symbol that is tagged internal. Check if 742 * this is such a node 743 * 744 * @param expression expression to check for internal symbol 745 * @return true if internal, false otherwise 746 */ 747 private static boolean isInternalExpression(final Expression expression) { 748 if (!(expression instanceof IdentNode)) { 749 return false; 750 } 751 final Symbol symbol = ((IdentNode)expression).getSymbol(); 752 return symbol != null && symbol.isInternal(); 753 } 754 755 /** 756 * Is this an assignment to the special variable that hosts scripting eval 757 * results, i.e. __return__? 758 * 759 * @param expression expression to check whether it is $evalresult = X 760 * @return true if an assignment to eval result, false otherwise 761 */ 762 private static boolean isEvalResultAssignment(final Node expression) { 763 final Node e = expression; 764 if (e instanceof BinaryNode) { 765 final Node lhs = ((BinaryNode)e).lhs(); 766 if (lhs instanceof IdentNode) { 767 return ((IdentNode)lhs).getName().equals(RETURN.symbolName()); 768 } 769 } 770 return false; 771 } 772} 773