View Javadoc
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.codegen.CompilerConstants.THIS;
31  
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.List;
35  import java.util.ListIterator;
36  import jdk.nashorn.internal.ir.BaseNode;
37  import jdk.nashorn.internal.ir.BinaryNode;
38  import jdk.nashorn.internal.ir.Block;
39  import jdk.nashorn.internal.ir.BlockLexicalContext;
40  import jdk.nashorn.internal.ir.BlockStatement;
41  import jdk.nashorn.internal.ir.BreakNode;
42  import jdk.nashorn.internal.ir.CallNode;
43  import jdk.nashorn.internal.ir.CatchNode;
44  import jdk.nashorn.internal.ir.ContinueNode;
45  import jdk.nashorn.internal.ir.EmptyNode;
46  import jdk.nashorn.internal.ir.Expression;
47  import jdk.nashorn.internal.ir.ExpressionStatement;
48  import jdk.nashorn.internal.ir.ForNode;
49  import jdk.nashorn.internal.ir.FunctionNode;
50  import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
51  import jdk.nashorn.internal.ir.IdentNode;
52  import jdk.nashorn.internal.ir.IfNode;
53  import jdk.nashorn.internal.ir.LabelNode;
54  import jdk.nashorn.internal.ir.LexicalContext;
55  import jdk.nashorn.internal.ir.LiteralNode;
56  import jdk.nashorn.internal.ir.LoopNode;
57  import jdk.nashorn.internal.ir.Node;
58  import jdk.nashorn.internal.ir.ReturnNode;
59  import jdk.nashorn.internal.ir.Statement;
60  import jdk.nashorn.internal.ir.SwitchNode;
61  import jdk.nashorn.internal.ir.Symbol;
62  import jdk.nashorn.internal.ir.ThrowNode;
63  import jdk.nashorn.internal.ir.TryNode;
64  import jdk.nashorn.internal.ir.VarNode;
65  import jdk.nashorn.internal.ir.WhileNode;
66  import jdk.nashorn.internal.ir.WithNode;
67  import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
68  import jdk.nashorn.internal.ir.visitor.NodeVisitor;
69  import jdk.nashorn.internal.parser.Token;
70  import jdk.nashorn.internal.parser.TokenType;
71  import jdk.nashorn.internal.runtime.CodeInstaller;
72  import jdk.nashorn.internal.runtime.DebugLogger;
73  import jdk.nashorn.internal.runtime.ScriptRuntime;
74  import jdk.nashorn.internal.runtime.Source;
75  
76  /**
77   * Lower to more primitive operations. After lowering, an AST still has no symbols
78   * and types, but several nodes have been turned into more low level constructs
79   * and control flow termination criteria have been computed.
80   *
81   * We do things like code copying/inlining of finallies here, as it is much
82   * harder and context dependent to do any code copying after symbols have been
83   * finalized.
84   */
85  
86  final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
87  
88      private static final DebugLogger LOG = new DebugLogger("lower");
89  
90      // needed only to get unique eval id
91      private final CodeInstaller<?> installer;
92  
93      /**
94       * Constructor.
95       */
96      Lower(final CodeInstaller<?> installer) {
97          super(new BlockLexicalContext() {
98  
99              @Override
100             public List<Statement> popStatements() {
101                 final List<Statement> newStatements = new ArrayList<>();
102                 boolean terminated = false;
103 
104                 final List<Statement> statements = super.popStatements();
105                 for (final Statement statement : statements) {
106                     if (!terminated) {
107                         newStatements.add(statement);
108                         if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
109                             terminated = true;
110                         }
111                     } else {
112                         statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
113                             @Override
114                             public boolean enterVarNode(final VarNode varNode) {
115                                 newStatements.add(varNode.setInit(null));
116                                 return false;
117                             }
118                         });
119                     }
120                 }
121                 return newStatements;
122             }
123 
124             @Override
125             protected Block afterSetStatements(final Block block) {
126                 final List<Statement> stmts = block.getStatements();
127                 for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) {
128                     final Statement stmt = li.previous();
129                     // popStatements() guarantees that the only thing after a terminal statement are uninitialized
130                     // VarNodes. We skip past those, and set the terminal state of the block to the value of the
131                     // terminal state of the first statement that is not an uninitialized VarNode.
132                     if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) {
133                         return block.setIsTerminal(this, stmt.isTerminal());
134                     }
135                 }
136                 return block.setIsTerminal(this, false);
137             }
138         });
139         this.installer = installer;
140     }
141 
142     @Override
143     public boolean enterBlock(final Block block) {
144         final FunctionNode   function = lc.getCurrentFunction();
145         if (lc.isFunctionBody() && function.isProgram() && !function.hasDeclaredFunctions()) {
146             new ExpressionStatement(function.getLineNumber(), block.getToken(), block.getFinish(), LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)).accept(this);
147         }
148         return true;
149     }
150 
151     @Override
152     public Node leaveBlock(final Block block) {
153         //now we have committed the entire statement list to the block, but we need to truncate
154         //whatever is after the last terminal. block append won't append past it
155 
156 
157         if (lc.isFunctionBody()) {
158             final FunctionNode currentFunction = lc.getCurrentFunction();
159             final boolean isProgram = currentFunction.isProgram();
160             final Statement last = lc.getLastStatement();
161             final ReturnNode returnNode = new ReturnNode(
162                 last == null ? currentFunction.getLineNumber() : last.getLineNumber(), //TODO?
163                 currentFunction.getToken(),
164                 currentFunction.getFinish(),
165                 isProgram ?
166                     compilerConstant(RETURN) :
167                     LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED));
168 
169             returnNode.accept(this);
170         }
171 
172         return block;
173     }
174 
175     @Override
176     public boolean enterBreakNode(final BreakNode breakNode) {
177         addStatement(breakNode);
178         return false;
179     }
180 
181     @Override
182     public Node leaveCallNode(final CallNode callNode) {
183         return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
184     }
185 
186     @Override
187     public Node leaveCatchNode(final CatchNode catchNode) {
188         return addStatement(catchNode);
189     }
190 
191     @Override
192     public boolean enterContinueNode(final ContinueNode continueNode) {
193         addStatement(continueNode);
194         return false;
195     }
196 
197     @Override
198     public boolean enterEmptyNode(final EmptyNode emptyNode) {
199         return false;
200     }
201 
202     @Override
203     public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
204         final Expression expr = expressionStatement.getExpression();
205         ExpressionStatement node = expressionStatement;
206 
207         final FunctionNode currentFunction = lc.getCurrentFunction();
208 
209         if (currentFunction.isProgram()) {
210             if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
211                 node = expressionStatement.setExpression(
212                     new BinaryNode(
213                         Token.recast(
214                             expressionStatement.getToken(),
215                             TokenType.ASSIGN),
216                         compilerConstant(RETURN),
217                     expr));
218             }
219         }
220 
221         return addStatement(node);
222     }
223 
224     @Override
225     public Node leaveBlockStatement(BlockStatement blockStatement) {
226         return addStatement(blockStatement);
227     }
228 
229     @Override
230     public Node leaveForNode(final ForNode forNode) {
231         ForNode newForNode = forNode;
232 
233         final Node  test = forNode.getTest();
234         if (!forNode.isForIn() && conservativeAlwaysTrue(test)) {
235             newForNode = forNode.setTest(lc, null);
236         }
237 
238         return addStatement(checkEscape(newForNode));
239     }
240 
241     @Override
242     public boolean enterFunctionNode(final FunctionNode functionNode) {
243         return !functionNode.isLazy();
244     }
245 
246     @Override
247     public Node leaveFunctionNode(final FunctionNode functionNode) {
248         LOG.info("END FunctionNode: ", functionNode.getName());
249         return functionNode.setState(lc, CompilationState.LOWERED);
250     }
251 
252     @Override
253     public Node leaveIfNode(final IfNode ifNode) {
254         return addStatement(ifNode);
255     }
256 
257     @Override
258     public Node leaveLabelNode(final LabelNode labelNode) {
259         return addStatement(labelNode);
260     }
261 
262     @Override
263     public Node leaveReturnNode(final ReturnNode returnNode) {
264         addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
265         return returnNode;
266     }
267 
268 
269     @Override
270     public Node leaveSwitchNode(final SwitchNode switchNode) {
271         return addStatement(switchNode);
272     }
273 
274     @Override
275     public Node leaveThrowNode(final ThrowNode throwNode) {
276         addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
277         return throwNode;
278     }
279 
280     private static Node ensureUniqueNamesIn(final Node node) {
281         return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
282             @Override
283             public Node leaveFunctionNode(final FunctionNode functionNode) {
284                 final String name = functionNode.getName();
285                 return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
286             }
287 
288             @Override
289             public Node leaveDefault(final Node labelledNode) {
290                 return labelledNode.ensureUniqueLabels(lc);
291             }
292         });
293     }
294 
295     private static List<Statement> copyFinally(final Block finallyBody) {
296         final List<Statement> newStatements = new ArrayList<>();
297         for (final Statement statement : finallyBody.getStatements()) {
298             newStatements.add((Statement)ensureUniqueNamesIn(statement));
299             if (statement.hasTerminalFlags()) {
300                 return newStatements;
301             }
302         }
303         return newStatements;
304     }
305 
306     private Block catchAllBlock(final TryNode tryNode) {
307         final int  lineNumber = tryNode.getLineNumber();
308         final long token      = tryNode.getToken();
309         final int  finish     = tryNode.getFinish();
310 
311         final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName("catch_all"));
312 
313         final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), ThrowNode.IS_SYNTHETIC_RETHROW));
314         assert catchBody.isTerminal(); //ends with throw, so terminal
315 
316         final CatchNode catchAllNode  = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, CatchNode.IS_SYNTHETIC_RETHROW);
317         final Block     catchAllBlock = new Block(token, finish, catchAllNode);
318 
319         //catchallblock -> catchallnode (catchnode) -> exception -> throw
320 
321         return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
322     }
323 
324     private IdentNode compilerConstant(final CompilerConstants cc) {
325         final FunctionNode functionNode = lc.getCurrentFunction();
326         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
327     }
328 
329     private static boolean isTerminal(final List<Statement> statements) {
330         return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
331     }
332 
333     /**
334      * Splice finally code into all endpoints of a trynode
335      * @param tryNode the try node
336      * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
337      * @param finallyBody the code in the original finally block
338      * @return new try node after splicing finally code (same if nop)
339      */
340     private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
341         assert tryNode.getFinallyBody() == null;
342 
343         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
344             final List<Node> insideTry = new ArrayList<>();
345 
346             @Override
347             public boolean enterDefault(final Node node) {
348                 insideTry.add(node);
349                 return true;
350             }
351 
352             @Override
353             public boolean enterFunctionNode(final FunctionNode functionNode) {
354                 // do not enter function nodes - finally code should not be inlined into them
355                 return false;
356             }
357 
358             @Override
359             public Node leaveThrowNode(final ThrowNode throwNode) {
360                 if (rethrows.contains(throwNode)) {
361                     final List<Statement> newStatements = copyFinally(finallyBody);
362                     if (!isTerminal(newStatements)) {
363                         newStatements.add(throwNode);
364                     }
365                     return BlockStatement.createReplacement(throwNode, newStatements);
366                 }
367                 return throwNode;
368             }
369 
370             @Override
371             public Node leaveBreakNode(final BreakNode breakNode) {
372                 return copy(breakNode, (Node)Lower.this.lc.getBreakable(breakNode.getLabel()));
373             }
374 
375             @Override
376             public Node leaveContinueNode(final ContinueNode continueNode) {
377                 return copy(continueNode, Lower.this.lc.getContinueTo(continueNode.getLabel()));
378             }
379 
380             @Override
381             public Node leaveReturnNode(final ReturnNode returnNode) {
382                 final Expression expr  = returnNode.getExpression();
383                 final List<Statement> newStatements = new ArrayList<>();
384 
385                 final Expression resultNode;
386                 if (expr != null) {
387                     //we need to evaluate the result of the return in case it is complex while
388                     //still in the try block, store it in a result value and return it afterwards
389                     resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
390                     newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
391                 } else {
392                     resultNode = null;
393                 }
394 
395                 newStatements.addAll(copyFinally(finallyBody));
396                 if (!isTerminal(newStatements)) {
397                     newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
398                 }
399 
400                 return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
401             }
402 
403             private Node copy(final Statement endpoint, final Node targetNode) {
404                 if (!insideTry.contains(targetNode)) {
405                     final List<Statement> newStatements = copyFinally(finallyBody);
406                     if (!isTerminal(newStatements)) {
407                         newStatements.add(endpoint);
408                     }
409                     return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
410                 }
411                 return endpoint;
412             }
413         });
414 
415         addStatement(newTryNode);
416         for (final Node statement : finallyBody.getStatements()) {
417             addStatement((Statement)statement);
418         }
419 
420         return newTryNode;
421     }
422 
423     @Override
424     public Node leaveTryNode(final TryNode tryNode) {
425         final Block finallyBody = tryNode.getFinallyBody();
426 
427         if (finallyBody == null) {
428             return addStatement(tryNode);
429         }
430 
431         /*
432          * create a new trynode
433          *    if we have catches:
434          *
435          *    try            try
436          *       x              try
437          *    catch               x
438          *       y              catch
439          *    finally z           y
440          *                   catchall
441          *                        rethrow
442          *
443          *   otheriwse
444          *
445          *   try              try
446          *      x               x
447          *   finally          catchall
448          *      y               rethrow
449          *
450          *
451          *   now splice in finally code wherever needed
452          *
453          */
454         TryNode newTryNode;
455 
456         final Block catchAll = catchAllBlock(tryNode);
457 
458         final List<ThrowNode> rethrows = new ArrayList<>();
459         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
460             @Override
461             public boolean enterThrowNode(final ThrowNode throwNode) {
462                 rethrows.add(throwNode);
463                 return true;
464             }
465         });
466         assert rethrows.size() == 1;
467 
468         if (tryNode.getCatchBlocks().isEmpty()) {
469             newTryNode = tryNode.setFinallyBody(null);
470         } else {
471             Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), tryNode.setFinallyBody(null));
472             newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
473         }
474 
475         newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
476 
477         /*
478          * Now that the transform is done, we have to go into the try and splice
479          * the finally block in front of any statement that is outside the try
480          */
481         return spliceFinally(newTryNode, rethrows, finallyBody);
482     }
483 
484     @Override
485     public Node leaveVarNode(final VarNode varNode) {
486         addStatement(varNode);
487         if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) {
488             new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
489         }
490         return varNode;
491     }
492 
493     @Override
494     public Node leaveWhileNode(final WhileNode whileNode) {
495         final Node test  = whileNode.getTest();
496         final Block body = whileNode.getBody();
497 
498         if (conservativeAlwaysTrue(test)) {
499             //turn it into a for node without a test.
500             final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), null, null, body, null, ForNode.IS_FOR).accept(this);
501             lc.replace(whileNode, forNode);
502             return forNode;
503         }
504 
505          return addStatement(checkEscape(whileNode));
506     }
507 
508     @Override
509     public Node leaveWithNode(final WithNode withNode) {
510         return addStatement(withNode);
511     }
512 
513     /**
514      * Given a function node that is a callee in a CallNode, replace it with
515      * the appropriate marker function. This is used by {@link CodeGenerator}
516      * for fast scope calls
517      *
518      * @param function function called by a CallNode
519      * @return transformed node to marker function or identity if not ident/access/indexnode
520      */
521     private static Expression markerFunction(final Expression function) {
522         if (function instanceof IdentNode) {
523             return ((IdentNode)function).setIsFunction();
524         } else if (function instanceof BaseNode) {
525             return ((BaseNode)function).setIsFunction();
526         }
527         return function;
528     }
529 
530     /**
531      * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval>
532      * @param node a node
533      * @return eval location
534      */
535     private String evalLocation(final IdentNode node) {
536         final Source source = lc.getCurrentFunction().getSource();
537         final int pos = node.position();
538         // Code installer is null when running with --compile-only, use 0 as id in that case
539         final long id = installer == null ? 0 : installer.getUniqueEvalId();
540         return new StringBuilder().
541             append(source.getName()).
542             append('#').
543             append(source.getLine(pos)).
544             append(':').
545             append(source.getColumn(pos)).
546             append("<eval>@").
547             append(id).
548             toString();
549     }
550 
551     /**
552      * Check whether a call node may be a call to eval. In that case we
553      * clone the args in order to create the following construct in
554      * {@link CodeGenerator}
555      *
556      * <pre>
557      * if (calledFuntion == buildInEval) {
558      *    eval(cloned arg);
559      * } else {
560      *    cloned arg;
561      * }
562      * </pre>
563      *
564      * @param callNode call node to check if it's an eval
565      */
566     private CallNode checkEval(final CallNode callNode) {
567         if (callNode.getFunction() instanceof IdentNode) {
568 
569             final List<Expression> args = callNode.getArgs();
570             final IdentNode callee = (IdentNode)callNode.getFunction();
571 
572             // 'eval' call with at least one argument
573             if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
574                 final FunctionNode currentFunction = lc.getCurrentFunction();
575                 return callNode.setEvalArgs(
576                     new CallNode.EvalArgs(
577                         (Expression)ensureUniqueNamesIn(args.get(0)).accept(this),
578                         compilerConstant(THIS),
579                         evalLocation(callee),
580                         currentFunction.isStrict()));
581             }
582         }
583 
584         return callNode;
585     }
586 
587     private static boolean conservativeAlwaysTrue(final Node node) {
588         return node == null || ((node instanceof LiteralNode) && Boolean.TRUE.equals(((LiteralNode<?>)node).getValue()));
589     }
590 
591     /**
592      * Helper that given a loop body makes sure that it is not terminal if it
593      * has a continue that leads to the loop header or to outer loops' loop
594      * headers. This means that, even if the body ends with a terminal
595      * statement, we cannot tag it as terminal
596      *
597      * @param loopBody the loop body to check
598      * @return true if control flow may escape the loop
599      */
600     private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
601         final List<Node> escapes = new ArrayList<>();
602 
603         loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
604             @Override
605             public Node leaveBreakNode(final BreakNode node) {
606                 escapes.add(node);
607                 return node;
608             }
609 
610             @Override
611             public Node leaveContinueNode(final ContinueNode node) {
612                 // all inner loops have been popped.
613                 if (lex.contains(lex.getContinueTo(node.getLabel()))) {
614                     escapes.add(node);
615                 }
616                 return node;
617             }
618         });
619 
620         return !escapes.isEmpty();
621     }
622 
623     private LoopNode checkEscape(final LoopNode loopNode) {
624         final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
625         if (escapes) {
626             return loopNode.
627                 setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
628                 setControlFlowEscapes(lc, escapes);
629         }
630         return loopNode;
631     }
632 
633 
634     private Node addStatement(final Statement statement) {
635         lc.appendStatement(statement);
636         return statement;
637     }
638 
639     /**
640      * An internal expression has a symbol that is tagged internal. Check if
641      * this is such a node
642      *
643      * @param expression expression to check for internal symbol
644      * @return true if internal, false otherwise
645      */
646     private static boolean isInternalExpression(final Expression expression) {
647         final Symbol symbol = expression.getSymbol();
648         return symbol != null && symbol.isInternal();
649     }
650 
651     /**
652      * Is this an assignment to the special variable that hosts scripting eval
653      * results, i.e. __return__?
654      *
655      * @param expression expression to check whether it is $evalresult = X
656      * @return true if an assignment to eval result, false otherwise
657      */
658     private static boolean isEvalResultAssignment(final Node expression) {
659         Node e = expression;
660         assert e.tokenType() != TokenType.DISCARD; //there are no discards this early anymore
661         if (e instanceof BinaryNode) {
662             final Node lhs = ((BinaryNode)e).lhs();
663             if (lhs instanceof IdentNode) {
664                 return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
665             }
666         }
667         return false;
668     }
669 
670 }