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