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