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.ARGUMENTS;
29 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR;
30 import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
31 import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX;
32 import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX;
33 import static jdk.nashorn.internal.codegen.CompilerConstants.LITERAL_PREFIX;
34 import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
35 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
36 import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX;
37 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
38 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
39 import static jdk.nashorn.internal.ir.Symbol.IS_ALWAYS_DEFINED;
40 import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT;
41 import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF;
42 import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
43 import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
44 import static jdk.nashorn.internal.ir.Symbol.IS_LET;
45 import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
46 import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
47 import static jdk.nashorn.internal.ir.Symbol.IS_THIS;
48 import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
49 import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
50
51 import java.util.ArrayDeque;
52 import java.util.ArrayList;
53 import java.util.Deque;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Set;
58 import jdk.nashorn.internal.codegen.types.Type;
59 import jdk.nashorn.internal.ir.AccessNode;
60 import jdk.nashorn.internal.ir.BinaryNode;
61 import jdk.nashorn.internal.ir.Block;
62 import jdk.nashorn.internal.ir.CallNode;
63 import jdk.nashorn.internal.ir.CaseNode;
64 import jdk.nashorn.internal.ir.CatchNode;
65 import jdk.nashorn.internal.ir.Expression;
66 import jdk.nashorn.internal.ir.ForNode;
67 import jdk.nashorn.internal.ir.FunctionNode;
68 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
69 import jdk.nashorn.internal.ir.IdentNode;
70 import jdk.nashorn.internal.ir.IndexNode;
71 import jdk.nashorn.internal.ir.LexicalContext;
72 import jdk.nashorn.internal.ir.LexicalContextNode;
73 import jdk.nashorn.internal.ir.LiteralNode;
74 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
75 import jdk.nashorn.internal.ir.Node;
76 import jdk.nashorn.internal.ir.ObjectNode;
77 import jdk.nashorn.internal.ir.ReturnNode;
78 import jdk.nashorn.internal.ir.RuntimeNode;
79 import jdk.nashorn.internal.ir.RuntimeNode.Request;
80 import jdk.nashorn.internal.ir.Statement;
81 import jdk.nashorn.internal.ir.SwitchNode;
82 import jdk.nashorn.internal.ir.Symbol;
83 import jdk.nashorn.internal.ir.TemporarySymbols;
84 import jdk.nashorn.internal.ir.TernaryNode;
85 import jdk.nashorn.internal.ir.TryNode;
86 import jdk.nashorn.internal.ir.UnaryNode;
87 import jdk.nashorn.internal.ir.VarNode;
88 import jdk.nashorn.internal.ir.WithNode;
89 import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
90 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
91 import jdk.nashorn.internal.parser.TokenType;
92 import jdk.nashorn.internal.runtime.Context;
93 import jdk.nashorn.internal.runtime.Debug;
94 import jdk.nashorn.internal.runtime.DebugLogger;
95 import jdk.nashorn.internal.runtime.JSType;
96 import jdk.nashorn.internal.runtime.Property;
97 import jdk.nashorn.internal.runtime.PropertyMap;
98
99 /**
100 * This is the attribution pass of the code generator. Attr takes Lowered IR,
101 * that is, IR where control flow has been computed and high level to low level
102 * substitions for operations have been performed.
103 *
104 * After Attr, every symbol will have a conservative correct type.
105 *
106 * Any expression that requires temporary storage as part of computation will
107 * also be detected here and give a temporary symbol
108 *
109 * Types can be narrowed after Attr by Access Specialization in FinalizeTypes,
110 * but in general, this is where the main symbol type information is
111 * computed.
112 */
113
114 final class Attr extends NodeOperatorVisitor<LexicalContext> {
115
116 /**
117 * Local definitions in current block (to discriminate from function
118 * declarations always defined in the function scope. This is for
119 * "can be undefined" analysis.
120 */
121 private final Deque<Set<String>> localDefs;
122
123 /**
124 * Local definitions in current block to guard against cases like
125 * NASHORN-467 when things can be undefined as they are used before
126 * their local var definition. *sigh* JavaScript...
127 */
128 private final Deque<Set<String>> localUses;
129
130 private final Deque<Type> returnTypes;
131
132 private int catchNestingLevel;
133
134 private static final DebugLogger LOG = new DebugLogger("attr");
135 private static final boolean DEBUG = LOG.isEnabled();
136
137 private final TemporarySymbols temporarySymbols;
138
139 /**
140 * Constructor.
141 */
142 Attr(final TemporarySymbols temporarySymbols) {
143 super(new LexicalContext());
144 this.temporarySymbols = temporarySymbols;
145 this.localDefs = new ArrayDeque<>();
146 this.localUses = new ArrayDeque<>();
147 this.returnTypes = new ArrayDeque<>();
148 }
149
150 @Override
151 protected boolean enterDefault(final Node node) {
152 return start(node);
153 }
154
155 @Override
156 protected Node leaveDefault(final Node node) {
157 return end(node);
158 }
159
160 @Override
161 public Node leaveAccessNode(final AccessNode accessNode) {
162 //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this, that
163 //is why we can't set the access node base to be an object here, that will ruin access specialization
164 //for example for a.x | 17.
165 return end(ensureSymbol(Type.OBJECT, accessNode));
166 }
167
168 private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) {
169 initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL);
170 initCompileConstant(THIS, body, IS_PARAM | IS_THIS, Type.OBJECT);
171
172 if (functionNode.isVarArg()) {
173 initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL);
174 if (functionNode.needsArguments()) {
175 initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED);
176 final String argumentsName = ARGUMENTS_VAR.symbolName();
177 newType(defineSymbol(body, argumentsName, IS_VAR | IS_ALWAYS_DEFINED), Type.typeFor(ARGUMENTS_VAR.type()));
178 addLocalDef(argumentsName);
179 }
180 }
181
182 initParameters(functionNode, body);
183 initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED);
184 initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED, Type.OBJECT);
185 }
186
187
188 /**
189 * This pushes all declarations (except for non-statements, i.e. for
190 * node temporaries) to the top of the function scope. This way we can
191 * get around problems like
192 *
193 * while (true) {
194 * break;
195 * if (true) {
196 * var s;
197 * }
198 * }
199 *
200 * to an arbitrary nesting depth.
201 *
202 * see NASHORN-73
203 *
204 * @param functionNode the FunctionNode we are entering
205 * @param body the body of the FunctionNode we are entering
206 */
207 private void acceptDeclarations(final FunctionNode functionNode, final Block body) {
208 // This visitor will assign symbol to all declared variables, except function declarations (which are taken care
209 // in a separate step above) and "var" declarations in for loop initializers.
210 //
211 // It also handles the case that a variable can be undefined, e.g
212 // if (cond) {
213 // x = x.y;
214 // }
215 // var x = 17;
216 //
217 // by making sure that no identifier has been found earlier in the body than the
218 // declaration - if such is the case the identifier is flagged as caBeUndefined to
219 // be safe if it turns into a local var. Otherwise corrupt bytecode results
220
221 body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
222 private final Set<String> uses = new HashSet<>();
223 private final Set<String> canBeUndefined = new HashSet<>();
224
225 @Override
226 public boolean enterFunctionNode(final FunctionNode nestedFn) {
227 return false;
228 }
229
230 @Override
231 public Node leaveIdentNode(final IdentNode identNode) {
232 uses.add(identNode.getName());
233 return identNode;
234 }
235
236 @Override
237 public boolean enterVarNode(final VarNode varNode) {
238 final String name = varNode.getName().getName();
239 //if this is used before the var node, the var node symbol needs to be tagged as can be undefined
240 if (uses.contains(name)) {
241 canBeUndefined.add(name);
242 }
243
244 // all uses of the declared varnode inside the var node are potentially undefined
245 // however this is a bit conservative as e.g. var x = 17; var x = 1 + x; does work
246 if (!varNode.isFunctionDeclaration() && varNode.getInit() != null) {
247 varNode.getInit().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
248 @Override
249 public boolean enterIdentNode(final IdentNode identNode) {
250 if (name.equals(identNode.getName())) {
251 canBeUndefined.add(name);
252 }
253 return false;
254 }
255 });
256 }
257
258 return true;
259 }
260
261 @Override
262 public Node leaveVarNode(final VarNode varNode) {
263 // any declared symbols that aren't visited need to be typed as well, hence the list
264 if (varNode.isStatement()) {
265 final IdentNode ident = varNode.getName();
266 final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR);
267 if (canBeUndefined.contains(ident.getName())) {
268 symbol.setType(Type.OBJECT);
269 symbol.setCanBeUndefined();
270 }
271 functionNode.addDeclaredSymbol(symbol);
272 if (varNode.isFunctionDeclaration()) {
273 newType(symbol, FunctionNode.FUNCTION_TYPE);
274 symbol.setIsFunctionDeclaration();
275 }
276 return varNode.setName((IdentNode)ident.setSymbol(lc, symbol));
277 }
278
279 return varNode;
280 }
281 });
282 }
283
284 private void enterFunctionBody() {
285
286 final FunctionNode functionNode = lc.getCurrentFunction();
287 final Block body = lc.getCurrentBlock();
288
289 initFunctionWideVariables(functionNode, body);
290
291 if (functionNode.isProgram()) {
292 initFromPropertyMap(body);
293 } else if (!functionNode.isDeclared()) {
294 // It's neither declared nor program - it's a function expression then; assign it a self-symbol.
295 assert functionNode.getSymbol() == null;
296
297 final boolean anonymous = functionNode.isAnonymous();
298 final String name = anonymous ? null : functionNode.getIdent().getName();
299 if (!(anonymous || body.getExistingSymbol(name) != null)) {
300 assert !anonymous && name != null;
301 newType(defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF), Type.OBJECT);
302 }
303 }
304
305 acceptDeclarations(functionNode, body);
306 }
307
308 @Override
309 public boolean enterBlock(final Block block) {
310 start(block);
311 //ensure that we don't use information from a previous compile. This is very ugly TODO
312 //the symbols in the block should really be stateless
313 block.clearSymbols();
314
315 if (lc.isFunctionBody()) {
316 enterFunctionBody();
317 }
318 pushLocalsBlock();
319
320 return true;
321 }
322
323 @Override
324 public Node leaveBlock(final Block block) {
325 popLocals();
326 return end(block);
327 }
328
329 @Override
330 public boolean enterCallNode(final CallNode callNode) {
331 return start(callNode);
332 }
333
334 @Override
335 public Node leaveCallNode(final CallNode callNode) {
336 return end(ensureSymbol(callNode.getType(), callNode));
337 }
338
339 @Override
340 public boolean enterCatchNode(final CatchNode catchNode) {
341 final IdentNode exception = catchNode.getException();
342 final Block block = lc.getCurrentBlock();
343
344 start(catchNode);
345 catchNestingLevel++;
346
347 // define block-local exception variable
348 final String exname = exception.getName();
349 final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED);
350 newType(def, Type.OBJECT); //we can catch anything, not just ecma exceptions
351
352 addLocalDef(exname);
353
354 return true;
355 }
356
357 @Override
358 public Node leaveCatchNode(final CatchNode catchNode) {
359 final IdentNode exception = catchNode.getException();
360 final Block block = lc.getCurrentBlock();
361 final Symbol symbol = findSymbol(block, exception.getName());
362
363 catchNestingLevel--;
364
365 assert symbol != null;
366 return end(catchNode.setException((IdentNode)exception.setSymbol(lc, symbol)));
367 }
368
369 /**
370 * Declare the definition of a new symbol.
371 *
372 * @param name Name of symbol.
373 * @param symbolFlags Symbol flags.
374 *
375 * @return Symbol for given name or null for redefinition.
376 */
377 private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) {
378 int flags = symbolFlags;
379 Symbol symbol = findSymbol(block, name); // Locate symbol.
380
381 if ((flags & KINDMASK) == IS_GLOBAL) {
382 flags |= IS_SCOPE;
383 }
384
385 final FunctionNode function = lc.getFunction(block);
386 if (symbol != null) {
387 // Symbol was already defined. Check if it needs to be redefined.
388 if ((flags & KINDMASK) == IS_PARAM) {
389 if (!isLocal(function, symbol)) {
390 // Not defined in this function. Create a new definition.
391 symbol = null;
392 } else if (symbol.isParam()) {
393 // Duplicate parameter. Null return will force an error.
394 assert false : "duplicate parameter";
395 return null;
396 }
397 } else if ((flags & KINDMASK) == IS_VAR) {
398 if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) {
399 // Always create a new definition.
400 symbol = null;
401 } else {
402 // Not defined in this function. Create a new definition.
403 if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
404 symbol = null;
405 }
406 }
407 }
408 }
409
410 if (symbol == null) {
411 // If not found, then create a new one.
412 Block symbolBlock;
413
414 // Determine where to create it.
415 if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) {
416 symbolBlock = block; //internal vars are always defined in the block closest to them
417 } else {
418 symbolBlock = lc.getFunctionBody(function);
419 }
420
421 // Create and add to appropriate block.
422 symbol = new Symbol(name, flags);
423 symbolBlock.putSymbol(lc, symbol);
424
425 if ((flags & Symbol.KINDMASK) != IS_GLOBAL) {
426 symbol.setNeedsSlot(true);
427 }
428 } else if (symbol.less(flags)) {
429 symbol.setFlags(flags);
430 }
431
432 return symbol;
433 }
434
435 @Override
436 public boolean enterFunctionNode(final FunctionNode functionNode) {
437 start(functionNode, false);
438
439 if (functionNode.isLazy()) {
440 return false;
441 }
442
443 //an outermost function in our lexical context that is not a program (runScript)
444 //is possible - it is a function being compiled lazily
445 if (functionNode.isDeclared()) {
446 final Iterator<Block> blocks = lc.getBlocks();
447 if (blocks.hasNext()) {
448 defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR);
449 }
450 }
451
452 returnTypes.push(functionNode.getReturnType());
453 pushLocalsFunction();
454
455 return true;
456 }
457
458 @Override
459 public Node leaveFunctionNode(final FunctionNode functionNode) {
460 FunctionNode newFunctionNode = functionNode;
461
462 final Block body = newFunctionNode.getBody();
463
464 //look for this function in the parent block
465 if (functionNode.isDeclared()) {
466 final Iterator<Block> blocks = lc.getBlocks();
467 if (blocks.hasNext()) {
468 newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, findSymbol(blocks.next(), functionNode.getIdent().getName()));
469 }
470 } else if (!functionNode.isProgram()) {
471 final boolean anonymous = functionNode.isAnonymous();
472 final String name = anonymous ? null : functionNode.getIdent().getName();
473 if (anonymous || body.getExistingSymbol(name) != null) {
474 newFunctionNode = (FunctionNode)ensureSymbol(FunctionNode.FUNCTION_TYPE, newFunctionNode);
475 } else {
476 assert name != null;
477 final Symbol self = body.getExistingSymbol(name);
478 assert self != null && self.isFunctionSelf();
479 newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, body.getExistingSymbol(name));
480 }
481 }
482
483 //unknown parameters are promoted to object type.
484 if (newFunctionNode.hasLazyChildren()) {
485 //the final body has already been assigned as we have left the function node block body by now
486 objectifySymbols(body);
487 }
488 newFunctionNode = finalizeParameters(newFunctionNode);
489 newFunctionNode = finalizeTypes(newFunctionNode);
490 for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) {
491 if (symbol.getSymbolType().isUnknown()) {
492 symbol.setType(Type.OBJECT);
493 symbol.setCanBeUndefined();
494 }
495 }
496
497 List<VarNode> syntheticInitializers = null;
498
499 if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) {
500 syntheticInitializers = new ArrayList<>(2);
501 LOG.info("Accepting self symbol init for ", newFunctionNode.getName());
502 // "var fn = :callee"
503 syntheticInitializers.add(createSyntheticInitializer(newFunctionNode.getIdent(), CALLEE, newFunctionNode));
504 }
505
506 if (newFunctionNode.needsArguments()) {
507 if (syntheticInitializers == null) {
508 syntheticInitializers = new ArrayList<>(1);
509 }
510 // "var arguments = :arguments"
511 syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()),
512 ARGUMENTS, newFunctionNode));
513 }
514
515 if (syntheticInitializers != null) {
516 final List<Statement> stmts = newFunctionNode.getBody().getStatements();
517 final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size());
518 newStatements.addAll(syntheticInitializers);
519 newStatements.addAll(stmts);
520 newFunctionNode = newFunctionNode.setBody(lc, newFunctionNode.getBody().setStatements(lc, newStatements));
521 }
522
523 if (returnTypes.peek().isUnknown()) {
524 LOG.info("Unknown return type promoted to object");
525 newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT);
526 }
527 final Type returnType = returnTypes.pop();
528 newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType);
529 newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR);
530
531 popLocals();
532
533 end(newFunctionNode, false);
534
535 return newFunctionNode;
536 }
537
538 /**
539 * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically
540 * used to create assignmnent of {@code :callee} to the function name symbol in self-referential function
541 * expressions as well as for assignment of {@code :arguments} to {@code arguments}.
542 *
543 * @param name the ident node identifying the variable to initialize
544 * @param initConstant the compiler constant it is initialized to
545 * @param fn the function node the assignment is for
546 * @return a var node with the appropriate assignment
547 */
548 private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) {
549 final IdentNode init = compilerConstant(initConstant);
550 assert init.getSymbol() != null && init.getSymbol().hasSlot();
551
552 VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init);
553
554 final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName());
555 assert nameSymbol != null;
556
557 return synthVar.setName((IdentNode)name.setSymbol(lc, nameSymbol));
558 }
559
560 @Override
561 public Node leaveIdentNode(final IdentNode identNode) {
562 final String name = identNode.getName();
563
564 if (identNode.isPropertyName()) {
565 // assign a pseudo symbol to property name
566 final Symbol pseudoSymbol = pseudoSymbol(name);
567 LOG.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol);
568 LOG.unindent();
569 return end(identNode.setSymbol(lc, pseudoSymbol));
570 }
571
572 final Block block = lc.getCurrentBlock();
573
574 Symbol symbol = findSymbol(block, name);
575
576 //If an existing symbol with the name is found, use that otherwise, declare a new one
577 if (symbol != null) {
578 LOG.info("Existing symbol = ", symbol);
579 if (symbol.isFunctionSelf()) {
580 final FunctionNode functionNode = lc.getDefiningFunction(symbol);
581 assert functionNode != null;
582 assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null;
583 lc.setFlag(functionNode.getBody(), Block.NEEDS_SELF_SYMBOL);
584 newType(symbol, FunctionNode.FUNCTION_TYPE);
585 } else if (!identNode.isInitializedHere()) {
586 /*
587 * See NASHORN-448, JDK-8016235
588 *
589 * Here is a use outside the local def scope
590 * the inCatch check is a conservative approach to handle things that might have only been
591 * defined in the try block, but with variable declarations, which due to JavaScript rules
592 * have to be lifted up into the function scope outside the try block anyway, but as the
593 * flow can fault at almost any place in the try block and get us to the catch block, all we
594 * know is that we have a declaration, not a definition. This can be made better and less
595 * conservative once we superimpose a CFG onto the AST.
596 */
597 if (!isLocalDef(name) || inCatch()) {
598 newType(symbol, Type.OBJECT);
599 symbol.setCanBeUndefined();
600 }
601 }
602
603 // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already)
604 maybeForceScope(symbol);
605 } else {
606 LOG.info("No symbol exists. Declare undefined: ", symbol);
607 symbol = defineSymbol(block, name, IS_GLOBAL);
608 // we have never seen this before, it can be undefined
609 newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway?
610 symbol.setCanBeUndefined();
611 Symbol.setSymbolIsScope(lc, symbol);
612 }
613
614 setBlockScope(name, symbol);
615
616 if (!identNode.isInitializedHere()) {
617 symbol.increaseUseCount();
618 }
619 addLocalUse(identNode.getName());
620
621 return end(identNode.setSymbol(lc, symbol));
622 }
623
624 private boolean inCatch() {
625 return catchNestingLevel > 0;
626 }
627
628 /**
629 * If the symbol isn't already a scope symbol, and it is either not local to the current function, or it is being
630 * referenced from within a with block, we force it to be a scope symbol.
631 * @param symbol the symbol that might be scoped
632 */
633 private void maybeForceScope(final Symbol symbol) {
634 if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) {
635 Symbol.setSymbolIsScope(lc, symbol);
636 }
637 }
638
639 private boolean symbolNeedsToBeScope(Symbol symbol) {
640 if (symbol.isThis() || symbol.isInternal()) {
641 return false;
642 }
643 boolean previousWasBlock = false;
644 for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
645 final LexicalContextNode node = it.next();
646 if (node instanceof FunctionNode) {
647 // We reached the function boundary without seeing a definition for the symbol - it needs to be in
648 // scope.
649 return true;
650 } else if (node instanceof WithNode) {
651 if (previousWasBlock) {
652 // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately
653 // preceded by a block, this means we're currently processing its expression, not its body,
654 // therefore it doesn't count.
655 return true;
656 }
657 previousWasBlock = false;
658 } else if (node instanceof Block) {
659 if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) {
660 // We reached the block that defines the symbol without reaching either the function boundary, or a
661 // WithNode. The symbol need not be scoped.
662 return false;
663 }
664 previousWasBlock = true;
665 } else {
666 previousWasBlock = false;
667 }
668 }
669 throw new AssertionError();
670 }
671
672 private void setBlockScope(final String name, final Symbol symbol) {
673 assert symbol != null;
674 if (symbol.isGlobal()) {
675 setUsesGlobalSymbol();
676 return;
677 }
678
679 if (symbol.isScope()) {
680 Block scopeBlock = null;
681 for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) {
682 final LexicalContextNode node = contextNodeIter.next();
683 if (node instanceof Block) {
684 if (((Block)node).getExistingSymbol(name) != null) {
685 scopeBlock = (Block)node;
686 break;
687 }
688 } else if (node instanceof FunctionNode) {
689 lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
690 }
691 }
692
693 if (scopeBlock != null) {
694 assert lc.contains(scopeBlock);
695 lc.setBlockNeedsScope(scopeBlock);
696 }
697 }
698 }
699
700 /**
701 * Marks the current function as one using any global symbol. The function and all its parent functions will all be
702 * marked as needing parent scope.
703 * @see #needsParentScope()
704 */
705 private void setUsesGlobalSymbol() {
706 for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) {
707 lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
708 }
709 }
710
711 /**
712 * Search for symbol in the lexical context starting from the given block.
713 * @param name Symbol name.
714 * @return Found symbol or null if not found.
715 */
716 private Symbol findSymbol(final Block block, final String name) {
717 // Search up block chain to locate symbol.
718
719 for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) {
720 // Find name.
721 final Symbol symbol = blocks.next().getExistingSymbol(name);
722 // If found then we are good.
723 if (symbol != null) {
724 return symbol;
725 }
726 }
727 return null;
728 }
729
730 @Override
731 public Node leaveIndexNode(final IndexNode indexNode) {
732 return end(ensureSymbol(Type.OBJECT, indexNode));
733 }
734
735 @SuppressWarnings("rawtypes")
736 @Override
737 public Node leaveLiteralNode(final LiteralNode literalNode) {
738 assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens
739 assert literalNode instanceof ArrayLiteralNode || !(literalNode.getValue() instanceof Node) : "literals with Node values not supported";
740 final Symbol symbol = new Symbol(lc.getCurrentFunction().uniqueName(LITERAL_PREFIX.symbolName()), IS_CONSTANT, literalNode.getType());
741 if (literalNode instanceof ArrayLiteralNode) {
742 ((ArrayLiteralNode)literalNode).analyze();
743 }
744 return end(literalNode.setSymbol(lc, symbol));
745 }
746
747 @Override
748 public boolean enterObjectNode(final ObjectNode objectNode) {
749 return start(objectNode);
750 }
751
752 @Override
753 public Node leaveObjectNode(final ObjectNode objectNode) {
754 return end(ensureSymbol(Type.OBJECT, objectNode));
755 }
756
757 @Override
758 public Node leaveReturnNode(final ReturnNode returnNode) {
759 final Expression expr = returnNode.getExpression();
760 final Type returnType;
761
762 if (expr != null) {
763 //we can't do parameter specialization if we return something that hasn't been typed yet
764 final Symbol symbol = expr.getSymbol();
765 if (expr.getType().isUnknown() && symbol.isParam()) {
766 symbol.setType(Type.OBJECT);
767 }
768
769 returnType = widestReturnType(returnTypes.pop(), symbol.getSymbolType());
770 } else {
771 returnType = Type.OBJECT; //undefined
772 }
773 LOG.info("Returntype is now ", returnType);
774 returnTypes.push(returnType);
775
776 end(returnNode);
777
778 return returnNode;
779 }
780
781 @Override
782 public Node leaveSwitchNode(final SwitchNode switchNode) {
783 Type type = Type.UNKNOWN;
784
785 final List<CaseNode> newCases = new ArrayList<>();
786 for (final CaseNode caseNode : switchNode.getCases()) {
787 final Node test = caseNode.getTest();
788
789 CaseNode newCaseNode = caseNode;
790 if (test != null) {
791 if (test instanceof LiteralNode) {
792 //go down to integers if we can
793 final LiteralNode<?> lit = (LiteralNode<?>)test;
794 if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
795 if (JSType.isRepresentableAsInt(lit.getNumber())) {
796 newCaseNode = caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
797 }
798 }
799 } else {
800 // the "all integer" case that CodeGenerator optimizes for currently assumes literals only
801 type = Type.OBJECT;
802 }
803
804 final Type newCaseType = newCaseNode.getTest().getType();
805 if (newCaseType.isBoolean()) {
806 type = Type.OBJECT; //booleans and integers aren't assignment compatible
807 } else {
808 type = Type.widest(type, newCaseType);
809 }
810 }
811
812 newCases.add(newCaseNode);
813 }
814
815 //only optimize for all integers
816 if (!type.isInteger()) {
817 type = Type.OBJECT;
818 }
819
820 switchNode.setTag(newInternal(lc.getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type));
821
822 end(switchNode);
823
824 return switchNode.setCases(lc, newCases);
825 }
826
827 @Override
828 public Node leaveTryNode(final TryNode tryNode) {
829 tryNode.setException(exceptionSymbol());
830
831 if (tryNode.getFinallyBody() != null) {
832 tryNode.setFinallyCatchAll(exceptionSymbol());
833 }
834
835 end(tryNode);
836
837 return tryNode;
838 }
839
840 @Override
841 public boolean enterVarNode(final VarNode varNode) {
842 start(varNode);
843
844 final IdentNode ident = varNode.getName();
845 final String name = ident.getName();
846
847 final Symbol symbol = defineSymbol(lc.getCurrentBlock(), name, IS_VAR);
848 assert symbol != null;
849
850 // NASHORN-467 - use before definition of vars - conservative
851 if (isLocalUse(ident.getName())) {
852 newType(symbol, Type.OBJECT);
853 symbol.setCanBeUndefined();
854 }
855
856 return true;
857 }
858
859 @Override
860 public Node leaveVarNode(final VarNode varNode) {
861 final Expression init = varNode.getInit();
862 final IdentNode ident = varNode.getName();
863 final String name = ident.getName();
864
865 final Symbol symbol = findSymbol(lc.getCurrentBlock(), name);
866 assert ident.getSymbol() == symbol;
867
868 if (init == null) {
869 // var x; with no init will be treated like a use of x by
870 // leaveIdentNode unless we remove the name from the localdef list.
871 removeLocalDef(name);
872 return end(varNode);
873 }
874
875 addLocalDef(name);
876
877 assert symbol != null;
878
879 final IdentNode newIdent = (IdentNode)ident.setSymbol(lc, symbol);
880
881 final VarNode newVarNode = varNode.setName(newIdent);
882
883 final boolean isScript = lc.getDefiningFunction(symbol).isProgram(); //see NASHORN-56
884 if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) {
885 // Forbid integers as local vars for now as we have no way to treat them as undefined
886 newType(symbol, init.getType());
887 } else {
888 newType(symbol, Type.OBJECT);
889 }
890
891 assert newVarNode.getName().hasType() : newVarNode + " has no type";
892
893 return end(newVarNode);
894 }
895
896 @Override
897 public Node leaveADD(final UnaryNode unaryNode) {
898 return end(ensureSymbol(arithType(), unaryNode));
899 }
900
901 @Override
902 public Node leaveBIT_NOT(final UnaryNode unaryNode) {
903 return end(ensureSymbol(Type.INT, unaryNode));
904 }
905
906 @Override
907 public Node leaveDECINC(final UnaryNode unaryNode) {
908 // @see assignOffset
909 final Type type = arithType();
910 newType(unaryNode.rhs().getSymbol(), type);
911 return end(ensureSymbol(type, unaryNode));
912 }
913
914 @Override
915 public Node leaveDELETE(final UnaryNode unaryNode) {
916 final FunctionNode currentFunctionNode = lc.getCurrentFunction();
917 final boolean strictMode = currentFunctionNode.isStrict();
918 final Expression rhs = unaryNode.rhs();
919 final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this);
920
921 Request request = Request.DELETE;
922 final List<Expression> args = new ArrayList<>();
923
924 if (rhs instanceof IdentNode) {
925 // If this is a declared variable or a function parameter, delete always fails (except for globals).
926 final String name = ((IdentNode)rhs).getName();
927
928 final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name));
929
930 if (failDelete && rhs.getSymbol().isThis()) {
931 return LiteralNode.newInstance(unaryNode, true).accept(this);
932 }
933 final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
934
935 if (!failDelete) {
936 args.add(compilerConstant(SCOPE));
937 }
938 args.add(literalNode);
939 args.add(strictFlagNode);
940
941 if (failDelete) {
942 request = Request.FAIL_DELETE;
943 }
944 } else if (rhs instanceof AccessNode) {
945 final Expression base = ((AccessNode)rhs).getBase();
946 final IdentNode property = ((AccessNode)rhs).getProperty();
947
948 args.add(base);
949 args.add((Expression)LiteralNode.newInstance(unaryNode, property.getName()).accept(this));
950 args.add(strictFlagNode);
951
952 } else if (rhs instanceof IndexNode) {
953 final IndexNode indexNode = (IndexNode)rhs;
954 final Expression base = indexNode.getBase();
955 final Expression index = indexNode.getIndex();
956
957 args.add(base);
958 args.add(index);
959 args.add(strictFlagNode);
960
961 } else {
962 return LiteralNode.newInstance(unaryNode, true).accept(this);
963 }
964
965 final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args);
966 assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this
967
968 return leaveRuntimeNode(runtimeNode);
969 }
970
971 /**
972 * Is the symbol denoted by the specified name in the current lexical context defined in the program level
973 * @param name the name of the symbol
974 * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level.
975 */
976 private boolean isProgramLevelSymbol(final String name) {
977 for(final Iterator<Block> it = lc.getBlocks(); it.hasNext();) {
978 final Block next = it.next();
979 if(next.getExistingSymbol(name) != null) {
980 return next == lc.getFunctionBody(lc.getOutermostFunction());
981 }
982 }
983 throw new AssertionError("Couldn't find symbol " + name + " in the context");
984 }
985
986 @Override
987 public Node leaveNEW(final UnaryNode unaryNode) {
988 return end(ensureSymbol(Type.OBJECT, unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew())));
989 }
990
991 @Override
992 public Node leaveNOT(final UnaryNode unaryNode) {
993 return end(ensureSymbol(Type.BOOLEAN, unaryNode));
994 }
995
996 private IdentNode compilerConstant(CompilerConstants cc) {
997 return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc, lc.getCurrentFunction().compilerConstant(cc));
998 }
999
1000 /**
1001 * Creates an ident node for an implicit identifier within the function (one not declared in the script source
1002 * code). These identifiers are defined with function's token and finish.
1003 * @param name the name of the identifier
1004 * @return an ident node representing the implicit identifier.
1005 */
1006 private IdentNode createImplicitIdentifier(final String name) {
1007 final FunctionNode fn = lc.getCurrentFunction();
1008 return new IdentNode(fn.getToken(), fn.getFinish(), name);
1009 }
1010
1011 @Override
1012 public Node leaveTYPEOF(final UnaryNode unaryNode) {
1013 final Expression rhs = unaryNode.rhs();
1014
1015 List<Expression> args = new ArrayList<>();
1016 if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) {
1017 args.add(compilerConstant(SCOPE));
1018 args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
1019 } else {
1020 args.add(rhs);
1021 args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
1022 }
1023
1024 RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
1025 assert runtimeNode.getSymbol() == unaryNode.getSymbol();
1026
1027 runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode);
1028
1029 end(unaryNode);
1030
1031 return runtimeNode;
1032 }
1033
1034 @Override
1035 public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
1036 return end(ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode));
1037 }
1038
1039 @Override
1040 public Node leaveSUB(final UnaryNode unaryNode) {
1041 return end(ensureSymbol(arithType(), unaryNode));
1042 }
1043
1044 @Override
1045 public Node leaveVOID(final UnaryNode unaryNode) {
1046 return end(ensureSymbol(Type.OBJECT, unaryNode));
1047 }
1048
1049 /**
1050 * Add is a special binary, as it works not only on arithmetic, but for
1051 * strings etc as well.
1052 */
1053 @Override
1054 public Node leaveADD(final BinaryNode binaryNode) {
1055 final Expression lhs = binaryNode.lhs();
1056 final Expression rhs = binaryNode.rhs();
1057
1058 ensureTypeNotUnknown(lhs);
1059 ensureTypeNotUnknown(rhs);
1060 //even if we are adding two known types, this can overflow. i.e.
1061 //int and number -> number.
1062 //int and int are also number though.
1063 //something and object is object
1064 return end(ensureSymbol(Type.widest(arithType(), Type.widest(lhs.getType(), rhs.getType())), binaryNode));
1065 }
1066
1067 @Override
1068 public Node leaveAND(final BinaryNode binaryNode) {
1069 return end(ensureSymbol(Type.OBJECT, binaryNode));
1070 }
1071
1072 /**
1073 * This is a helper called before an assignment.
1074 * @param binaryNode assignment node
1075 */
1076 private boolean enterAssignmentNode(final BinaryNode binaryNode) {
1077 start(binaryNode);
1078
1079 return true;
1080 }
1081
1082
1083 /**
1084 * This assign helper is called after an assignment, when all children of
1085 * the assign has been processed. It fixes the types and recursively makes
1086 * sure that everyhing has slots that should have them in the chain.
1087 *
1088 * @param binaryNode assignment node
1089 */
1090 private Node leaveAssignmentNode(final BinaryNode binaryNode) {
1091 final Expression lhs = binaryNode.lhs();
1092 final Expression rhs = binaryNode.rhs();
1093 final Type type;
1094
1095 if (lhs instanceof IdentNode) {
1096 final Block block = lc.getCurrentBlock();
1097 final IdentNode ident = (IdentNode)lhs;
1098 final String name = ident.getName();
1099 final Symbol symbol = findSymbol(block, name);
1100
1101 if (symbol == null) {
1102 defineSymbol(block, name, IS_GLOBAL);
1103 } else {
1104 maybeForceScope(symbol);
1105 }
1106
1107 addLocalDef(name);
1108 }
1109
1110 if (rhs.getType().isNumeric()) {
1111 type = Type.widest(lhs.getType(), rhs.getType());
1112 } else {
1113 type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too.
1114 }
1115
1116 newType(lhs.getSymbol(), type);
1117 return end(ensureSymbol(type, binaryNode));
1118 }
1119
1120 private boolean isLocal(FunctionNode function, Symbol symbol) {
1121 final FunctionNode definingFn = lc.getDefiningFunction(symbol);
1122 // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local
1123 return definingFn == null || definingFn == function;
1124 }
1125
1126 @Override
1127 public boolean enterASSIGN(final BinaryNode binaryNode) {
1128 return enterAssignmentNode(binaryNode);
1129 }
1130
1131 @Override
1132 public Node leaveASSIGN(final BinaryNode binaryNode) {
1133 return leaveAssignmentNode(binaryNode);
1134 }
1135
1136 @Override
1137 public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
1138 return enterAssignmentNode(binaryNode);
1139 }
1140
1141 @Override
1142 public Node leaveASSIGN_ADD(final BinaryNode binaryNode) {
1143 final Expression lhs = binaryNode.lhs();
1144 final Expression rhs = binaryNode.rhs();
1145
1146 final Type widest = Type.widest(lhs.getType(), rhs.getType());
1147 //Type.NUMBER if we can't prove that the add doesn't overflow. todo
1148 return leaveSelfModifyingAssignmentNode(binaryNode, widest.isNumeric() ? Type.NUMBER : Type.OBJECT);
1149 }
1150
1151 @Override
1152 public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
1153 return enterAssignmentNode(binaryNode);
1154 }
1155
1156 @Override
1157 public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) {
1158 return leaveSelfModifyingAssignmentNode(binaryNode);
1159 }
1160
1161 @Override
1162 public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
1163 return enterAssignmentNode(binaryNode);
1164 }
1165
1166 @Override
1167 public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) {
1168 return leaveSelfModifyingAssignmentNode(binaryNode);
1169 }
1170
1171 @Override
1172 public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
1173 return enterAssignmentNode(binaryNode);
1174 }
1175
1176 @Override
1177 public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) {
1178 return leaveSelfModifyingAssignmentNode(binaryNode);
1179 }
1180
1181 @Override
1182 public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
1183 return enterAssignmentNode(binaryNode);
1184 }
1185
1186 @Override
1187 public Node leaveASSIGN_DIV(final BinaryNode binaryNode) {
1188 return leaveSelfModifyingAssignmentNode(binaryNode);
1189 }
1190
1191 @Override
1192 public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
1193 return enterAssignmentNode(binaryNode);
1194 }
1195
1196 @Override
1197 public Node leaveASSIGN_MOD(final BinaryNode binaryNode) {
1198 return leaveSelfModifyingAssignmentNode(binaryNode);
1199 }
1200
1201 @Override
1202 public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
1203 return enterAssignmentNode(binaryNode);
1204 }
1205
1206 @Override
1207 public Node leaveASSIGN_MUL(final BinaryNode binaryNode) {
1208 return leaveSelfModifyingAssignmentNode(binaryNode);
1209 }
1210
1211 @Override
1212 public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
1213 return enterAssignmentNode(binaryNode);
1214 }
1215
1216 @Override
1217 public Node leaveASSIGN_SAR(final BinaryNode binaryNode) {
1218 return leaveSelfModifyingAssignmentNode(binaryNode);
1219 }
1220
1221 @Override
1222 public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
1223 return enterAssignmentNode(binaryNode);
1224 }
1225
1226 @Override
1227 public Node leaveASSIGN_SHL(final BinaryNode binaryNode) {
1228 return leaveSelfModifyingAssignmentNode(binaryNode);
1229 }
1230
1231 @Override
1232 public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
1233 return enterAssignmentNode(binaryNode);
1234 }
1235
1236 @Override
1237 public Node leaveASSIGN_SHR(final BinaryNode binaryNode) {
1238 return leaveSelfModifyingAssignmentNode(binaryNode);
1239 }
1240
1241 @Override
1242 public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
1243 return enterAssignmentNode(binaryNode);
1244 }
1245
1246 @Override
1247 public Node leaveASSIGN_SUB(final BinaryNode binaryNode) {
1248 return leaveSelfModifyingAssignmentNode(binaryNode);
1249 }
1250
1251 @Override
1252 public Node leaveBIT_AND(final BinaryNode binaryNode) {
1253 return end(coerce(binaryNode, Type.INT));
1254 }
1255
1256 @Override
1257 public Node leaveBIT_OR(final BinaryNode binaryNode) {
1258 return end(coerce(binaryNode, Type.INT));
1259 }
1260
1261 @Override
1262 public Node leaveBIT_XOR(final BinaryNode binaryNode) {
1263 return end(coerce(binaryNode, Type.INT));
1264 }
1265
1266 @Override
1267 public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
1268 return leaveComma(binaryNode, binaryNode.rhs());
1269 }
1270
1271 @Override
1272 public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
1273 return leaveComma(binaryNode, binaryNode.lhs());
1274 }
1275
1276 private Node leaveComma(final BinaryNode commaNode, final Expression effectiveExpr) {
1277 ensureTypeNotUnknown(effectiveExpr);
1278 return end(ensureSymbol(effectiveExpr.getType(), commaNode));
1279 }
1280
1281 @Override
1282 public Node leaveDIV(final BinaryNode binaryNode) {
1283 return leaveBinaryArithmetic(binaryNode);
1284 }
1285
1286 private Node leaveCmp(final BinaryNode binaryNode) {
1287 ensureTypeNotUnknown(binaryNode.lhs());
1288 ensureTypeNotUnknown(binaryNode.rhs());
1289 Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
1290 ensureSymbol(widest, binaryNode.lhs());
1291 ensureSymbol(widest, binaryNode.rhs());
1292 return end(ensureSymbol(Type.BOOLEAN, binaryNode));
1293 }
1294
1295 private Node coerce(final BinaryNode binaryNode, final Type operandType, final Type destType) {
1296 // TODO we currently don't support changing inferred type based on uses, only on
1297 // definitions. we would need some additional logic. We probably want to do that
1298 // in the future, if e.g. a specialized method gets parameter that is only used
1299 // as, say, an int : function(x) { return x & 4711 }, and x is not defined in
1300 // the function. to make this work, uncomment the following two type inferences
1301 // and debug.
1302 //newType(binaryNode.lhs().getSymbol(), operandType);
1303 //newType(binaryNode.rhs().getSymbol(), operandType);
1304 return ensureSymbol(destType, binaryNode);
1305 }
1306
1307 private Node coerce(final BinaryNode binaryNode, final Type type) {
1308 return coerce(binaryNode, type, type);
1309 }
1310
1311 //leave a binary node and inherit the widest type of lhs , rhs
1312 private Node leaveBinaryArithmetic(final BinaryNode binaryNode) {
1313 assert !Compiler.shouldUseIntegerArithmetic();
1314 return end(coerce(binaryNode, Type.NUMBER));
1315 }
1316
1317 @Override
1318 public Node leaveEQ(final BinaryNode binaryNode) {
1319 return leaveCmp(binaryNode);
1320 }
1321
1322 @Override
1323 public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
1324 return leaveCmp(binaryNode);
1325 }
1326
1327 @Override
1328 public Node leaveGE(final BinaryNode binaryNode) {
1329 return leaveCmp(binaryNode);
1330 }
1331
1332 @Override
1333 public Node leaveGT(final BinaryNode binaryNode) {
1334 return leaveCmp(binaryNode);
1335 }
1336
1337 @Override
1338 public Node leaveIN(final BinaryNode binaryNode) {
1339 return leaveBinaryRuntimeOperator(binaryNode, Request.IN);
1340 }
1341
1342 @Override
1343 public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
1344 return leaveBinaryRuntimeOperator(binaryNode, Request.INSTANCEOF);
1345 }
1346
1347 private Node leaveBinaryRuntimeOperator(final BinaryNode binaryNode, final Request request) {
1348 try {
1349 // Don't do a full RuntimeNode.accept, as we don't want to double-visit the binary node operands
1350 return leaveRuntimeNode(new RuntimeNode(binaryNode, request));
1351 } finally {
1352 end(binaryNode);
1353 }
1354 }
1355
1356 @Override
1357 public Node leaveLE(final BinaryNode binaryNode) {
1358 return leaveCmp(binaryNode);
1359 }
1360
1361 @Override
1362 public Node leaveLT(final BinaryNode binaryNode) {
1363 return leaveCmp(binaryNode);
1364 }
1365
1366 @Override
1367 public Node leaveMOD(final BinaryNode binaryNode) {
1368 return leaveBinaryArithmetic(binaryNode);
1369 }
1370
1371 @Override
1372 public Node leaveMUL(final BinaryNode binaryNode) {
1373 return leaveBinaryArithmetic(binaryNode);
1374 }
1375
1376 @Override
1377 public Node leaveNE(final BinaryNode binaryNode) {
1378 return leaveCmp(binaryNode);
1379 }
1380
1381 @Override
1382 public Node leaveNE_STRICT(final BinaryNode binaryNode) {
1383 return leaveCmp(binaryNode);
1384 }
1385
1386 @Override
1387 public Node leaveOR(final BinaryNode binaryNode) {
1388 return end(ensureSymbol(Type.OBJECT, binaryNode));
1389 }
1390
1391 @Override
1392 public Node leaveSAR(final BinaryNode binaryNode) {
1393 return end(coerce(binaryNode, Type.INT));
1394 }
1395
1396 @Override
1397 public Node leaveSHL(final BinaryNode binaryNode) {
1398 return end(coerce(binaryNode, Type.INT));
1399 }
1400
1401 @Override
1402 public Node leaveSHR(final BinaryNode binaryNode) {
1403 return end(coerce(binaryNode, Type.LONG));
1404 }
1405
1406 @Override
1407 public Node leaveSUB(final BinaryNode binaryNode) {
1408 return leaveBinaryArithmetic(binaryNode);
1409 }
1410
1411 @Override
1412 public Node leaveForNode(final ForNode forNode) {
1413 if (forNode.isForIn()) {
1414 forNode.setIterator(newInternal(lc.getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.typeFor(ITERATOR_PREFIX.type()))); //NASHORN-73
1415 /*
1416 * Iterators return objects, so we need to widen the scope of the
1417 * init variable if it, for example, has been assigned double type
1418 * see NASHORN-50
1419 */
1420 newType(forNode.getInit().getSymbol(), Type.OBJECT);
1421 }
1422
1423 end(forNode);
1424
1425 return forNode;
1426 }
1427
1428 @Override
1429 public Node leaveTernaryNode(final TernaryNode ternaryNode) {
1430 final Expression trueExpr = ternaryNode.getTrueExpression();
1431 final Expression falseExpr = ternaryNode.getFalseExpression();
1432
1433 ensureTypeNotUnknown(trueExpr);
1434 ensureTypeNotUnknown(falseExpr);
1435
1436 final Type type = widestReturnType(trueExpr.getType(), falseExpr.getType());
1437 return end(ensureSymbol(type, ternaryNode));
1438 }
1439
1440 /**
1441 * When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to
1442 * anything other than Object. Also, widening a numeric type to an object type must widen to Object proper and not
1443 * any more specific subclass (e.g. widest of int/long/double and String is Object).
1444 * @param t1 type 1
1445 * @param t2 type 2
1446 * @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, or if one is
1447 * numeric and the other is neither numeric nor unknown in which case {@code Type.OBJECT} is returned.
1448 */
1449 private static Type widestReturnType(final Type t1, final Type t2) {
1450 if (t1.isUnknown()) {
1451 return t2;
1452 } else if (t2.isUnknown()) {
1453 return t1;
1454 } else if (t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) {
1455 return Type.OBJECT;
1456 }
1457 return Type.widest(t1, t2);
1458 }
1459
1460 private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) {
1461 final Class<?> type = cc.type();
1462 // Must not call this method for constants with no explicit types; use the one with (..., Type) signature instead.
1463 assert type != null;
1464 initCompileConstant(cc, block, flags, Type.typeFor(type));
1465 }
1466
1467 private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags, final Type type) {
1468 final Symbol symbol = defineSymbol(block, cc.symbolName(), flags);
1469 symbol.setTypeOverride(type);
1470 symbol.setNeedsSlot(true);
1471 }
1472
1473 /**
1474 * Initialize parameters for function node. This may require specializing
1475 * types if a specialization profile is known
1476 *
1477 * @param functionNode the function node
1478 */
1479 private void initParameters(final FunctionNode functionNode, final Block body) {
1480 int pos = 0;
1481 for (final IdentNode param : functionNode.getParameters()) {
1482 addLocalDef(param.getName());
1483
1484 final Type callSiteParamType = functionNode.getHints().getParameterType(pos);
1485 int flags = IS_PARAM;
1486 if (callSiteParamType != null) {
1487 LOG.info("Param ", param, " has a callsite type ", callSiteParamType, ". Using that.");
1488 flags |= Symbol.IS_SPECIALIZED_PARAM;
1489 }
1490
1491 final Symbol paramSymbol = defineSymbol(body, param.getName(), flags);
1492 assert paramSymbol != null;
1493
1494 newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType);
1495
1496 LOG.info("Initialized param ", pos, "=", paramSymbol);
1497 pos++;
1498 }
1499
1500 }
1501
1502 /**
1503 * This has to run before fix assignment types, store any type specializations for
1504 * paramters, then turn then to objects for the generic version of this method
1505 *
1506 * @param functionNode functionNode
1507 */
1508 private FunctionNode finalizeParameters(final FunctionNode functionNode) {
1509 final List<IdentNode> newParams = new ArrayList<>();
1510 final boolean isVarArg = functionNode.isVarArg();
1511 final int nparams = functionNode.getParameters().size();
1512
1513 int specialize = 0;
1514 int pos = 0;
1515 for (final IdentNode param : functionNode.getParameters()) {
1516 final Symbol paramSymbol = functionNode.getBody().getExistingSymbol(param.getName());
1517 assert paramSymbol != null;
1518 assert paramSymbol.isParam();
1519 newParams.add((IdentNode)param.setSymbol(lc, paramSymbol));
1520
1521 assert paramSymbol != null;
1522 Type type = functionNode.getHints().getParameterType(pos);
1523 if (type == null) {
1524 type = Type.OBJECT;
1525 }
1526
1527 // if we know that a parameter is only used as a certain type throughout
1528 // this function, we can tell the runtime system that no matter what the
1529 // call site is, use this information:
1530 // we also need more than half of the parameters to be specializable
1531 // for the heuristic to be worth it, and we need more than one use of
1532 // the parameter to consider it, i.e. function(x) { call(x); } doens't count
1533 if (paramSymbol.getUseCount() > 1 && !paramSymbol.getSymbolType().isObject()) {
1534 LOG.finest("Parameter ", param, " could profit from specialization to ", paramSymbol.getSymbolType());
1535 specialize++;
1536 }
1537
1538 newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType()));
1539
1540 // parameters should not be slots for a function that uses variable arity signature
1541 if (isVarArg) {
1542 paramSymbol.setNeedsSlot(false);
1543 }
1544
1545 pos++;
1546 }
1547
1548 FunctionNode newFunctionNode = functionNode;
1549
1550 if (nparams == 0 || (specialize * 2) < nparams) {
1551 newFunctionNode = newFunctionNode.clearSnapshot(lc);
1552 }
1553
1554 return newFunctionNode.setParameters(lc, newParams);
1555 }
1556
1557 /**
1558 * Move any properties from a global map into the scope of this method
1559 * @param block the function node body for which to init scope vars
1560 */
1561 private void initFromPropertyMap(final Block block) {
1562 // For a script, add scope symbols as defined in the property map
1563
1564 final PropertyMap map = Context.getGlobalMap();
1565
1566 for (final Property property : map.getProperties()) {
1567 final String key = property.getKey();
1568 final Symbol symbol = defineSymbol(block, key, IS_GLOBAL);
1569 newType(symbol, Type.OBJECT);
1570 LOG.info("Added global symbol from property map ", symbol);
1571 }
1572 }
1573
1574 private static void ensureTypeNotUnknown(final Expression node) {
1575
1576 final Symbol symbol = node.getSymbol();
1577
1578 LOG.info("Ensure type not unknown for: ", symbol);
1579
1580 /*
1581 * Note that not just unknowns, but params need to be blown
1582 * up to objects, because we can have something like
1583 *
1584 * function f(a) {
1585 * var b = ~a; //b and a are inferred to be int
1586 * return b;
1587 * }
1588 *
1589 * In this case, it would be correct to say that "if you have
1590 * an int at the callsite, just pass it".
1591 *
1592 * However
1593 *
1594 * function f(a) {
1595 * var b = ~a; //b and a are inferred to be int
1596 * return b == 17; //b is still inferred to be int.
1597 * }
1598 *
1599 * can be called with f("17") and if we assume that b is an
1600 * int and don't blow it up to an object in the comparison, we
1601 * are screwed. I hate JavaScript.
1602 *
1603 * This check has to be done for any operation that might take
1604 * objects as parameters, for example +, but not *, which is known
1605 * to coerce types into doubles
1606 */
1607 if (node.getType().isUnknown() || (symbol.isParam() && !symbol.isSpecializedParam())) {
1608 newType(symbol, Type.OBJECT);
1609 symbol.setCanBeUndefined();
1610 }
1611 }
1612
1613 private static Symbol pseudoSymbol(final String name) {
1614 return new Symbol(name, 0, Type.OBJECT);
1615 }
1616
1617 private Symbol exceptionSymbol() {
1618 return newInternal(lc.getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(EXCEPTION_PREFIX.type()));
1619 }
1620
1621 /**
1622 * Return the type that arithmetic ops should use. Until we have implemented better type
1623 * analysis (range based) or overflow checks that are fast enough for int arithmetic,
1624 * this is the number type
1625 * @return the arithetic type
1626 */
1627 private static Type arithType() {
1628 return Compiler.shouldUseIntegerArithmetic() ? Type.INT : Type.NUMBER;
1629 }
1630
1631 /**
1632 * If types have changed, we can have failed to update vars. For example
1633 *
1634 * var x = 17; //x is int
1635 * x = "apa"; //x is object. This will be converted fine
1636 *
1637 * @param functionNode
1638 */
1639 private FunctionNode finalizeTypes(final FunctionNode functionNode) {
1640 final Set<Node> changed = new HashSet<>();
1641 FunctionNode currentFunctionNode = functionNode;
1642 do {
1643 changed.clear();
1644 final FunctionNode newFunctionNode = (FunctionNode)currentFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
1645
1646 private Expression widen(final Expression node, final Type to) {
1647 if (node instanceof LiteralNode) {
1648 return node;
1649 }
1650 Type from = node.getType();
1651 if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) {
1652 LOG.fine("Had to post pass widen '", node, "' ", Debug.id(node), " from ", node.getType(), " to ", to);
1653 Symbol symbol = node.getSymbol();
1654 if (symbol.isShared() && symbol.wouldChangeType(to)) {
1655 symbol = temporarySymbols.getTypedTemporarySymbol(to);
1656 }
1657 newType(symbol, to);
1658 final Expression newNode = node.setSymbol(lc, symbol);
1659 changed.add(newNode);
1660 return newNode;
1661 }
1662 return node;
1663 }
1664
1665 @Override
1666 public boolean enterFunctionNode(final FunctionNode node) {
1667 return !node.isLazy();
1668 }
1669
1670 //
1671 // Eg.
1672 //
1673 // var d = 17;
1674 // var e;
1675 // e = d; //initially typed as int for node type, should retype as double
1676 // e = object;
1677 //
1678 // var d = 17;
1679 // var e;
1680 // e -= d; //initially type number, should number remain with a final conversion supplied by Store. ugly, but the computation result of the sub is numeric
1681 // e = object;
1682 //
1683 @SuppressWarnings("fallthrough")
1684 @Override
1685 public Node leaveBinaryNode(final BinaryNode binaryNode) {
1686 final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
1687 BinaryNode newBinaryNode = binaryNode;
1688
1689 if (isAdd(binaryNode)) {
1690 newBinaryNode = (BinaryNode)widen(newBinaryNode, widest);
1691 if (newBinaryNode.getType().isObject() && !isAddString(newBinaryNode)) {
1692 return new RuntimeNode(newBinaryNode, Request.ADD);
1693 }
1694 } else if (binaryNode.isComparison()) {
1695 final Expression lhs = newBinaryNode.lhs();
1696 final Expression rhs = newBinaryNode.rhs();
1697
1698 Type cmpWidest = Type.widest(lhs.getType(), rhs.getType());
1699
1700 boolean newRuntimeNode = false, finalized = false;
1701 switch (newBinaryNode.tokenType()) {
1702 case EQ_STRICT:
1703 case NE_STRICT:
1704 if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
1705 newRuntimeNode = true;
1706 cmpWidest = Type.OBJECT;
1707 finalized = true;
1708 }
1709 //fallthru
1710 default:
1711 if (newRuntimeNode || cmpWidest.isObject()) {
1712 return new RuntimeNode(newBinaryNode, Request.requestFor(binaryNode)).setIsFinal(finalized);
1713 }
1714 break;
1715 }
1716
1717 return newBinaryNode;
1718 } else {
1719 if (!binaryNode.isAssignment() || binaryNode.isSelfModifying()) {
1720 return newBinaryNode;
1721 }
1722 checkThisAssignment(binaryNode);
1723 newBinaryNode = newBinaryNode.setLHS(widen(newBinaryNode.lhs(), widest));
1724 newBinaryNode = (BinaryNode)widen(newBinaryNode, widest);
1725 }
1726
1727 return newBinaryNode;
1728
1729 }
1730
1731 private boolean isAdd(final Node node) {
1732 return node.isTokenType(TokenType.ADD);
1733 }
1734
1735 /**
1736 * Determine if the outcome of + operator is a string.
1737 *
1738 * @param node Node to test.
1739 * @return true if a string result.
1740 */
1741 private boolean isAddString(final Node node) {
1742 if (node instanceof BinaryNode && isAdd(node)) {
1743 final BinaryNode binaryNode = (BinaryNode)node;
1744 final Node lhs = binaryNode.lhs();
1745 final Node rhs = binaryNode.rhs();
1746
1747 return isAddString(lhs) || isAddString(rhs);
1748 }
1749
1750 return node instanceof LiteralNode<?> && ((LiteralNode<?>)node).isString();
1751 }
1752
1753 private void checkThisAssignment(final BinaryNode binaryNode) {
1754 if (binaryNode.isAssignment()) {
1755 if (binaryNode.lhs() instanceof AccessNode) {
1756 final AccessNode accessNode = (AccessNode) binaryNode.lhs();
1757
1758 if (accessNode.getBase().getSymbol().isThis()) {
1759 lc.getCurrentFunction().addThisProperty(accessNode.getProperty().getName());
1760 }
1761 }
1762 }
1763 }
1764 });
1765 lc.replace(currentFunctionNode, newFunctionNode);
1766 currentFunctionNode = newFunctionNode;
1767 } while (!changed.isEmpty());
1768
1769 return currentFunctionNode;
1770 }
1771
1772 private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode) {
1773 return leaveSelfModifyingAssignmentNode(binaryNode, binaryNode.getWidestOperationType());
1774 }
1775
1776 private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type destType) {
1777 //e.g. for -=, Number, no wider, destType (binaryNode.getWidestOperationType()) is the coerce type
1778 final Expression lhs = binaryNode.lhs();
1779
1780 newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType
1781
1782 return end(ensureSymbol(destType, binaryNode));
1783 }
1784
1785 private Expression ensureSymbol(final Type type, final Expression expr) {
1786 LOG.info("New TEMPORARY added to ", lc.getCurrentFunction().getName(), " type=", type);
1787 return temporarySymbols.ensureSymbol(lc, type, expr);
1788 }
1789
1790 private Symbol newInternal(final String name, final Type type) {
1791 final Symbol iter = defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL);
1792 iter.setType(type); // NASHORN-73
1793 return iter;
1794 }
1795
1796 private static void newType(final Symbol symbol, final Type type) {
1797 final Type oldType = symbol.getSymbolType();
1798 symbol.setType(type);
1799
1800 if (symbol.getSymbolType() != oldType) {
1801 LOG.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")");
1802 }
1803
1804 if (symbol.isParam()) {
1805 symbol.setType(type);
1806 LOG.info("Param type change ", symbol);
1807 }
1808 }
1809
1810 private void pushLocalsFunction() {
1811 localDefs.push(new HashSet<String>());
1812 localUses.push(new HashSet<String>());
1813 }
1814
1815 private void pushLocalsBlock() {
1816 localDefs.push(new HashSet<>(localDefs.peek()));
1817 localUses.push(new HashSet<>(localUses.peek()));
1818 }
1819
1820 private void popLocals() {
1821 localDefs.pop();
1822 localUses.pop();
1823 }
1824
1825 private boolean isLocalDef(final String name) {
1826 return localDefs.peek().contains(name);
1827 }
1828
1829 private void addLocalDef(final String name) {
1830 LOG.info("Adding local def of symbol: '", name, "'");
1831 localDefs.peek().add(name);
1832 }
1833
1834 private void removeLocalDef(final String name) {
1835 LOG.info("Removing local def of symbol: '", name, "'");
1836 localDefs.peek().remove(name);
1837 }
1838
1839 private boolean isLocalUse(final String name) {
1840 return localUses.peek().contains(name);
1841 }
1842
1843 private void addLocalUse(final String name) {
1844 LOG.info("Adding local use of symbol: '", name, "'");
1845 localUses.peek().add(name);
1846 }
1847
1848 /**
1849 * Pessimistically promote all symbols in current function node to Object types
1850 * This is done when the function contains unevaluated black boxes such as
1851 * lazy sub-function nodes that have not been compiled.
1852 *
1853 * @param body body for the function node we are leaving
1854 */
1855 private static void objectifySymbols(final Block body) {
1856 body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
1857 private void toObject(final Block block) {
1858 for (final Symbol symbol : block.getSymbols()) {
1859 if (!symbol.isTemp()) {
1860 newType(symbol, Type.OBJECT);
1861 }
1862 }
1863 }
1864
1865 @Override
1866 public boolean enterBlock(final Block block) {
1867 toObject(block);
1868 return true;
1869 }
1870
1871 @Override
1872 public boolean enterFunctionNode(final FunctionNode node) {
1873 return false;
1874 }
1875 });
1876 }
1877
1878 private static String name(final Node node) {
1879 final String cn = node.getClass().getName();
1880 int lastDot = cn.lastIndexOf('.');
1881 if (lastDot == -1) {
1882 return cn;
1883 }
1884 return cn.substring(lastDot + 1);
1885 }
1886
1887 private boolean start(final Node node) {
1888 return start(node, true);
1889 }
1890
1891 private boolean start(final Node node, final boolean printNode) {
1892 if (DEBUG) {
1893 final StringBuilder sb = new StringBuilder();
1894
1895 sb.append("[ENTER ").
1896 append(name(node)).
1897 append("] ").
1898 append(printNode ? node.toString() : "").
1899 append(" in '").
1900 append(lc.getCurrentFunction().getName()).
1901 append("'");
1902 LOG.info(sb);
1903 LOG.indent();
1904 }
1905
1906 return true;
1907 }
1908
1909 private <T extends Node> T end(final T node) {
1910 return end(node, true);
1911 }
1912
1913 private <T extends Node> T end(final T node, final boolean printNode) {
1914 if(node instanceof Statement) {
1915 // If we're done with a statement, all temporaries can be reused.
1916 temporarySymbols.reuse();
1917 }
1918 if (DEBUG) {
1919 final StringBuilder sb = new StringBuilder();
1920
1921 sb.append("[LEAVE ").
1922 append(name(node)).
1923 append("] ").
1924 append(printNode ? node.toString() : "").
1925 append(" in '").
1926 append(lc.getCurrentFunction().getName()).
1927 append('\'');
1928
1929 if (node instanceof Expression) {
1930 final Symbol symbol = ((Expression)node).getSymbol();
1931 if (symbol == null) {
1932 sb.append(" <NO SYMBOL>");
1933 } else {
1934 sb.append(" <symbol=").append(symbol).append('>');
1935 }
1936 }
1937
1938 LOG.unindent();
1939 LOG.info(sb);
1940 }
1941
1942 return node;
1943 }
1944 }
--- EOF ---