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