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