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