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