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