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