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.RETURN;
34 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
35 import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX;
36 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
37 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
38 import static jdk.nashorn.internal.ir.Symbol.HAS_OBJECT_VALUE;
39 import static jdk.nashorn.internal.ir.Symbol.IS_CONST;
40 import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF;
41 import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
42 import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
43 import static jdk.nashorn.internal.ir.Symbol.IS_LET;
44 import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
45 import static jdk.nashorn.internal.ir.Symbol.IS_PROGRAM_LEVEL;
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.HashMap;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.ListIterator;
59 import java.util.Map;
60 import java.util.Set;
61 import jdk.nashorn.internal.ir.AccessNode;
62 import jdk.nashorn.internal.ir.BinaryNode;
63 import jdk.nashorn.internal.ir.Block;
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.IdentNode;
69 import jdk.nashorn.internal.ir.IndexNode;
70 import jdk.nashorn.internal.ir.LexicalContextNode;
71 import jdk.nashorn.internal.ir.LiteralNode;
72 import jdk.nashorn.internal.ir.Node;
73 import jdk.nashorn.internal.ir.RuntimeNode;
74 import jdk.nashorn.internal.ir.RuntimeNode.Request;
75 import jdk.nashorn.internal.ir.Splittable;
76 import jdk.nashorn.internal.ir.Statement;
77 import jdk.nashorn.internal.ir.SwitchNode;
78 import jdk.nashorn.internal.ir.Symbol;
79 import jdk.nashorn.internal.ir.TryNode;
80 import jdk.nashorn.internal.ir.UnaryNode;
81 import jdk.nashorn.internal.ir.VarNode;
82 import jdk.nashorn.internal.ir.WithNode;
83 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
84 import jdk.nashorn.internal.parser.TokenType;
85 import jdk.nashorn.internal.runtime.Context;
86 import jdk.nashorn.internal.runtime.ECMAErrors;
87 import jdk.nashorn.internal.runtime.ErrorManager;
88 import jdk.nashorn.internal.runtime.JSErrorType;
89 import jdk.nashorn.internal.runtime.ParserException;
90 import jdk.nashorn.internal.runtime.Source;
91 import jdk.nashorn.internal.runtime.logging.DebugLogger;
92 import jdk.nashorn.internal.runtime.logging.Loggable;
93 import jdk.nashorn.internal.runtime.logging.Logger;
94
95 /**
96 * This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only
97 * possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime
98 * nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable
99 * for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types
100 * during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate
101 * visitor.
102 */
103 @Logger(name="symbols")
104 final class AssignSymbols extends SimpleNodeVisitor implements Loggable {
105 private final DebugLogger log;
106 private final boolean debug;
107
108 private static boolean isParamOrVar(final IdentNode identNode) {
109 final Symbol symbol = identNode.getSymbol();
110 return symbol.isParam() || symbol.isVar();
111 }
112
113 private static String name(final Node node) {
114 final String cn = node.getClass().getName();
115 final int lastDot = cn.lastIndexOf('.');
116 if (lastDot == -1) {
117 return cn;
118 }
119 return cn.substring(lastDot + 1);
120 }
121
122 /**
123 * Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not
124 * needing a slot after all.
125 * @param functionNode the function node
126 * @return the passed in node, for easy chaining
127 */
128 private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) {
129 if (!functionNode.needsCallee()) {
130 functionNode.compilerConstant(CALLEE).setNeedsSlot(false);
131 }
132 if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) {
133 functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
134 }
135 // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol.
136 if(functionNode.isNamedFunctionExpression() && !functionNode.usesSelfSymbol()) {
137 final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName());
138 if(selfSymbol != null && selfSymbol.isFunctionSelf()) {
139 selfSymbol.setNeedsSlot(false);
140 selfSymbol.clearFlag(Symbol.IS_VAR);
141 }
142 }
143 return functionNode;
144 }
145
146 private final Deque<Set<String>> thisProperties = new ArrayDeque<>();
147 private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
148 private final Compiler compiler;
149 private final boolean isOnDemand;
150
151 public AssignSymbols(final Compiler compiler) {
152 this.compiler = compiler;
153 this.log = initLogger(compiler.getContext());
154 this.debug = log.isEnabled();
155 this.isOnDemand = compiler.isOnDemandCompilation();
156 }
157
158 @Override
159 public DebugLogger getLogger() {
160 return log;
161 }
162
163 @Override
164 public DebugLogger initLogger(final Context context) {
165 return context.getLogger(this.getClass());
166 }
167
168 /**
169 * Define symbols for all variable declarations at the top of the function scope. This way we can get around
170 * problems like
171 *
172 * while (true) {
173 * break;
174 * if (true) {
175 * var s;
176 * }
177 * }
178 *
179 * to an arbitrary nesting depth.
180 *
181 * see NASHORN-73
182 *
183 * @param functionNode the FunctionNode we are entering
184 * @param body the body of the FunctionNode we are entering
185 */
186 private void acceptDeclarations(final FunctionNode functionNode, final Block body) {
187 // This visitor will assign symbol to all declared variables.
188 body.accept(new SimpleNodeVisitor() {
189 @Override
190 protected boolean enterDefault(final Node node) {
191 // Don't bother visiting expressions; var is a statement, it can't be inside an expression.
192 // This will also prevent visiting nested functions (as FunctionNode is an expression).
193 return !(node instanceof Expression);
194 }
195
196 @Override
197 public Node leaveVarNode(final VarNode varNode) {
198 final IdentNode ident = varNode.getName();
199 final boolean blockScoped = varNode.isBlockScoped();
200 if (blockScoped && lc.inUnprotectedSwitchContext()) {
201 throwUnprotectedSwitchError(varNode);
202 }
203 final Block block = blockScoped ? lc.getCurrentBlock() : body;
204 final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags());
205 if (varNode.isFunctionDeclaration()) {
206 symbol.setIsFunctionDeclaration();
207 }
208 return varNode.setName(ident.setSymbol(symbol));
209 }
210 });
211 }
212
213 private IdentNode compilerConstantIdentifier(final CompilerConstants cc) {
214 return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc));
215 }
216
217 /**
218 * Creates an ident node for an implicit identifier within the function (one not declared in the script source
219 * code). These identifiers are defined with function's token and finish.
220 * @param name the name of the identifier
221 * @return an ident node representing the implicit identifier.
222 */
223 private IdentNode createImplicitIdentifier(final String name) {
224 final FunctionNode fn = lc.getCurrentFunction();
225 return new IdentNode(fn.getToken(), fn.getFinish(), name);
226 }
227
228 private Symbol createSymbol(final String name, final int flags) {
229 if ((flags & Symbol.KINDMASK) == IS_GLOBAL) {
230 //reuse global symbols so they can be hashed
231 Symbol global = globalSymbols.get(name);
232 if (global == null) {
233 global = new Symbol(name, flags);
234 globalSymbols.put(name, global);
235 }
236 return global;
237 }
238 return new Symbol(name, flags);
239 }
240
241 /**
242 * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically
243 * used to create assignment of {@code :callee} to the function name symbol in self-referential function
244 * expressions as well as for assignment of {@code :arguments} to {@code arguments}.
245 *
246 * @param name the ident node identifying the variable to initialize
247 * @param initConstant the compiler constant it is initialized to
248 * @param fn the function node the assignment is for
249 * @return a var node with the appropriate assignment
250 */
251 private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) {
252 final IdentNode init = compilerConstantIdentifier(initConstant);
253 assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal();
254
255 final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init);
256
257 final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName());
258 assert nameSymbol != null;
259
260 return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this);
261 }
262
263 private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) {
264 final List<VarNode> syntheticInitializers = new ArrayList<>(2);
265
266 // Must visit the new var nodes in the context of the body. We could also just set the new statements into the
267 // block and then revisit the entire block, but that seems to be too much double work.
268 final Block body = functionNode.getBody();
269 lc.push(body);
270 try {
271 if (functionNode.usesSelfSymbol()) {
272 // "var fn = :callee"
273 syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode));
274 }
275
276 if (functionNode.needsArguments()) {
277 // "var arguments = :arguments"
278 syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()),
279 ARGUMENTS, functionNode));
280 }
281
282 if (syntheticInitializers.isEmpty()) {
283 return functionNode;
284 }
285
286 for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) {
287 it.set((VarNode)it.next().accept(this));
288 }
289 } finally {
290 lc.pop(body);
291 }
292
293 final List<Statement> stmts = body.getStatements();
294 final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size());
295 newStatements.addAll(syntheticInitializers);
296 newStatements.addAll(stmts);
297 return functionNode.setBody(lc, body.setStatements(lc, newStatements));
298 }
299
300 /**
301 * Defines a new symbol in the given block.
302 *
303 * @param block the block in which to define the symbol
304 * @param name name of symbol.
305 * @param origin origin node
306 * @param symbolFlags Symbol flags.
307 *
308 * @return Symbol for given name or null for redefinition.
309 */
310 private Symbol defineSymbol(final Block block, final String name, final Node origin, final int symbolFlags) {
311 int flags = symbolFlags;
312 final boolean isBlockScope = (flags & IS_LET) != 0 || (flags & IS_CONST) != 0;
313 final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL;
314
315 Symbol symbol;
316 final FunctionNode function;
317 if (isBlockScope) {
318 // block scoped variables always live in current block, no need to look for existing symbols in parent blocks.
319 symbol = block.getExistingSymbol(name);
320 function = lc.getCurrentFunction();
321 } else {
322 symbol = findSymbol(block, name);
323 function = lc.getFunction(block);
324 }
325
326 // Global variables are implicitly always scope variables too.
327 if (isGlobal) {
328 flags |= IS_SCOPE;
329 }
330
331 if (lc.getCurrentFunction().isProgram()) {
332 flags |= IS_PROGRAM_LEVEL;
333 }
334
335 final boolean isParam = (flags & KINDMASK) == IS_PARAM;
336 final boolean isVar = (flags & KINDMASK) == IS_VAR;
337
338 if (symbol != null) {
339 // Symbol was already defined. Check if it needs to be redefined.
340 if (isParam) {
341 if (!isLocal(function, symbol)) {
342 // Not defined in this function. Create a new definition.
343 symbol = null;
344 } else if (symbol.isParam()) {
345 // Duplicate parameter. Null return will force an error.
346 throw new AssertionError("duplicate parameter");
347 }
348 } else if (isVar) {
349 if (isBlockScope) {
350 // Check redeclaration in same block
351 if (symbol.hasBeenDeclared()) {
352 throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin);
353 } else {
354 symbol.setHasBeenDeclared();
355 // Set scope flag on top-level block scoped symbols
356 if (function.isProgram() && function.getBody() == block) {
357 symbol.setIsScope();
358 }
359 }
360 } else if ((flags & IS_INTERNAL) != 0) {
361 // Always create a new definition.
362 symbol = null;
363 } else {
364 // Found LET or CONST in parent scope of same function - s SyntaxError
365 if (symbol.isBlockScoped() && isLocal(lc.getCurrentFunction(), symbol)) {
366 throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin);
367 }
368 // Not defined in this function. Create a new definition.
369 if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
370 symbol = null;
371 }
372 }
373 }
374 }
375
376 if (symbol == null) {
377 // If not found, then create a new one.
378 final Block symbolBlock;
379
380 // Determine where to create it.
381 if (isVar && ((flags & IS_INTERNAL) != 0 || isBlockScope)) {
382 symbolBlock = block; //internal vars are always defined in the block closest to them
383 } else if (isGlobal) {
384 symbolBlock = lc.getOutermostFunction().getBody();
385 } else {
386 symbolBlock = lc.getFunctionBody(function);
387 }
388
389 // Create and add to appropriate block.
390 symbol = createSymbol(name, flags);
391 symbolBlock.putSymbol(symbol);
392
393 if ((flags & IS_SCOPE) == 0) {
394 // Initial assumption; symbol can lose its slot later
395 symbol.setNeedsSlot(true);
396 }
397 } else if (symbol.less(flags)) {
398 symbol.setFlags(flags);
399 }
400
401 return symbol;
402 }
403
404 private <T extends Node> T end(final T node) {
405 return end(node, true);
406 }
407
408 private <T extends Node> T end(final T node, final boolean printNode) {
409 if (debug) {
410 final StringBuilder sb = new StringBuilder();
411
412 sb.append("[LEAVE ").
413 append(name(node)).
414 append("] ").
415 append(printNode ? node.toString() : "").
416 append(" in '").
417 append(lc.getCurrentFunction().getName()).
418 append('\'');
419
420 if (node instanceof IdentNode) {
421 final Symbol symbol = ((IdentNode)node).getSymbol();
422 if (symbol == null) {
423 sb.append(" <NO SYMBOL>");
424 } else {
425 sb.append(" <symbol=").append(symbol).append('>');
426 }
427 }
428
429 log.unindent();
430 log.info(sb);
431 }
432
433 return node;
434 }
435
436 @Override
437 public boolean enterBlock(final Block block) {
438 start(block);
439
440 if (lc.isFunctionBody()) {
441 assert !block.hasSymbols();
442 final FunctionNode fn = lc.getCurrentFunction();
443 if (isUnparsedFunction(fn)) {
444 // It's a skipped nested function. Just mark the symbols being used by it as being in use.
445 for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) {
446 nameIsUsed(name, null);
447 }
448 // Don't bother descending into it, it must be empty anyway.
449 assert block.getStatements().isEmpty();
450 return false;
451 }
452
453 enterFunctionBody();
454 }
455
456 return true;
457 }
458
459 private boolean isUnparsedFunction(final FunctionNode fn) {
460 return isOnDemand && fn != lc.getOutermostFunction();
461 }
462
463 @Override
464 public boolean enterCatchNode(final CatchNode catchNode) {
465 final IdentNode exception = catchNode.getException();
466 final Block block = lc.getCurrentBlock();
467
468 start(catchNode);
469
470 // define block-local exception variable
471 final String exname = exception.getName();
472 // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its
473 // symbol is naturally internal, and should be treated as such.
474 final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName());
475 // IS_LET flag is required to make sure symbol is not visible outside catch block. However, we need to
476 // clear the IS_LET flag after creation to allow redefinition of symbol inside the catch block.
477 final Symbol symbol = defineSymbol(block, exname, catchNode, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE);
478 symbol.clearFlag(IS_LET);
479
480 return true;
481 }
482
483 private void enterFunctionBody() {
484 final FunctionNode functionNode = lc.getCurrentFunction();
485 final Block body = lc.getCurrentBlock();
486
487 initFunctionWideVariables(functionNode, body);
488 acceptDeclarations(functionNode, body);
489 defineFunctionSelfSymbol(functionNode, body);
490 }
491
492 private void defineFunctionSelfSymbol(final FunctionNode functionNode, final Block body) {
493 // Function self-symbol is only declared as a local variable for named function expressions. Declared functions
494 // don't need it as they are local variables in their declaring scope.
495 if (!functionNode.isNamedFunctionExpression()) {
496 return;
497 }
498
499 final String name = functionNode.getIdent().getName();
500 assert name != null; // As it's a named function expression.
501
502 if (body.getExistingSymbol(name) != null) {
503 // Body already has a declaration for the name. It's either a parameter "function x(x)" or a
504 // top-level variable "function x() { ... var x; ... }".
505 return;
506 }
507
508 defineSymbol(body, name, functionNode, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE);
509 if(functionNode.allVarsInScope()) { // basically, has deep eval
510 // We must conservatively presume that eval'd code can dynamically use the function symbol.
511 lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
512 }
513 }
514
515 @Override
516 public boolean enterFunctionNode(final FunctionNode functionNode) {
517 start(functionNode, false);
518
519 thisProperties.push(new HashSet<String>());
520
521 // Every function has a body, even the ones skipped on reparse (they have an empty one). We're
522 // asserting this as even for those, enterBlock() must be invoked to correctly process symbols that
523 // are used in them.
524 assert functionNode.getBody() != null;
525
526 return true;
527 }
528
529 @Override
530 public boolean enterVarNode(final VarNode varNode) {
531 start(varNode);
532 // Normally, a symbol assigned in a var statement is not live for its RHS. Since we also represent function
533 // declarations as VarNodes, they are exception to the rule, as they need to have the symbol visible to the
534 // body of the declared function for self-reference.
535 if (varNode.isFunctionDeclaration()) {
536 defineVarIdent(varNode);
537 }
538 return true;
539 }
540
541 @Override
542 public Node leaveVarNode(final VarNode varNode) {
543 if (!varNode.isFunctionDeclaration()) {
544 defineVarIdent(varNode);
545 }
546 return super.leaveVarNode(varNode);
547 }
548
549 private void defineVarIdent(final VarNode varNode) {
550 final IdentNode ident = varNode.getName();
551 final int flags;
552 if (!varNode.isBlockScoped() && lc.getCurrentFunction().isProgram()) {
553 flags = IS_SCOPE;
554 } else {
555 flags = 0;
556 }
557 defineSymbol(lc.getCurrentBlock(), ident.getName(), ident, varNode.getSymbolFlags() | flags);
558 }
559
560 private Symbol exceptionSymbol() {
561 return newObjectInternal(EXCEPTION_PREFIX);
562 }
563
564 /**
565 * This has to run before fix assignment types, store any type specializations for
566 * parameters, then turn them into objects for the generic version of this method.
567 *
568 * @param functionNode functionNode
569 */
570 private FunctionNode finalizeParameters(final FunctionNode functionNode) {
571 final List<IdentNode> newParams = new ArrayList<>();
572 final boolean isVarArg = functionNode.isVarArg();
573
574 final Block body = functionNode.getBody();
575 for (final IdentNode param : functionNode.getParameters()) {
576 final Symbol paramSymbol = body.getExistingSymbol(param.getName());
577 assert paramSymbol != null;
578 assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags();
579 newParams.add(param.setSymbol(paramSymbol));
580
581 // parameters should not be slots for a function that uses variable arity signature
582 if (isVarArg) {
583 paramSymbol.setNeedsSlot(false);
584 }
585 }
586
587 return functionNode.setParameters(lc, newParams);
588 }
589
590 /**
591 * Search for symbol in the lexical context starting from the given block.
592 * @param name Symbol name.
593 * @return Found symbol or null if not found.
594 */
595 private Symbol findSymbol(final Block block, final String name) {
596 for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) {
597 final Symbol symbol = blocks.next().getExistingSymbol(name);
598 if (symbol != null) {
599 return symbol;
600 }
601 }
602 return null;
603 }
604
605 /**
606 * Marks the current function as one using any global symbol. The function and all its parent functions will all be
607 * marked as needing parent scope.
608 * @see FunctionNode#needsParentScope()
609 */
610 private void functionUsesGlobalSymbol() {
611 for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) {
612 lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
613 }
614 }
615
616 /**
617 * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing
618 * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all
619 * functions up to (but not including) the function containing the defining block will be marked as needing parent
620 * function scope.
621 * @see FunctionNode#needsParentScope()
622 */
623 private void functionUsesScopeSymbol(final Symbol symbol) {
624 final String name = symbol.getName();
625 for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) {
626 final LexicalContextNode node = contextNodeIter.next();
627 if (node instanceof Block) {
628 final Block block = (Block)node;
629 if (block.getExistingSymbol(name) != null) {
630 assert lc.contains(block);
631 lc.setBlockNeedsScope(block);
632 break;
633 }
634 } else if (node instanceof FunctionNode) {
635 lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
636 }
637 }
638 }
639
640 /**
641 * Declares that the current function is using the symbol.
642 * @param symbol the symbol used by the current function.
643 */
644 private void functionUsesSymbol(final Symbol symbol) {
645 assert symbol != null;
646 if (symbol.isScope()) {
647 if (symbol.isGlobal()) {
648 functionUsesGlobalSymbol();
649 } else {
650 functionUsesScopeSymbol(symbol);
651 }
652 } else {
653 assert !symbol.isGlobal(); // Every global is also scope
654 }
655 }
656
657 private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) {
658 defineSymbol(block, cc.symbolName(), null, flags).setNeedsSlot(true);
659 }
660
661 private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) {
662 initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
663 initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE);
664
665 if (functionNode.isVarArg()) {
666 initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
667 if (functionNode.needsArguments()) {
668 initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
669 defineSymbol(body, ARGUMENTS_VAR.symbolName(), null, IS_VAR | HAS_OBJECT_VALUE);
670 }
671 }
672
673 initParameters(functionNode, body);
674 initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
675 initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL);
676 }
677
678 /**
679 * Initialize parameters for function node.
680 * @param functionNode the function node
681 */
682 private void initParameters(final FunctionNode functionNode, final Block body) {
683 final boolean isVarArg = functionNode.isVarArg();
684 final boolean scopeParams = functionNode.allVarsInScope() || isVarArg;
685 for (final IdentNode param : functionNode.getParameters()) {
686 final Symbol symbol = defineSymbol(body, param.getName(), param, IS_PARAM);
687 if(scopeParams) {
688 // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored.
689 // It will force creation of scopes where they would otherwise not necessarily be needed (functions
690 // using arguments object and other variable arity functions). Tracked by JDK-8038942.
691 symbol.setIsScope();
692 assert symbol.hasSlot();
693 if(isVarArg) {
694 symbol.setNeedsSlot(false);
695 }
696 }
697 }
698 }
699
700 /**
701 * Is the symbol local to (that is, defined in) the specified function?
702 * @param function the function
703 * @param symbol the symbol
704 * @return true if the symbol is defined in the specified function
705 */
706 private boolean isLocal(final FunctionNode function, final Symbol symbol) {
707 final FunctionNode definingFn = lc.getDefiningFunction(symbol);
708 assert definingFn != null;
709 return definingFn == function;
710 }
711
712 @Override
713 public Node leaveBinaryNode(final BinaryNode binaryNode) {
714 if (binaryNode.isTokenType(TokenType.ASSIGN)) {
715 return leaveASSIGN(binaryNode);
716 }
717 return super.leaveBinaryNode(binaryNode);
718 }
719
720 private Node leaveASSIGN(final BinaryNode binaryNode) {
721 // If we're assigning a property of the this object ("this.foo = ..."), record it.
722 final Expression lhs = binaryNode.lhs();
723 if (lhs instanceof AccessNode) {
724 final AccessNode accessNode = (AccessNode) lhs;
725 final Expression base = accessNode.getBase();
726 if (base instanceof IdentNode) {
727 final Symbol symbol = ((IdentNode)base).getSymbol();
728 if(symbol.isThis()) {
729 thisProperties.peek().add(accessNode.getProperty());
730 }
731 }
732 }
733 return binaryNode;
734 }
735
736 @Override
737 public Node leaveUnaryNode(final UnaryNode unaryNode) {
738 switch (unaryNode.tokenType()) {
739 case DELETE:
740 return leaveDELETE(unaryNode);
741 case TYPEOF:
742 return leaveTYPEOF(unaryNode);
743 default:
744 return super.leaveUnaryNode(unaryNode);
745 }
746 }
747
748 private Node leaveDELETE(final UnaryNode unaryNode) {
749 final FunctionNode currentFunctionNode = lc.getCurrentFunction();
750 final boolean strictMode = currentFunctionNode.isStrict();
751 final Expression rhs = unaryNode.getExpression();
752 final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this);
753
754 Request request = Request.DELETE;
755 final List<Expression> args = new ArrayList<>();
756
757 if (rhs instanceof IdentNode) {
758 final IdentNode ident = (IdentNode)rhs;
759 // If this is a declared variable or a function parameter, delete always fails (except for globals).
760 final String name = ident.getName();
761 final Symbol symbol = ident.getSymbol();
762
763 if (symbol.isThis()) {
764 // Can't delete "this", ignore and return true
765 return LiteralNode.newInstance(unaryNode, true);
766 }
767 final Expression literalNode = LiteralNode.newInstance(unaryNode, name);
768 final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
769
770 if (!failDelete) {
771 args.add(compilerConstantIdentifier(SCOPE));
772 }
773 args.add(literalNode);
774 args.add(strictFlagNode);
775
776 if (failDelete) {
777 request = Request.FAIL_DELETE;
778 } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) {
779 request = Request.SLOW_DELETE;
780 }
781 } else if (rhs instanceof AccessNode) {
782 final Expression base = ((AccessNode)rhs).getBase();
783 final String property = ((AccessNode)rhs).getProperty();
784
785 args.add(base);
786 args.add(LiteralNode.newInstance(unaryNode, property));
787 args.add(strictFlagNode);
788
789 } else if (rhs instanceof IndexNode) {
790 final IndexNode indexNode = (IndexNode)rhs;
791 final Expression base = indexNode.getBase();
792 final Expression index = indexNode.getIndex();
793
794 args.add(base);
795 args.add(index);
796 args.add(strictFlagNode);
797
798 } else {
799 return LiteralNode.newInstance(unaryNode, true);
800 }
801 return new RuntimeNode(unaryNode, request, args);
802 }
803
804 @Override
805 public Node leaveForNode(final ForNode forNode) {
806 if (forNode.isForIn()) {
807 return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
808 }
809
810 return end(forNode);
811 }
812
813 @Override
814 public Node leaveFunctionNode(final FunctionNode functionNode) {
815 final FunctionNode finalizedFunction;
816 if (isUnparsedFunction(functionNode)) {
817 finalizedFunction = functionNode;
818 } else {
819 finalizedFunction =
820 markProgramBlock(
821 removeUnusedSlots(
822 createSyntheticInitializers(
823 finalizeParameters(
824 lc.applyTopFlags(functionNode))))
825 .setThisProperties(lc, thisProperties.pop().size()));
826 }
827 return finalizedFunction;
828 }
829
830 @Override
831 public Node leaveIdentNode(final IdentNode identNode) {
832 if (identNode.isPropertyName()) {
833 return identNode;
834 }
835
836 final Symbol symbol = nameIsUsed(identNode.getName(), identNode);
837
838 if (!identNode.isInitializedHere()) {
839 symbol.increaseUseCount();
840 }
841
842 IdentNode newIdentNode = identNode.setSymbol(symbol);
843
844 // If a block-scoped var is used before its declaration mark it as dead.
845 // We can only statically detect this for local vars, cross-function symbols require runtime checks.
846 if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) {
847 newIdentNode = newIdentNode.markDead();
848 }
849
850 return end(newIdentNode);
851 }
852
853 private Symbol nameIsUsed(final String name, final IdentNode origin) {
854 final Block block = lc.getCurrentBlock();
855
856 Symbol symbol = findSymbol(block, name);
857
858 //If an existing symbol with the name is found, use that otherwise, declare a new one
859 if (symbol != null) {
860 log.info("Existing symbol = ", symbol);
861 if (symbol.isFunctionSelf()) {
862 final FunctionNode functionNode = lc.getDefiningFunction(symbol);
863 assert functionNode != null;
864 assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null;
865 lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
866 }
867
868 // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already)
869 maybeForceScope(symbol);
870 } else {
871 log.info("No symbol exists. Declare as global: ", name);
872 symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE);
873 }
874
875 functionUsesSymbol(symbol);
876 return symbol;
877 }
878
879 @Override
880 public Node leaveSwitchNode(final SwitchNode switchNode) {
881 // We only need a symbol for the tag if it's not an integer switch node
882 if(!switchNode.isUniqueInteger()) {
883 return switchNode.setTag(lc, newObjectInternal(SWITCH_TAG_PREFIX));
884 }
885 return switchNode;
886 }
887
888 @Override
889 public Node leaveTryNode(final TryNode tryNode) {
890 assert tryNode.getFinallyBody() == null;
891
892 end(tryNode);
893
894 return tryNode.setException(lc, exceptionSymbol());
895 }
896
897 private Node leaveTYPEOF(final UnaryNode unaryNode) {
898 final Expression rhs = unaryNode.getExpression();
899
900 final List<Expression> args = new ArrayList<>();
901 if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) {
902 args.add(compilerConstantIdentifier(SCOPE));
903 args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName())); //null
904 } else {
905 args.add(rhs);
906 args.add(LiteralNode.newInstance(unaryNode)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
907 }
908
909 final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
910
911 end(unaryNode);
912
913 return runtimeNode;
914 }
915
916 private FunctionNode markProgramBlock(final FunctionNode functionNode) {
917 if (isOnDemand || !functionNode.isProgram()) {
918 return functionNode;
919 }
920
921 return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE));
922 }
923
924 /**
925 * If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is
926 * promoted to a scope symbol and its block marked as needing a scope.
927 * @param symbol the symbol that might be scoped
928 */
929 private void maybeForceScope(final Symbol symbol) {
930 if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) {
931 Symbol.setSymbolIsScope(lc, symbol);
932 }
933 }
934
935 private Symbol newInternal(final CompilerConstants cc, final int flags) {
936 return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), null, IS_VAR | IS_INTERNAL | flags); //NASHORN-73
937 }
938
939 private Symbol newObjectInternal(final CompilerConstants cc) {
940 return newInternal(cc, HAS_OBJECT_VALUE);
941 }
942
943 private boolean start(final Node node) {
944 return start(node, true);
945 }
946
947 private boolean start(final Node node, final boolean printNode) {
948 if (debug) {
949 final StringBuilder sb = new StringBuilder();
950
951 sb.append("[ENTER ").
952 append(name(node)).
953 append("] ").
954 append(printNode ? node.toString() : "").
955 append(" in '").
956 append(lc.getCurrentFunction().getName()).
957 append("'");
958 log.info(sb);
959 log.indent();
960 }
961
962 return true;
963 }
964
965 /**
966 * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only
967 * be reached from the current block by traversing a function node, a split node, or a with node.
968 * @param symbol the symbol checked for needing to be a scope symbol
969 * @return true if the symbol has to be a scope symbol.
970 */
971 private boolean symbolNeedsToBeScope(final Symbol symbol) {
972 if (symbol.isThis() || symbol.isInternal()) {
973 return false;
974 }
975
976 final FunctionNode func = lc.getCurrentFunction();
977 if ( func.allVarsInScope() || (!symbol.isBlockScoped() && func.isProgram())) {
978 return true;
979 }
980
981 boolean previousWasBlock = false;
982 for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
983 final LexicalContextNode node = it.next();
984 if (node instanceof FunctionNode || isSplitLiteral(node)) {
985 // We reached the function boundary or a splitting boundary without seeing a definition for the symbol.
986 // It needs to be in scope.
987 return true;
988 } else if (node instanceof WithNode) {
989 if (previousWasBlock) {
990 // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately
991 // preceded by a block, this means we're currently processing its expression, not its body,
992 // therefore it doesn't count.
993 return true;
994 }
995 previousWasBlock = false;
996 } else if (node instanceof Block) {
997 if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) {
998 // We reached the block that defines the symbol without reaching either the function boundary, or a
999 // WithNode. The symbol need not be scoped.
1000 return false;
1001 }
1002 previousWasBlock = true;
1003 } else {
1004 previousWasBlock = false;
1005 }
1006 }
1007 throw new AssertionError();
1008 }
1009
1010 private static boolean isSplitLiteral(final LexicalContextNode expr) {
1011 return expr instanceof Splittable && ((Splittable) expr).getSplitRanges() != null;
1012 }
1013
1014 private void throwUnprotectedSwitchError(final VarNode varNode) {
1015 // Block scoped declarations in switch statements without explicit blocks should be declared
1016 // in a common block that contains all the case clauses. We cannot support this without a
1017 // fundamental rewrite of how switch statements are handled (case nodes contain blocks and are
1018 // directly contained by switch node). As a temporary solution we throw a reference error here.
1019 final String msg = ECMAErrors.getMessage("syntax.error.unprotected.switch.declaration", varNode.isLet() ? "let" : "const");
1020 throwParserException(msg, varNode);
1021 }
1022
1023 private void throwParserException(final String message, final Node origin) {
1024 if (origin == null) {
1025 throw new ParserException(message);
1026 }
1027 final Source source = compiler.getSource();
1028 final long token = origin.getToken();
1029 final int line = source.getLine(origin.getStart());
1030 final int column = source.getColumn(origin.getStart());
1031 final String formatted = ErrorManager.format(message, source, line, column, token);
1032 throw new ParserException(JSErrorType.SYNTAX_ERROR, formatted, source, line, column, token);
1033 }
1034 }
--- EOF ---