Wed, 05 Jun 2013 12:17:10 +0200
8015961: Several small code-gardening fixes
Reviewed-by: lagergren, sundar
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.codegen; |
jlaskey@3 | 27 | |
jlaskey@3 | 28 | import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL; |
lagergren@211 | 29 | import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; |
jlaskey@3 | 30 | import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; |
jlaskey@3 | 31 | |
jlaskey@3 | 32 | import java.util.ArrayList; |
jlaskey@3 | 33 | import java.util.Arrays; |
jlaskey@3 | 34 | import java.util.List; |
lagergren@57 | 35 | import jdk.nashorn.internal.ir.BaseNode; |
jlaskey@3 | 36 | import jdk.nashorn.internal.ir.BinaryNode; |
jlaskey@3 | 37 | import jdk.nashorn.internal.ir.Block; |
lagergren@211 | 38 | import jdk.nashorn.internal.ir.BlockLexicalContext; |
jlaskey@3 | 39 | import jdk.nashorn.internal.ir.BreakNode; |
jlaskey@3 | 40 | import jdk.nashorn.internal.ir.CallNode; |
jlaskey@3 | 41 | import jdk.nashorn.internal.ir.CatchNode; |
jlaskey@3 | 42 | import jdk.nashorn.internal.ir.ContinueNode; |
jlaskey@3 | 43 | import jdk.nashorn.internal.ir.EmptyNode; |
jlaskey@3 | 44 | import jdk.nashorn.internal.ir.ExecuteNode; |
jlaskey@3 | 45 | import jdk.nashorn.internal.ir.ForNode; |
jlaskey@3 | 46 | import jdk.nashorn.internal.ir.FunctionNode; |
attila@144 | 47 | import jdk.nashorn.internal.ir.FunctionNode.CompilationState; |
jlaskey@3 | 48 | import jdk.nashorn.internal.ir.IdentNode; |
jlaskey@3 | 49 | import jdk.nashorn.internal.ir.IfNode; |
jlaskey@3 | 50 | import jdk.nashorn.internal.ir.LabelNode; |
attila@144 | 51 | import jdk.nashorn.internal.ir.LexicalContext; |
jlaskey@3 | 52 | import jdk.nashorn.internal.ir.LiteralNode; |
lagergren@211 | 53 | import jdk.nashorn.internal.ir.LoopNode; |
jlaskey@3 | 54 | import jdk.nashorn.internal.ir.Node; |
jlaskey@3 | 55 | import jdk.nashorn.internal.ir.ReturnNode; |
lagergren@253 | 56 | import jdk.nashorn.internal.ir.Statement; |
jlaskey@3 | 57 | import jdk.nashorn.internal.ir.SwitchNode; |
jlaskey@3 | 58 | import jdk.nashorn.internal.ir.Symbol; |
jlaskey@3 | 59 | import jdk.nashorn.internal.ir.ThrowNode; |
jlaskey@3 | 60 | import jdk.nashorn.internal.ir.TryNode; |
jlaskey@3 | 61 | import jdk.nashorn.internal.ir.VarNode; |
jlaskey@3 | 62 | import jdk.nashorn.internal.ir.WhileNode; |
jlaskey@3 | 63 | import jdk.nashorn.internal.ir.WithNode; |
jlaskey@3 | 64 | import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; |
jlaskey@3 | 65 | import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
jlaskey@3 | 66 | import jdk.nashorn.internal.parser.Token; |
jlaskey@3 | 67 | import jdk.nashorn.internal.parser.TokenType; |
jlaskey@3 | 68 | import jdk.nashorn.internal.runtime.DebugLogger; |
jlaskey@3 | 69 | import jdk.nashorn.internal.runtime.ScriptRuntime; |
jlaskey@3 | 70 | import jdk.nashorn.internal.runtime.Source; |
jlaskey@3 | 71 | |
jlaskey@3 | 72 | /** |
lagergren@57 | 73 | * Lower to more primitive operations. After lowering, an AST still has no symbols |
lagergren@57 | 74 | * and types, but several nodes have been turned into more low level constructs |
lagergren@57 | 75 | * and control flow termination criteria have been computed. |
jlaskey@3 | 76 | * |
lagergren@57 | 77 | * We do things like code copying/inlining of finallies here, as it is much |
lagergren@57 | 78 | * harder and context dependent to do any code copying after symbols have been |
lagergren@57 | 79 | * finalized. |
jlaskey@3 | 80 | */ |
jlaskey@3 | 81 | |
lagergren@290 | 82 | final class Lower extends NodeOperatorVisitor<BlockLexicalContext> { |
lagergren@57 | 83 | |
lagergren@57 | 84 | private static final DebugLogger LOG = new DebugLogger("lower"); |
lagergren@57 | 85 | |
jlaskey@3 | 86 | /** |
jlaskey@3 | 87 | * Constructor. |
jlaskey@3 | 88 | * |
jlaskey@3 | 89 | * @param compiler the compiler |
jlaskey@3 | 90 | */ |
lagergren@89 | 91 | Lower() { |
lagergren@211 | 92 | super(new BlockLexicalContext() { |
lagergren@211 | 93 | |
lagergren@211 | 94 | @Override |
lagergren@253 | 95 | public List<Statement> popStatements() { |
lagergren@253 | 96 | final List<Statement> newStatements = new ArrayList<>(); |
lagergren@211 | 97 | boolean terminated = false; |
lagergren@211 | 98 | |
lagergren@253 | 99 | final List<Statement> statements = super.popStatements(); |
lagergren@253 | 100 | for (final Statement statement : statements) { |
lagergren@211 | 101 | if (!terminated) { |
lagergren@211 | 102 | newStatements.add(statement); |
lagergren@253 | 103 | if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why? |
lagergren@211 | 104 | terminated = true; |
lagergren@211 | 105 | } |
lagergren@211 | 106 | } else { |
lagergren@290 | 107 | statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@253 | 108 | @Override |
lagergren@253 | 109 | public boolean enterVarNode(final VarNode varNode) { |
lagergren@253 | 110 | newStatements.add(varNode.setInit(null)); |
lagergren@253 | 111 | return false; |
lagergren@253 | 112 | } |
lagergren@253 | 113 | }); |
lagergren@211 | 114 | } |
lagergren@211 | 115 | } |
lagergren@211 | 116 | return newStatements; |
lagergren@211 | 117 | } |
lagergren@211 | 118 | }); |
jlaskey@3 | 119 | } |
jlaskey@3 | 120 | |
jlaskey@3 | 121 | @Override |
lagergren@211 | 122 | public boolean enterBlock(final Block block) { |
lagergren@252 | 123 | final FunctionNode function = lc.getCurrentFunction(); |
lagergren@252 | 124 | if (lc.isFunctionBody() && function.isProgram() && !function.hasDeclaredFunctions()) { |
lagergren@253 | 125 | new ExecuteNode(block.getLineNumber(), block.getToken(), block.getFinish(), LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)).accept(this); |
lagergren@57 | 126 | } |
lagergren@211 | 127 | return true; |
lagergren@57 | 128 | } |
lagergren@57 | 129 | |
lagergren@57 | 130 | @Override |
lagergren@211 | 131 | public Node leaveBlock(final Block block) { |
lagergren@211 | 132 | //now we have committed the entire statement list to the block, but we need to truncate |
lagergren@211 | 133 | //whatever is after the last terminal. block append won't append past it |
lagergren@211 | 134 | |
lagergren@253 | 135 | Statement last = lc.getLastStatement(); |
lagergren@211 | 136 | |
lagergren@211 | 137 | if (lc.isFunctionBody()) { |
lagergren@290 | 138 | final FunctionNode currentFunction = lc.getCurrentFunction(); |
lagergren@211 | 139 | final boolean isProgram = currentFunction.isProgram(); |
lagergren@211 | 140 | final ReturnNode returnNode = new ReturnNode( |
lagergren@253 | 141 | last == null ? block.getLineNumber() : last.getLineNumber(), //TODO? |
lagergren@211 | 142 | currentFunction.getToken(), |
lagergren@211 | 143 | currentFunction.getFinish(), |
lagergren@211 | 144 | isProgram ? |
lagergren@211 | 145 | compilerConstant(RETURN) : |
lagergren@211 | 146 | LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)); |
lagergren@211 | 147 | |
lagergren@253 | 148 | last = (Statement)returnNode.accept(this); |
lagergren@211 | 149 | } |
lagergren@211 | 150 | |
lagergren@211 | 151 | if (last != null && last.isTerminal()) { |
lagergren@211 | 152 | return block.setIsTerminal(lc, true); |
lagergren@211 | 153 | } |
lagergren@211 | 154 | |
lagergren@211 | 155 | return block; |
lagergren@57 | 156 | } |
lagergren@57 | 157 | |
lagergren@57 | 158 | @Override |
lagergren@211 | 159 | public boolean enterBreakNode(final BreakNode breakNode) { |
lagergren@211 | 160 | addStatement(breakNode); |
lagergren@211 | 161 | return false; |
lagergren@57 | 162 | } |
lagergren@57 | 163 | |
lagergren@57 | 164 | @Override |
lagergren@211 | 165 | public Node leaveCallNode(final CallNode callNode) { |
lagergren@211 | 166 | return checkEval(callNode.setFunction(markerFunction(callNode.getFunction()))); |
lagergren@57 | 167 | } |
lagergren@57 | 168 | |
lagergren@57 | 169 | @Override |
attila@144 | 170 | public Node leaveCatchNode(final CatchNode catchNode) { |
lagergren@211 | 171 | return addStatement(catchNode); |
lagergren@57 | 172 | } |
lagergren@57 | 173 | |
lagergren@57 | 174 | @Override |
lagergren@211 | 175 | public boolean enterContinueNode(final ContinueNode continueNode) { |
lagergren@211 | 176 | addStatement(continueNode); |
lagergren@211 | 177 | return false; |
lagergren@57 | 178 | } |
lagergren@57 | 179 | |
lagergren@57 | 180 | @Override |
lagergren@211 | 181 | public boolean enterEmptyNode(final EmptyNode emptyNode) { |
lagergren@211 | 182 | return false; |
lagergren@57 | 183 | } |
lagergren@57 | 184 | |
lagergren@57 | 185 | @Override |
attila@144 | 186 | public Node leaveExecuteNode(final ExecuteNode executeNode) { |
lagergren@57 | 187 | final Node expr = executeNode.getExpression(); |
lagergren@211 | 188 | ExecuteNode node = executeNode; |
lagergren@57 | 189 | |
lagergren@290 | 190 | final FunctionNode currentFunction = lc.getCurrentFunction(); |
lagergren@211 | 191 | |
lagergren@211 | 192 | if (currentFunction.isProgram()) { |
attila@144 | 193 | if (!(expr instanceof Block) || expr instanceof FunctionNode) { // it's not a block, but can be a function |
lagergren@57 | 194 | if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) { |
lagergren@211 | 195 | node = executeNode.setExpression( |
lagergren@211 | 196 | new BinaryNode( |
lagergren@211 | 197 | Token.recast( |
lagergren@211 | 198 | executeNode.getToken(), |
lagergren@211 | 199 | TokenType.ASSIGN), |
lagergren@211 | 200 | compilerConstant(RETURN), |
lagergren@211 | 201 | expr)); |
lagergren@57 | 202 | } |
lagergren@57 | 203 | } |
lagergren@57 | 204 | } |
lagergren@57 | 205 | |
lagergren@211 | 206 | return addStatement(node); |
lagergren@57 | 207 | } |
lagergren@57 | 208 | |
lagergren@57 | 209 | @Override |
attila@144 | 210 | public Node leaveForNode(final ForNode forNode) { |
lagergren@211 | 211 | ForNode newForNode = forNode; |
lagergren@211 | 212 | |
lagergren@57 | 213 | final Node test = forNode.getTest(); |
lagergren@211 | 214 | if (!forNode.isForIn() && conservativeAlwaysTrue(test)) { |
lagergren@290 | 215 | newForNode = forNode.setTest(lc, null); |
lagergren@57 | 216 | } |
lagergren@57 | 217 | |
lagergren@211 | 218 | return addStatement(checkEscape(newForNode)); |
lagergren@57 | 219 | } |
lagergren@57 | 220 | |
lagergren@57 | 221 | @Override |
lagergren@211 | 222 | public boolean enterFunctionNode(final FunctionNode functionNode) { |
lagergren@211 | 223 | return !functionNode.isLazy(); |
attila@144 | 224 | } |
attila@144 | 225 | |
lagergren@57 | 226 | @Override |
lagergren@211 | 227 | public Node leaveFunctionNode(final FunctionNode functionNode) { |
lagergren@211 | 228 | LOG.info("END FunctionNode: ", functionNode.getName()); |
lagergren@290 | 229 | return functionNode.setState(lc, CompilationState.LOWERED); |
lagergren@57 | 230 | } |
lagergren@57 | 231 | |
lagergren@57 | 232 | @Override |
attila@144 | 233 | public Node leaveIfNode(final IfNode ifNode) { |
lagergren@211 | 234 | return addStatement(ifNode); |
lagergren@57 | 235 | } |
lagergren@57 | 236 | |
lagergren@57 | 237 | @Override |
lagergren@211 | 238 | public Node leaveLabelNode(final LabelNode labelNode) { |
lagergren@211 | 239 | return addStatement(labelNode); |
lagergren@57 | 240 | } |
lagergren@57 | 241 | |
lagergren@57 | 242 | @Override |
attila@144 | 243 | public Node leaveReturnNode(final ReturnNode returnNode) { |
lagergren@57 | 244 | addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor |
lagergren@57 | 245 | return returnNode; |
lagergren@57 | 246 | } |
lagergren@57 | 247 | |
lagergren@57 | 248 | |
lagergren@57 | 249 | @Override |
attila@144 | 250 | public Node leaveSwitchNode(final SwitchNode switchNode) { |
lagergren@211 | 251 | return addStatement(switchNode); |
lagergren@57 | 252 | } |
lagergren@57 | 253 | |
lagergren@57 | 254 | @Override |
attila@144 | 255 | public Node leaveThrowNode(final ThrowNode throwNode) { |
lagergren@57 | 256 | addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor |
lagergren@57 | 257 | return throwNode; |
lagergren@57 | 258 | } |
lagergren@57 | 259 | |
attila@325 | 260 | private static Node ensureUniqueNamesIn(final Node node) { |
lagergren@290 | 261 | return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@272 | 262 | @Override |
lagergren@272 | 263 | public Node leaveFunctionNode(final FunctionNode functionNode) { |
lagergren@272 | 264 | final String name = functionNode.getName(); |
lagergren@290 | 265 | return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name)); |
lagergren@272 | 266 | } |
lagergren@272 | 267 | |
lagergren@272 | 268 | @Override |
lagergren@272 | 269 | public Node leaveDefault(final Node labelledNode) { |
lagergren@290 | 270 | return labelledNode.ensureUniqueLabels(lc); |
lagergren@272 | 271 | } |
lagergren@211 | 272 | }); |
lagergren@211 | 273 | } |
lagergren@57 | 274 | |
attila@325 | 275 | private static List<Statement> copyFinally(final Block finallyBody) { |
lagergren@253 | 276 | final List<Statement> newStatements = new ArrayList<>(); |
lagergren@253 | 277 | for (final Statement statement : finallyBody.getStatements()) { |
attila@325 | 278 | newStatements.add((Statement)ensureUniqueNamesIn(statement)); |
lagergren@211 | 279 | if (statement.hasTerminalFlags()) { |
lagergren@211 | 280 | return newStatements; |
lagergren@211 | 281 | } |
lagergren@211 | 282 | } |
lagergren@211 | 283 | return newStatements; |
lagergren@211 | 284 | } |
lagergren@57 | 285 | |
lagergren@211 | 286 | private Block catchAllBlock(final TryNode tryNode) { |
lagergren@253 | 287 | final int lineNumber = tryNode.getLineNumber(); |
lagergren@253 | 288 | final long token = tryNode.getToken(); |
lagergren@253 | 289 | final int finish = tryNode.getFinish(); |
lagergren@211 | 290 | |
lagergren@290 | 291 | final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName("catch_all")); |
lagergren@211 | 292 | |
lagergren@265 | 293 | final Block catchBody = new Block(lineNumber, token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), ThrowNode.IS_SYNTHETIC_RETHROW)). |
lagergren@290 | 294 | setIsTerminal(lc, true); //ends with throw, so terminal |
lagergren@211 | 295 | |
lagergren@265 | 296 | final CatchNode catchAllNode = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, CatchNode.IS_SYNTHETIC_RETHROW); |
lagergren@253 | 297 | final Block catchAllBlock = new Block(lineNumber, token, finish, catchAllNode); |
lagergren@211 | 298 | |
lagergren@211 | 299 | //catchallblock -> catchallnode (catchnode) -> exception -> throw |
lagergren@211 | 300 | |
lagergren@211 | 301 | return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower |
lagergren@211 | 302 | } |
lagergren@211 | 303 | |
lagergren@211 | 304 | private IdentNode compilerConstant(final CompilerConstants cc) { |
lagergren@290 | 305 | final FunctionNode functionNode = lc.getCurrentFunction(); |
lagergren@252 | 306 | return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName()); |
lagergren@211 | 307 | } |
lagergren@211 | 308 | |
lagergren@253 | 309 | private static boolean isTerminal(final List<Statement> statements) { |
lagergren@211 | 310 | return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags(); |
lagergren@211 | 311 | } |
lagergren@211 | 312 | |
lagergren@211 | 313 | /** |
lagergren@211 | 314 | * Splice finally code into all endpoints of a trynode |
lagergren@211 | 315 | * @param tryNode the try node |
lagergren@211 | 316 | * @param list of rethrowing throw nodes from synthetic catch blocks |
lagergren@211 | 317 | * @param finallyBody the code in the original finally block |
lagergren@211 | 318 | * @return new try node after splicing finally code (same if nop) |
lagergren@211 | 319 | */ |
lagergren@211 | 320 | private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) { |
lagergren@211 | 321 | assert tryNode.getFinallyBody() == null; |
lagergren@272 | 322 | final int finish = tryNode.getFinish(); |
lagergren@211 | 323 | |
lagergren@290 | 324 | final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@211 | 325 | final List<Node> insideTry = new ArrayList<>(); |
lagergren@211 | 326 | |
lagergren@211 | 327 | @Override |
lagergren@211 | 328 | public boolean enterDefault(final Node node) { |
lagergren@211 | 329 | insideTry.add(node); |
lagergren@211 | 330 | return true; |
lagergren@211 | 331 | } |
lagergren@211 | 332 | |
lagergren@211 | 333 | @Override |
lagergren@211 | 334 | public boolean enterFunctionNode(final FunctionNode functionNode) { |
lagergren@211 | 335 | // do not enter function nodes - finally code should not be inlined into them |
lagergren@211 | 336 | return false; |
lagergren@211 | 337 | } |
lagergren@211 | 338 | |
lagergren@211 | 339 | @Override |
lagergren@211 | 340 | public Node leaveThrowNode(final ThrowNode throwNode) { |
lagergren@211 | 341 | if (rethrows.contains(throwNode)) { |
attila@325 | 342 | final List<Statement> newStatements = copyFinally(finallyBody); |
lagergren@211 | 343 | if (!isTerminal(newStatements)) { |
lagergren@211 | 344 | newStatements.add(throwNode); |
lagergren@211 | 345 | } |
lagergren@253 | 346 | return new Block(throwNode.getLineNumber(), throwNode.getToken(), throwNode.getFinish(), newStatements); |
lagergren@211 | 347 | } |
lagergren@211 | 348 | return throwNode; |
lagergren@211 | 349 | } |
lagergren@211 | 350 | |
lagergren@211 | 351 | @Override |
lagergren@211 | 352 | public Node leaveBreakNode(final BreakNode breakNode) { |
lagergren@290 | 353 | return copy(breakNode, Lower.this.lc.getBreakable(breakNode.getLabel())); |
lagergren@211 | 354 | } |
lagergren@211 | 355 | |
lagergren@211 | 356 | @Override |
lagergren@211 | 357 | public Node leaveContinueNode(final ContinueNode continueNode) { |
lagergren@290 | 358 | return copy(continueNode, Lower.this.lc.getContinueTo(continueNode.getLabel())); |
lagergren@211 | 359 | } |
lagergren@211 | 360 | |
lagergren@211 | 361 | @Override |
lagergren@211 | 362 | public Node leaveReturnNode(final ReturnNode returnNode) { |
lagergren@211 | 363 | final Node expr = returnNode.getExpression(); |
lagergren@253 | 364 | final List<Statement> newStatements = new ArrayList<>(); |
lagergren@211 | 365 | |
lagergren@211 | 366 | final Node resultNode; |
lagergren@211 | 367 | if (expr != null) { |
lagergren@211 | 368 | //we need to evaluate the result of the return in case it is complex while |
lagergren@211 | 369 | //still in the try block, store it in a result value and return it afterwards |
lagergren@211 | 370 | resultNode = new IdentNode(Lower.this.compilerConstant(RETURN)); |
lagergren@253 | 371 | newStatements.add(new ExecuteNode(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); |
lagergren@211 | 372 | } else { |
lagergren@211 | 373 | resultNode = null; |
lagergren@211 | 374 | } |
lagergren@211 | 375 | |
attila@325 | 376 | newStatements.addAll(copyFinally(finallyBody)); |
lagergren@211 | 377 | if (!isTerminal(newStatements)) { |
lagergren@211 | 378 | newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode)); |
lagergren@211 | 379 | } |
lagergren@211 | 380 | |
lagergren@290 | 381 | return new ExecuteNode(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new Block(returnNode.getLineNumber(), returnNode.getToken(), lc.getCurrentBlock().getFinish(), newStatements)); |
lagergren@211 | 382 | } |
lagergren@211 | 383 | |
lagergren@253 | 384 | private Node copy(final Statement endpoint, final Node targetNode) { |
lagergren@211 | 385 | if (!insideTry.contains(targetNode)) { |
attila@325 | 386 | final List<Statement> newStatements = copyFinally(finallyBody); |
lagergren@211 | 387 | if (!isTerminal(newStatements)) { |
lagergren@211 | 388 | newStatements.add(endpoint); |
lagergren@211 | 389 | } |
lagergren@253 | 390 | return new ExecuteNode(endpoint.getLineNumber(), endpoint.getToken(), endpoint.getFinish(), new Block(endpoint.getLineNumber(), endpoint.getToken(), finish, newStatements)); |
lagergren@211 | 391 | } |
lagergren@211 | 392 | return endpoint; |
lagergren@211 | 393 | } |
lagergren@211 | 394 | }); |
lagergren@211 | 395 | |
lagergren@211 | 396 | addStatement(newTryNode); |
lagergren@211 | 397 | for (final Node statement : finallyBody.getStatements()) { |
lagergren@253 | 398 | addStatement((Statement)statement); |
lagergren@57 | 399 | } |
lagergren@57 | 400 | |
lagergren@211 | 401 | return newTryNode; |
lagergren@57 | 402 | } |
lagergren@57 | 403 | |
lagergren@57 | 404 | @Override |
attila@144 | 405 | public Node leaveTryNode(final TryNode tryNode) { |
lagergren@211 | 406 | final Block finallyBody = tryNode.getFinallyBody(); |
lagergren@57 | 407 | |
lagergren@211 | 408 | if (finallyBody == null) { |
lagergren@211 | 409 | return addStatement(tryNode); |
lagergren@57 | 410 | } |
lagergren@57 | 411 | |
lagergren@211 | 412 | /* |
lagergren@211 | 413 | * create a new trynode |
lagergren@211 | 414 | * if we have catches: |
lagergren@211 | 415 | * |
lagergren@211 | 416 | * try try |
lagergren@211 | 417 | * x try |
lagergren@211 | 418 | * catch x |
lagergren@211 | 419 | * y catch |
lagergren@211 | 420 | * finally z y |
lagergren@211 | 421 | * catchall |
lagergren@211 | 422 | * rethrow |
lagergren@211 | 423 | * |
lagergren@211 | 424 | * otheriwse |
lagergren@211 | 425 | * |
lagergren@211 | 426 | * try try |
lagergren@211 | 427 | * x x |
lagergren@211 | 428 | * finally catchall |
lagergren@211 | 429 | * y rethrow |
lagergren@211 | 430 | * |
lagergren@211 | 431 | * |
lagergren@211 | 432 | * now splice in finally code wherever needed |
lagergren@211 | 433 | * |
lagergren@211 | 434 | */ |
lagergren@211 | 435 | TryNode newTryNode; |
lagergren@57 | 436 | |
lagergren@211 | 437 | final Block catchAll = catchAllBlock(tryNode); |
lagergren@57 | 438 | |
lagergren@211 | 439 | final List<ThrowNode> rethrows = new ArrayList<>(); |
lagergren@290 | 440 | catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@211 | 441 | @Override |
lagergren@211 | 442 | public boolean enterThrowNode(final ThrowNode throwNode) { |
lagergren@211 | 443 | rethrows.add(throwNode); |
lagergren@211 | 444 | return true; |
lagergren@211 | 445 | } |
lagergren@211 | 446 | }); |
lagergren@211 | 447 | assert rethrows.size() == 1; |
lagergren@211 | 448 | |
lagergren@211 | 449 | if (tryNode.getCatchBlocks().isEmpty()) { |
lagergren@211 | 450 | newTryNode = tryNode.setFinallyBody(null); |
lagergren@211 | 451 | } else { |
lagergren@253 | 452 | Block outerBody = new Block(tryNode.getLineNumber(), tryNode.getToken(), tryNode.getFinish(), new ArrayList<Statement>(Arrays.asList(tryNode.setFinallyBody(null)))); |
lagergren@211 | 453 | newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null); |
lagergren@57 | 454 | } |
lagergren@57 | 455 | |
lagergren@211 | 456 | newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null); |
lagergren@211 | 457 | |
lagergren@211 | 458 | /* |
lagergren@211 | 459 | * Now that the transform is done, we have to go into the try and splice |
lagergren@211 | 460 | * the finally block in front of any statement that is outside the try |
lagergren@211 | 461 | */ |
lagergren@211 | 462 | return spliceFinally(newTryNode, rethrows, finallyBody); |
lagergren@57 | 463 | } |
lagergren@57 | 464 | |
lagergren@57 | 465 | @Override |
attila@144 | 466 | public Node leaveVarNode(final VarNode varNode) { |
lagergren@57 | 467 | addStatement(varNode); |
lagergren@290 | 468 | if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) { |
lagergren@253 | 469 | new ExecuteNode(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this); |
lagergren@211 | 470 | } |
lagergren@57 | 471 | return varNode; |
lagergren@57 | 472 | } |
lagergren@57 | 473 | |
lagergren@57 | 474 | @Override |
attila@144 | 475 | public Node leaveWhileNode(final WhileNode whileNode) { |
lagergren@252 | 476 | final Node test = whileNode.getTest(); |
lagergren@211 | 477 | final Block body = whileNode.getBody(); |
lagergren@57 | 478 | |
lagergren@211 | 479 | if (conservativeAlwaysTrue(test)) { |
lagergren@211 | 480 | //turn it into a for node without a test. |
lagergren@253 | 481 | final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), null, null, body, null, ForNode.IS_FOR).accept(this); |
lagergren@290 | 482 | lc.replace(whileNode, forNode); |
lagergren@211 | 483 | return forNode; |
lagergren@57 | 484 | } |
lagergren@57 | 485 | |
lagergren@211 | 486 | return addStatement(checkEscape(whileNode)); |
lagergren@57 | 487 | } |
lagergren@57 | 488 | |
lagergren@57 | 489 | @Override |
attila@144 | 490 | public Node leaveWithNode(final WithNode withNode) { |
lagergren@211 | 491 | return addStatement(withNode); |
lagergren@57 | 492 | } |
lagergren@57 | 493 | |
lagergren@57 | 494 | /** |
lagergren@57 | 495 | * Given a function node that is a callee in a CallNode, replace it with |
lagergren@57 | 496 | * the appropriate marker function. This is used by {@link CodeGenerator} |
lagergren@57 | 497 | * for fast scope calls |
lagergren@57 | 498 | * |
lagergren@57 | 499 | * @param function function called by a CallNode |
lagergren@57 | 500 | * @return transformed node to marker function or identity if not ident/access/indexnode |
lagergren@57 | 501 | */ |
lagergren@57 | 502 | private static Node markerFunction(final Node function) { |
lagergren@57 | 503 | if (function instanceof IdentNode) { |
attila@144 | 504 | return ((IdentNode)function).setIsFunction(); |
attila@144 | 505 | } else if (function instanceof BaseNode) { |
attila@144 | 506 | return ((BaseNode)function).setIsFunction(); |
lagergren@57 | 507 | } |
lagergren@57 | 508 | return function; |
lagergren@57 | 509 | } |
lagergren@57 | 510 | |
lagergren@57 | 511 | /** |
lagergren@57 | 512 | * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval> |
lagergren@57 | 513 | * @param node a node |
lagergren@57 | 514 | * @return eval location |
lagergren@57 | 515 | */ |
lagergren@252 | 516 | private String evalLocation(final IdentNode node) { |
lagergren@290 | 517 | final Source source = lc.getCurrentFunction().getSource(); |
lagergren@57 | 518 | return new StringBuilder(). |
lagergren@252 | 519 | append(source.getName()). |
lagergren@57 | 520 | append('#'). |
lagergren@252 | 521 | append(source.getLine(node.position())). |
lagergren@57 | 522 | append("<eval>"). |
lagergren@57 | 523 | toString(); |
lagergren@57 | 524 | } |
lagergren@57 | 525 | |
lagergren@57 | 526 | /** |
lagergren@57 | 527 | * Check whether a call node may be a call to eval. In that case we |
lagergren@57 | 528 | * clone the args in order to create the following construct in |
lagergren@57 | 529 | * {@link CodeGenerator} |
lagergren@57 | 530 | * |
lagergren@57 | 531 | * <pre> |
lagergren@57 | 532 | * if (calledFuntion == buildInEval) { |
lagergren@57 | 533 | * eval(cloned arg); |
lagergren@57 | 534 | * } else { |
lagergren@57 | 535 | * cloned arg; |
lagergren@57 | 536 | * } |
lagergren@57 | 537 | * </pre> |
lagergren@57 | 538 | * |
lagergren@57 | 539 | * @param callNode call node to check if it's an eval |
lagergren@57 | 540 | */ |
lagergren@211 | 541 | private CallNode checkEval(final CallNode callNode) { |
lagergren@57 | 542 | if (callNode.getFunction() instanceof IdentNode) { |
lagergren@57 | 543 | |
lagergren@57 | 544 | final List<Node> args = callNode.getArgs(); |
lagergren@57 | 545 | final IdentNode callee = (IdentNode)callNode.getFunction(); |
lagergren@57 | 546 | |
lagergren@57 | 547 | // 'eval' call with at least one argument |
lagergren@211 | 548 | if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) { |
lagergren@290 | 549 | final FunctionNode currentFunction = lc.getCurrentFunction(); |
lagergren@211 | 550 | return callNode.setEvalArgs( |
lagergren@57 | 551 | new CallNode.EvalArgs( |
attila@325 | 552 | ensureUniqueNamesIn(args.get(0)).accept(this), |
lagergren@211 | 553 | compilerConstant(THIS), |
lagergren@57 | 554 | evalLocation(callee), |
lagergren@211 | 555 | currentFunction.isStrict())); |
lagergren@57 | 556 | } |
lagergren@57 | 557 | } |
lagergren@211 | 558 | |
lagergren@211 | 559 | return callNode; |
lagergren@57 | 560 | } |
lagergren@57 | 561 | |
lagergren@57 | 562 | private static boolean conservativeAlwaysTrue(final Node node) { |
lagergren@57 | 563 | return node == null || ((node instanceof LiteralNode) && Boolean.TRUE.equals(((LiteralNode<?>)node).getValue())); |
lagergren@57 | 564 | } |
lagergren@57 | 565 | |
lagergren@57 | 566 | /** |
lagergren@57 | 567 | * Helper that given a loop body makes sure that it is not terminal if it |
lagergren@57 | 568 | * has a continue that leads to the loop header or to outer loops' loop |
lagergren@57 | 569 | * headers. This means that, even if the body ends with a terminal |
lagergren@57 | 570 | * statement, we cannot tag it as terminal |
lagergren@57 | 571 | * |
lagergren@57 | 572 | * @param loopBody the loop body to check |
lagergren@57 | 573 | * @return true if control flow may escape the loop |
lagergren@57 | 574 | */ |
lagergren@211 | 575 | private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) { |
lagergren@57 | 576 | final List<Node> escapes = new ArrayList<>(); |
lagergren@57 | 577 | |
lagergren@290 | 578 | loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
lagergren@57 | 579 | @Override |
attila@144 | 580 | public Node leaveBreakNode(final BreakNode node) { |
lagergren@57 | 581 | escapes.add(node); |
lagergren@57 | 582 | return node; |
lagergren@57 | 583 | } |
lagergren@57 | 584 | |
lagergren@57 | 585 | @Override |
attila@144 | 586 | public Node leaveContinueNode(final ContinueNode node) { |
lagergren@57 | 587 | // all inner loops have been popped. |
lagergren@211 | 588 | if (lex.contains(lex.getContinueTo(node.getLabel()))) { |
lagergren@57 | 589 | escapes.add(node); |
lagergren@57 | 590 | } |
lagergren@57 | 591 | return node; |
lagergren@57 | 592 | } |
lagergren@57 | 593 | }); |
lagergren@57 | 594 | |
lagergren@57 | 595 | return !escapes.isEmpty(); |
lagergren@57 | 596 | } |
lagergren@57 | 597 | |
lagergren@211 | 598 | private LoopNode checkEscape(final LoopNode loopNode) { |
lagergren@211 | 599 | final boolean escapes = controlFlowEscapes(lc, loopNode.getBody()); |
lagergren@211 | 600 | if (escapes) { |
lagergren@211 | 601 | return loopNode. |
lagergren@211 | 602 | setBody(lc, loopNode.getBody().setIsTerminal(lc, false)). |
lagergren@211 | 603 | setControlFlowEscapes(lc, escapes); |
lagergren@57 | 604 | } |
lagergren@211 | 605 | return loopNode; |
lagergren@57 | 606 | } |
lagergren@57 | 607 | |
lagergren@57 | 608 | |
lagergren@253 | 609 | private Node addStatement(final Statement statement) { |
lagergren@290 | 610 | lc.appendStatement(statement); |
lagergren@211 | 611 | return statement; |
lagergren@57 | 612 | } |
lagergren@57 | 613 | |
jlaskey@3 | 614 | /** |
jlaskey@3 | 615 | * An internal expression has a symbol that is tagged internal. Check if |
jlaskey@3 | 616 | * this is such a node |
jlaskey@3 | 617 | * |
jlaskey@3 | 618 | * @param expression expression to check for internal symbol |
jlaskey@3 | 619 | * @return true if internal, false otherwise |
jlaskey@3 | 620 | */ |
jlaskey@3 | 621 | private static boolean isInternalExpression(final Node expression) { |
jlaskey@3 | 622 | final Symbol symbol = expression.getSymbol(); |
jlaskey@3 | 623 | return symbol != null && symbol.isInternal(); |
jlaskey@3 | 624 | } |
jlaskey@3 | 625 | |
jlaskey@3 | 626 | /** |
lagergren@57 | 627 | * Is this an assignment to the special variable that hosts scripting eval |
lagergren@211 | 628 | * results, i.e. __return__? |
jlaskey@3 | 629 | * |
lagergren@57 | 630 | * @param expression expression to check whether it is $evalresult = X |
lagergren@57 | 631 | * @return true if an assignment to eval result, false otherwise |
jlaskey@3 | 632 | */ |
lagergren@211 | 633 | private static boolean isEvalResultAssignment(final Node expression) { |
lagergren@57 | 634 | Node e = expression; |
lagergren@211 | 635 | assert e.tokenType() != TokenType.DISCARD; //there are no discards this early anymore |
lagergren@211 | 636 | if (e instanceof BinaryNode) { |
lagergren@211 | 637 | final Node lhs = ((BinaryNode)e).lhs(); |
lagergren@211 | 638 | if (lhs instanceof IdentNode) { |
lagergren@211 | 639 | return ((IdentNode)lhs).getName().equals(RETURN.symbolName()); |
attila@62 | 640 | } |
attila@62 | 641 | } |
lagergren@211 | 642 | return false; |
jlaskey@3 | 643 | } |
jlaskey@3 | 644 | |
lagergren@57 | 645 | } |