Lower.java revision 1135:f3a3d20c03f8
1133123Sjmg/*
2133123Sjmg * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3133123Sjmg * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4133123Sjmg *
5133123Sjmg * This code is free software; you can redistribute it and/or modify it
6133123Sjmg * under the terms of the GNU General Public License version 2 only, as
7133123Sjmg * published by the Free Software Foundation.  Oracle designates this
8133123Sjmg * particular file as subject to the "Classpath" exception as provided
9133123Sjmg * by Oracle in the LICENSE file that accompanied this code.
10133123Sjmg *
11133123Sjmg * This code is distributed in the hope that it will be useful, but WITHOUT
12133123Sjmg * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13133123Sjmg * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14133123Sjmg * version 2 for more details (a copy is included in the LICENSE file that
15133123Sjmg * accompanied this code).
16133123Sjmg *
17133123Sjmg * You should have received a copy of the GNU General Public License version
18133123Sjmg * 2 along with this work; if not, write to the Free Software Foundation,
19133123Sjmg * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20133123Sjmg *
21133123Sjmg * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22133123Sjmg * or visit www.oracle.com if you need additional information or have any
23133123Sjmg * questions.
24133123Sjmg */
25133123Sjmg
26133123Sjmgpackage jdk.nashorn.internal.codegen;
27133123Sjmg
28133123Sjmgimport static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
29133123Sjmgimport static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
30133123Sjmgimport static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
31133123Sjmg
32133123Sjmgimport java.util.ArrayList;
33133123Sjmgimport java.util.Arrays;
34143864Sjmgimport java.util.Collections;
35133123Sjmgimport java.util.List;
36133123Sjmgimport java.util.ListIterator;
37133123Sjmgimport jdk.nashorn.internal.ir.BaseNode;
38133123Sjmgimport jdk.nashorn.internal.ir.BinaryNode;
39133123Sjmgimport jdk.nashorn.internal.ir.Block;
40133123Sjmgimport jdk.nashorn.internal.ir.BlockLexicalContext;
41133123Sjmgimport jdk.nashorn.internal.ir.BlockStatement;
42133123Sjmgimport jdk.nashorn.internal.ir.BreakNode;
43133123Sjmgimport jdk.nashorn.internal.ir.CallNode;
44133123Sjmgimport jdk.nashorn.internal.ir.CaseNode;
45133123Sjmgimport jdk.nashorn.internal.ir.CatchNode;
46133123Sjmgimport jdk.nashorn.internal.ir.ContinueNode;
47133123Sjmgimport jdk.nashorn.internal.ir.EmptyNode;
48133123Sjmgimport jdk.nashorn.internal.ir.Expression;
49133123Sjmgimport jdk.nashorn.internal.ir.ExpressionStatement;
50133123Sjmgimport jdk.nashorn.internal.ir.ForNode;
51133123Sjmgimport jdk.nashorn.internal.ir.FunctionNode;
52133123Sjmgimport jdk.nashorn.internal.ir.FunctionNode.CompilationState;
53133123Sjmgimport jdk.nashorn.internal.ir.IdentNode;
54133123Sjmgimport jdk.nashorn.internal.ir.IfNode;
55133123Sjmgimport jdk.nashorn.internal.ir.JumpStatement;
56133123Sjmgimport jdk.nashorn.internal.ir.LabelNode;
57133123Sjmgimport jdk.nashorn.internal.ir.LexicalContext;
58133123Sjmgimport jdk.nashorn.internal.ir.LiteralNode;
59133123Sjmgimport jdk.nashorn.internal.ir.LoopNode;
60133123Sjmgimport jdk.nashorn.internal.ir.Node;
61133123Sjmgimport jdk.nashorn.internal.ir.ReturnNode;
62133123Sjmgimport jdk.nashorn.internal.ir.RuntimeNode;
63133123Sjmgimport jdk.nashorn.internal.ir.Statement;
64143864Sjmgimport jdk.nashorn.internal.ir.SwitchNode;
65133123Sjmgimport jdk.nashorn.internal.ir.Symbol;
66133123Sjmgimport jdk.nashorn.internal.ir.ThrowNode;
67133123Sjmgimport jdk.nashorn.internal.ir.TryNode;
68133123Sjmgimport jdk.nashorn.internal.ir.VarNode;
69133123Sjmgimport jdk.nashorn.internal.ir.WhileNode;
70133123Sjmgimport jdk.nashorn.internal.ir.WithNode;
71228975Suqsimport jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
72133123Sjmgimport jdk.nashorn.internal.ir.visitor.NodeVisitor;
73228975Suqsimport jdk.nashorn.internal.parser.Token;
74133123Sjmgimport jdk.nashorn.internal.parser.TokenType;
75133123Sjmgimport jdk.nashorn.internal.runtime.Context;
76133123Sjmgimport jdk.nashorn.internal.runtime.JSType;
77133123Sjmgimport jdk.nashorn.internal.runtime.Source;
78133123Sjmgimport jdk.nashorn.internal.runtime.logging.DebugLogger;
79133123Sjmgimport jdk.nashorn.internal.runtime.logging.Loggable;
80133123Sjmgimport jdk.nashorn.internal.runtime.logging.Logger;
81133123Sjmg
82133123Sjmg/**
83133123Sjmg * Lower to more primitive operations. After lowering, an AST still has no symbols
84133123Sjmg * and types, but several nodes have been turned into more low level constructs
85133123Sjmg * and control flow termination criteria have been computed.
86133123Sjmg *
87133123Sjmg * We do things like code copying/inlining of finallies here, as it is much
88133123Sjmg * harder and context dependent to do any code copying after symbols have been
89133123Sjmg * finalized.
90228975Suqs */
91133123Sjmg@Logger(name="lower")
92133123Sjmgfinal class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Loggable {
93133123Sjmg
94133123Sjmg    private final DebugLogger log;
95133123Sjmg
96133123Sjmg    /**
97133123Sjmg     * Constructor.
98133123Sjmg     */
99133123Sjmg    Lower(final Compiler compiler) {
100133123Sjmg        super(new BlockLexicalContext() {
101133123Sjmg
102133123Sjmg            @Override
103133123Sjmg            public List<Statement> popStatements() {
104133123Sjmg                final List<Statement> newStatements = new ArrayList<>();
105133123Sjmg                boolean terminated = false;
106133123Sjmg
107133123Sjmg                final List<Statement> statements = super.popStatements();
108133123Sjmg                for (final Statement statement : statements) {
109133123Sjmg                    if (!terminated) {
110133123Sjmg                        newStatements.add(statement);
111133123Sjmg                        if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
112133123Sjmg                            terminated = true;
113133123Sjmg                        }
114133123Sjmg                    } else {
115133123Sjmg                        statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
116133123Sjmg                            @Override
117133123Sjmg                            public boolean enterVarNode(final VarNode varNode) {
118133123Sjmg                                newStatements.add(varNode.setInit(null));
119133123Sjmg                                return false;
120133123Sjmg                            }
121133123Sjmg                        });
122133123Sjmg                    }
123133123Sjmg                }
124133123Sjmg                return newStatements;
125133123Sjmg            }
126133123Sjmg
127133123Sjmg            @Override
128133123Sjmg            protected Block afterSetStatements(final Block block) {
129133123Sjmg                final List<Statement> stmts = block.getStatements();
130133123Sjmg                for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) {
131133123Sjmg                    final Statement stmt = li.previous();
132133123Sjmg                    // popStatements() guarantees that the only thing after a terminal statement are uninitialized
133133123Sjmg                    // VarNodes. We skip past those, and set the terminal state of the block to the value of the
134133123Sjmg                    // terminal state of the first statement that is not an uninitialized VarNode.
135133123Sjmg                    if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) {
136133123Sjmg                        return block.setIsTerminal(this, stmt.isTerminal());
137133123Sjmg                    }
138133123Sjmg                }
139133123Sjmg                return block.setIsTerminal(this, false);
140133123Sjmg            }
141133123Sjmg        });
142133123Sjmg
143        this.log       = initLogger(compiler.getContext());
144    }
145
146    @Override
147    public DebugLogger getLogger() {
148        return log;
149    }
150
151    @Override
152    public DebugLogger initLogger(final Context context) {
153        return context.getLogger(this.getClass());
154    }
155
156    @Override
157    public boolean enterBreakNode(final BreakNode breakNode) {
158        addStatement(breakNode);
159        return false;
160    }
161
162    @Override
163    public Node leaveCallNode(final CallNode callNode) {
164        return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
165    }
166
167    @Override
168    public Node leaveCatchNode(final CatchNode catchNode) {
169        return addStatement(catchNode);
170    }
171
172    @Override
173    public boolean enterContinueNode(final ContinueNode continueNode) {
174        addStatement(continueNode);
175        return false;
176    }
177
178    @Override
179    public boolean enterEmptyNode(final EmptyNode emptyNode) {
180        return false;
181    }
182
183    @Override
184    public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
185        final Expression expr = expressionStatement.getExpression();
186        ExpressionStatement node = expressionStatement;
187
188        final FunctionNode currentFunction = lc.getCurrentFunction();
189
190        if (currentFunction.isProgram()) {
191            if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
192                node = expressionStatement.setExpression(
193                    new BinaryNode(
194                        Token.recast(
195                            expressionStatement.getToken(),
196                            TokenType.ASSIGN),
197                        compilerConstant(RETURN),
198                    expr));
199            }
200        }
201
202        return addStatement(node);
203    }
204
205    @Override
206    public Node leaveBlockStatement(final BlockStatement blockStatement) {
207        return addStatement(blockStatement);
208    }
209
210    @Override
211    public Node leaveForNode(final ForNode forNode) {
212        ForNode newForNode = forNode;
213
214        final Expression test = forNode.getTest();
215        if (!forNode.isForIn() && isAlwaysTrue(test)) {
216            newForNode = forNode.setTest(lc, null);
217        }
218
219        newForNode = checkEscape(newForNode);
220        if(newForNode.isForIn()) {
221            // Wrap it in a block so its internally created iterator is restricted in scope
222            addStatementEnclosedInBlock(newForNode);
223        } else {
224            addStatement(newForNode);
225        }
226        return newForNode;
227    }
228
229    @Override
230    public Node leaveFunctionNode(final FunctionNode functionNode) {
231        log.info("END FunctionNode: ", functionNode.getName());
232        return functionNode.setState(lc, CompilationState.LOWERED);
233    }
234
235    @Override
236    public Node leaveIfNode(final IfNode ifNode) {
237        return addStatement(ifNode);
238    }
239
240    @Override
241    public Node leaveIN(final BinaryNode binaryNode) {
242        return new RuntimeNode(binaryNode);
243    }
244
245    @Override
246    public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
247        return new RuntimeNode(binaryNode);
248    }
249
250    @Override
251    public Node leaveLabelNode(final LabelNode labelNode) {
252        return addStatement(labelNode);
253    }
254
255    @Override
256    public Node leaveReturnNode(final ReturnNode returnNode) {
257        addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
258        return returnNode;
259    }
260
261    @Override
262    public Node leaveCaseNode(final CaseNode caseNode) {
263        // Try to represent the case test as an integer
264        final Node test = caseNode.getTest();
265        if (test instanceof LiteralNode) {
266            final LiteralNode<?> lit = (LiteralNode<?>)test;
267            if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
268                if (JSType.isRepresentableAsInt(lit.getNumber())) {
269                    return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
270                }
271            }
272        }
273        return caseNode;
274    }
275
276    @Override
277    public Node leaveSwitchNode(final SwitchNode switchNode) {
278        if(!switchNode.isUniqueInteger()) {
279            // Wrap it in a block so its internally created tag is restricted in scope
280            addStatementEnclosedInBlock(switchNode);
281        } else {
282            addStatement(switchNode);
283        }
284        return switchNode;
285    }
286
287    @Override
288    public Node leaveThrowNode(final ThrowNode throwNode) {
289        return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
290    }
291
292    private static Node ensureUniqueNamesIn(final Node node) {
293        return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
294            @Override
295            public Node leaveFunctionNode(final FunctionNode functionNode) {
296                final String name = functionNode.getName();
297                return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
298            }
299
300            @Override
301            public Node leaveDefault(final Node labelledNode) {
302                return labelledNode.ensureUniqueLabels(lc);
303            }
304        });
305    }
306
307    private static List<Statement> copyFinally(final Block finallyBody) {
308        final List<Statement> newStatements = new ArrayList<>();
309        for (final Statement statement : finallyBody.getStatements()) {
310            newStatements.add((Statement)ensureUniqueNamesIn(statement));
311            if (statement.hasTerminalFlags()) {
312                return newStatements;
313            }
314        }
315        return newStatements;
316    }
317
318    private Block catchAllBlock(final TryNode tryNode) {
319        final int  lineNumber = tryNode.getLineNumber();
320        final long token      = tryNode.getToken();
321        final int  finish     = tryNode.getFinish();
322
323        final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
324
325        final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true));
326        assert catchBody.isTerminal(); //ends with throw, so terminal
327
328        final CatchNode catchAllNode  = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true);
329        final Block     catchAllBlock = new Block(token, finish, catchAllNode);
330
331        //catchallblock -> catchallnode (catchnode) -> exception -> throw
332
333        return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
334    }
335
336    private IdentNode compilerConstant(final CompilerConstants cc) {
337        final FunctionNode functionNode = lc.getCurrentFunction();
338        return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
339    }
340
341    private static boolean isTerminal(final List<Statement> statements) {
342        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
343    }
344
345    /**
346     * Splice finally code into all endpoints of a trynode
347     * @param tryNode the try node
348     * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
349     * @param finallyBody the code in the original finally block
350     * @return new try node after splicing finally code (same if nop)
351     */
352    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
353        assert tryNode.getFinallyBody() == null;
354
355        final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
356            final List<Node> insideTry = new ArrayList<>();
357
358            @Override
359            public boolean enterDefault(final Node node) {
360                insideTry.add(node);
361                return true;
362            }
363
364            @Override
365            public boolean enterFunctionNode(final FunctionNode functionNode) {
366                // do not enter function nodes - finally code should not be inlined into them
367                return false;
368            }
369
370            @Override
371            public Node leaveThrowNode(final ThrowNode throwNode) {
372                if (rethrows.contains(throwNode)) {
373                    final List<Statement> newStatements = copyFinally(finallyBody);
374                    if (!isTerminal(newStatements)) {
375                        newStatements.add(throwNode);
376                    }
377                    return BlockStatement.createReplacement(throwNode, newStatements);
378                }
379                return throwNode;
380            }
381
382            @Override
383            public Node leaveBreakNode(final BreakNode breakNode) {
384                return leaveJumpStatement(breakNode);
385            }
386
387            @Override
388            public Node leaveContinueNode(final ContinueNode continueNode) {
389                return leaveJumpStatement(continueNode);
390            }
391
392            private Node leaveJumpStatement(final JumpStatement jump) {
393                return copy(jump, (Node)jump.getTarget(Lower.this.lc));
394            }
395
396            @Override
397            public Node leaveReturnNode(final ReturnNode returnNode) {
398                final Expression expr  = returnNode.getExpression();
399                final List<Statement> newStatements = new ArrayList<>();
400
401                final Expression resultNode;
402                if (expr != null) {
403                    //we need to evaluate the result of the return in case it is complex while
404                    //still in the try block, store it in a result value and return it afterwards
405                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
406                    newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
407                } else {
408                    resultNode = null;
409                }
410
411                newStatements.addAll(copyFinally(finallyBody));
412                if (!isTerminal(newStatements)) {
413                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
414                }
415
416                return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
417            }
418
419            private Node copy(final Statement endpoint, final Node targetNode) {
420                if (!insideTry.contains(targetNode)) {
421                    final List<Statement> newStatements = copyFinally(finallyBody);
422                    if (!isTerminal(newStatements)) {
423                        newStatements.add(endpoint);
424                    }
425                    return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
426                }
427                return endpoint;
428            }
429        });
430
431        addStatement(newTryNode);
432        for (final Node statement : finallyBody.getStatements()) {
433            addStatement((Statement)statement);
434        }
435
436        return newTryNode;
437    }
438
439    @Override
440    public Node leaveTryNode(final TryNode tryNode) {
441        final Block finallyBody = tryNode.getFinallyBody();
442
443        if (finallyBody == null) {
444            return addStatement(ensureUnconditionalCatch(tryNode));
445        }
446
447        /*
448         * create a new trynode
449         *    if we have catches:
450         *
451         *    try            try
452         *       x              try
453         *    catch               x
454         *       y              catch
455         *    finally z           y
456         *                   catchall
457         *                        rethrow
458         *
459         *   otheriwse
460         *
461         *   try              try
462         *      x               x
463         *   finally          catchall
464         *      y               rethrow
465         *
466         *
467         *   now splice in finally code wherever needed
468         *
469         */
470        TryNode newTryNode;
471
472        final Block catchAll = catchAllBlock(tryNode);
473
474        final List<ThrowNode> rethrows = new ArrayList<>();
475        catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
476            @Override
477            public boolean enterThrowNode(final ThrowNode throwNode) {
478                rethrows.add(throwNode);
479                return true;
480            }
481        });
482        assert rethrows.size() == 1;
483
484        if (tryNode.getCatchBlocks().isEmpty()) {
485            newTryNode = tryNode.setFinallyBody(null);
486        } else {
487            final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
488            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
489        }
490
491        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
492
493        /*
494         * Now that the transform is done, we have to go into the try and splice
495         * the finally block in front of any statement that is outside the try
496         */
497        return spliceFinally(newTryNode, rethrows, finallyBody);
498    }
499
500    private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
501        final List<CatchNode> catches = tryNode.getCatches();
502        if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
503            return tryNode;
504        }
505        // If the last catch block is conditional, add an unconditional rethrow block
506        final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
507
508        newCatchBlocks.add(catchAllBlock(tryNode));
509        return tryNode.setCatchBlocks(newCatchBlocks);
510    }
511
512    @Override
513    public Node leaveVarNode(final VarNode varNode) {
514        addStatement(varNode);
515        if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) {
516            new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
517        }
518        return varNode;
519    }
520
521    @Override
522    public Node leaveWhileNode(final WhileNode whileNode) {
523        final Expression test = whileNode.getTest();
524        final Block body = whileNode.getBody();
525
526        if (isAlwaysTrue(test)) {
527            //turn it into a for node without a test.
528            final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this);
529            lc.replace(whileNode, forNode);
530            return forNode;
531        }
532
533         return addStatement(checkEscape(whileNode));
534    }
535
536    @Override
537    public Node leaveWithNode(final WithNode withNode) {
538        return addStatement(withNode);
539    }
540
541    /**
542     * Given a function node that is a callee in a CallNode, replace it with
543     * the appropriate marker function. This is used by {@link CodeGenerator}
544     * for fast scope calls
545     *
546     * @param function function called by a CallNode
547     * @return transformed node to marker function or identity if not ident/access/indexnode
548     */
549    private static Expression markerFunction(final Expression function) {
550        if (function instanceof IdentNode) {
551            return ((IdentNode)function).setIsFunction();
552        } else if (function instanceof BaseNode) {
553            return ((BaseNode)function).setIsFunction();
554        }
555        return function;
556    }
557
558    /**
559     * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval>
560     * @param node a node
561     * @return eval location
562     */
563    private String evalLocation(final IdentNode node) {
564        final Source source = lc.getCurrentFunction().getSource();
565        final int pos = node.position();
566        return new StringBuilder().
567            append(source.getName()).
568            append('#').
569            append(source.getLine(pos)).
570            append(':').
571            append(source.getColumn(pos)).
572            append("<eval>").
573            toString();
574    }
575
576    /**
577     * Check whether a call node may be a call to eval. In that case we
578     * clone the args in order to create the following construct in
579     * {@link CodeGenerator}
580     *
581     * <pre>
582     * if (calledFuntion == buildInEval) {
583     *    eval(cloned arg);
584     * } else {
585     *    cloned arg;
586     * }
587     * </pre>
588     *
589     * @param callNode call node to check if it's an eval
590     */
591    private CallNode checkEval(final CallNode callNode) {
592        if (callNode.getFunction() instanceof IdentNode) {
593
594            final List<Expression> args = callNode.getArgs();
595            final IdentNode callee = (IdentNode)callNode.getFunction();
596
597            // 'eval' call with at least one argument
598            if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
599                final List<Expression> evalArgs = new ArrayList<>(args.size());
600                for(final Expression arg: args) {
601                    evalArgs.add((Expression)ensureUniqueNamesIn(arg).accept(this));
602                }
603                return callNode.setEvalArgs(new CallNode.EvalArgs(evalArgs, evalLocation(callee)));
604            }
605        }
606
607        return callNode;
608    }
609
610    /**
611     * Helper that given a loop body makes sure that it is not terminal if it
612     * has a continue that leads to the loop header or to outer loops' loop
613     * headers. This means that, even if the body ends with a terminal
614     * statement, we cannot tag it as terminal
615     *
616     * @param loopBody the loop body to check
617     * @return true if control flow may escape the loop
618     */
619    private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
620        final List<Node> escapes = new ArrayList<>();
621
622        loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
623            @Override
624            public Node leaveBreakNode(final BreakNode node) {
625                escapes.add(node);
626                return node;
627            }
628
629            @Override
630            public Node leaveContinueNode(final ContinueNode node) {
631                // all inner loops have been popped.
632                if (lex.contains(node.getTarget(lex))) {
633                    escapes.add(node);
634                }
635                return node;
636            }
637        });
638
639        return !escapes.isEmpty();
640    }
641
642    @SuppressWarnings("unchecked")
643    private <T extends LoopNode> T checkEscape(final T loopNode) {
644        final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
645        if (escapes) {
646            return (T)loopNode.
647                setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
648                setControlFlowEscapes(lc, escapes);
649        }
650        return loopNode;
651    }
652
653
654    private Node addStatement(final Statement statement) {
655        lc.appendStatement(statement);
656        return statement;
657    }
658
659    private void addStatementEnclosedInBlock(final Statement stmt) {
660        BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt));
661        if(stmt.isTerminal()) {
662            b = b.setBlock(b.getBlock().setIsTerminal(null, true));
663        }
664        addStatement(b);
665    }
666
667    /**
668     * An internal expression has a symbol that is tagged internal. Check if
669     * this is such a node
670     *
671     * @param expression expression to check for internal symbol
672     * @return true if internal, false otherwise
673     */
674    private static boolean isInternalExpression(final Expression expression) {
675        if (!(expression instanceof IdentNode)) {
676            return false;
677        }
678        final Symbol symbol = ((IdentNode)expression).getSymbol();
679        return symbol != null && symbol.isInternal();
680    }
681
682    /**
683     * Is this an assignment to the special variable that hosts scripting eval
684     * results, i.e. __return__?
685     *
686     * @param expression expression to check whether it is $evalresult = X
687     * @return true if an assignment to eval result, false otherwise
688     */
689    private static boolean isEvalResultAssignment(final Node expression) {
690        final Node e = expression;
691        if (e instanceof BinaryNode) {
692            final Node lhs = ((BinaryNode)e).lhs();
693            if (lhs instanceof IdentNode) {
694                return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
695            }
696        }
697        return false;
698    }
699}
700