AssignSymbols.java revision 994:e56bfeff25a9
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_FUNCTION_SELF;
40import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
41import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
42import static jdk.nashorn.internal.ir.Symbol.IS_LET;
43import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
44import static jdk.nashorn.internal.ir.Symbol.IS_PROGRAM_LEVEL;
45import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
46import static jdk.nashorn.internal.ir.Symbol.IS_THIS;
47import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
48import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
49
50import java.util.ArrayDeque;
51import java.util.ArrayList;
52import java.util.Deque;
53import java.util.HashMap;
54import java.util.HashSet;
55import java.util.Iterator;
56import java.util.List;
57import java.util.ListIterator;
58import java.util.Map;
59import java.util.Set;
60import jdk.nashorn.internal.ir.AccessNode;
61import jdk.nashorn.internal.ir.BinaryNode;
62import jdk.nashorn.internal.ir.Block;
63import jdk.nashorn.internal.ir.CatchNode;
64import jdk.nashorn.internal.ir.Expression;
65import jdk.nashorn.internal.ir.ForNode;
66import jdk.nashorn.internal.ir.FunctionNode;
67import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
68import jdk.nashorn.internal.ir.IdentNode;
69import jdk.nashorn.internal.ir.IndexNode;
70import jdk.nashorn.internal.ir.LexicalContext;
71import jdk.nashorn.internal.ir.LexicalContextNode;
72import jdk.nashorn.internal.ir.LiteralNode;
73import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
74import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
75import jdk.nashorn.internal.ir.Node;
76import jdk.nashorn.internal.ir.RuntimeNode;
77import jdk.nashorn.internal.ir.RuntimeNode.Request;
78import jdk.nashorn.internal.ir.SplitNode;
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.NodeOperatorVisitor;
87import jdk.nashorn.internal.ir.visitor.NodeVisitor;
88import jdk.nashorn.internal.runtime.Context;
89import jdk.nashorn.internal.runtime.Property;
90import jdk.nashorn.internal.runtime.PropertyMap;
91import jdk.nashorn.internal.runtime.logging.DebugLogger;
92import jdk.nashorn.internal.runtime.logging.Loggable;
93import 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")
104final class AssignSymbols extends NodeOperatorVisitor<LexicalContext> 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        if (!functionNode.usesReturnSymbol()) {
136            functionNode.compilerConstant(RETURN).setNeedsSlot(false);
137        }
138        // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol.
139        if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) {
140            final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName());
141            if(selfSymbol != null) {
142                if(selfSymbol.isFunctionSelf()) {
143                    selfSymbol.setNeedsSlot(false);
144                    selfSymbol.clearFlag(Symbol.IS_VAR);
145                }
146            } else {
147                assert functionNode.isProgram();
148            }
149        }
150        return functionNode;
151    }
152
153    private final Deque<Set<String>> thisProperties = new ArrayDeque<>();
154    private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
155    private final Compiler compiler;
156
157    public AssignSymbols(final Compiler compiler) {
158        super(new LexicalContext());
159        this.compiler = compiler;
160        this.log   = initLogger(compiler.getContext());
161        this.debug = log.isEnabled();
162    }
163
164    @Override
165    public DebugLogger getLogger() {
166        return log;
167    }
168
169    @Override
170    public DebugLogger initLogger(final Context context) {
171        return context.getLogger(this.getClass());
172    }
173
174    /**
175     * Define symbols for all variable declarations at the top of the function scope. This way we can get around
176     * problems like
177     *
178     * while (true) {
179     *   break;
180     *   if (true) {
181     *     var s;
182     *   }
183     * }
184     *
185     * to an arbitrary nesting depth.
186     *
187     * see NASHORN-73
188     *
189     * @param functionNode the FunctionNode we are entering
190     * @param body the body of the FunctionNode we are entering
191     */
192    private void acceptDeclarations(final FunctionNode functionNode, final Block body) {
193        // This visitor will assign symbol to all declared variables, except function declarations (which are taken care
194        // in a separate step above) and "var" declarations in for loop initializers.
195        //
196        body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
197            @Override
198            public boolean enterFunctionNode(final FunctionNode nestedFn) {
199                // Don't descend into nested functions
200                return false;
201            }
202
203            @Override
204            public Node leaveVarNode(final VarNode varNode) {
205                if (varNode.isStatement()) {
206                    final IdentNode ident  = varNode.getName();
207                    final Symbol    symbol = defineSymbol(body, ident.getName(), IS_VAR);
208                    functionNode.addDeclaredSymbol(symbol);
209                    if (varNode.isFunctionDeclaration()) {
210                        symbol.setIsFunctionDeclaration();
211                    }
212                    return varNode.setName(ident.setSymbol(symbol));
213                }
214                return varNode;
215            }
216        });
217    }
218
219    private IdentNode compilerConstantIdentifier(final CompilerConstants cc) {
220        return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc));
221    }
222
223    /**
224     * Creates an ident node for an implicit identifier within the function (one not declared in the script source
225     * code). These identifiers are defined with function's token and finish.
226     * @param name the name of the identifier
227     * @return an ident node representing the implicit identifier.
228     */
229    private IdentNode createImplicitIdentifier(final String name) {
230        final FunctionNode fn = lc.getCurrentFunction();
231        return new IdentNode(fn.getToken(), fn.getFinish(), name);
232    }
233
234    private Symbol createSymbol(final String name, final int flags) {
235        if ((flags & Symbol.KINDMASK) == IS_GLOBAL) {
236            //reuse global symbols so they can be hashed
237            Symbol global = globalSymbols.get(name);
238            if (global == null) {
239                global = new Symbol(name, flags);
240                globalSymbols.put(name, global);
241            }
242            return global;
243        }
244        return new Symbol(name, flags);
245    }
246
247    /**
248     * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically
249     * used to create assignmnent of {@code :callee} to the function name symbol in self-referential function
250     * expressions as well as for assignment of {@code :arguments} to {@code arguments}.
251     *
252     * @param name the ident node identifying the variable to initialize
253     * @param initConstant the compiler constant it is initialized to
254     * @param fn the function node the assignment is for
255     * @return a var node with the appropriate assignment
256     */
257    private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) {
258        final IdentNode init = compilerConstantIdentifier(initConstant);
259        assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal();
260
261        final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init);
262
263        final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName());
264        assert nameSymbol != null;
265
266        return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this);
267    }
268
269    private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) {
270        final List<VarNode> syntheticInitializers = new ArrayList<>(2);
271
272        // Must visit the new var nodes in the context of the body. We could also just set the new statements into the
273        // block and then revisit the entire block, but that seems to be too much double work.
274        final Block body = functionNode.getBody();
275        lc.push(body);
276        try {
277            if (functionNode.usesSelfSymbol()) {
278                // "var fn = :callee"
279                syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode));
280            }
281
282            if (functionNode.needsArguments()) {
283                // "var arguments = :arguments"
284                syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()),
285                        ARGUMENTS, functionNode));
286            }
287
288            if (syntheticInitializers.isEmpty()) {
289                return functionNode;
290            }
291
292            for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) {
293                it.set((VarNode)it.next().accept(this));
294            }
295        } finally {
296            lc.pop(body);
297        }
298
299        final List<Statement> stmts = body.getStatements();
300        final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size());
301        newStatements.addAll(syntheticInitializers);
302        newStatements.addAll(stmts);
303        return functionNode.setBody(lc, body.setStatements(lc, newStatements));
304    }
305
306    private Symbol defineGlobalSymbol(final Block block, final String name) {
307        return defineSymbol(block, name, IS_GLOBAL);
308    }
309
310    /**
311     * Defines a new symbol in the given block.
312     *
313     * @param block        the block in which to define the symbol
314     * @param name         name of symbol.
315     * @param symbolFlags  Symbol flags.
316     *
317     * @return Symbol for given name or null for redefinition.
318     */
319    private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) {
320        int    flags  = symbolFlags;
321        Symbol symbol = findSymbol(block, name); // Locate symbol.
322        final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL;
323
324        // Global variables are implicitly always scope variables too.
325        if (isGlobal) {
326            flags |= IS_SCOPE;
327        }
328
329        if (lc.getCurrentFunction().isProgram()) {
330            flags |= IS_PROGRAM_LEVEL;
331        }
332
333        final boolean isParam = (flags & KINDMASK) == IS_PARAM;
334        final boolean isVar =   (flags & KINDMASK) == IS_VAR;
335
336        final FunctionNode function = lc.getFunction(block);
337        if (symbol != null) {
338            // Symbol was already defined. Check if it needs to be redefined.
339            if (isParam) {
340                if (!isLocal(function, symbol)) {
341                    // Not defined in this function. Create a new definition.
342                    symbol = null;
343                } else if (symbol.isParam()) {
344                    // Duplicate parameter. Null return will force an error.
345                    throw new AssertionError("duplicate parameter");
346                }
347            } else if (isVar) {
348                if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) {
349                    // Always create a new definition.
350                    symbol = null;
351                } else {
352                    // Not defined in this function. Create a new definition.
353                    if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
354                        symbol = null;
355                    }
356                }
357            }
358        }
359
360        if (symbol == null) {
361            // If not found, then create a new one.
362            Block symbolBlock;
363
364            // Determine where to create it.
365            if (isVar && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) {
366                symbolBlock = block; //internal vars are always defined in the block closest to them
367            } else if (isGlobal) {
368                symbolBlock = lc.getOutermostFunction().getBody();
369            } else {
370                symbolBlock = lc.getFunctionBody(function);
371            }
372
373            // Create and add to appropriate block.
374            symbol = createSymbol(name, flags);
375            symbolBlock.putSymbol(lc, symbol);
376
377            if ((flags & IS_SCOPE) == 0) {
378                // Initial assumption; symbol can lose its slot later
379                symbol.setNeedsSlot(true);
380            }
381        } else if (symbol.less(flags)) {
382            symbol.setFlags(flags);
383        }
384
385        return symbol;
386    }
387
388    private <T extends Node> T end(final T node) {
389        return end(node, true);
390    }
391
392    private <T extends Node> T end(final T node, final boolean printNode) {
393        if (debug) {
394            final StringBuilder sb = new StringBuilder();
395
396            sb.append("[LEAVE ").
397                append(name(node)).
398                append("] ").
399                append(printNode ? node.toString() : "").
400                append(" in '").
401                append(lc.getCurrentFunction().getName()).
402                append('\'');
403
404            if (node instanceof IdentNode) {
405                final Symbol symbol = ((IdentNode)node).getSymbol();
406                if (symbol == null) {
407                    sb.append(" <NO SYMBOL>");
408                } else {
409                    sb.append(" <symbol=").append(symbol).append('>');
410                }
411            }
412
413            log.unindent();
414            log.info(sb);
415        }
416
417        return node;
418    }
419
420    @Override
421    public boolean enterBlock(final Block block) {
422        start(block);
423        block.clearSymbols();
424
425        if (lc.isFunctionBody()) {
426            enterFunctionBody();
427        }
428
429        return true;
430    }
431
432    @Override
433    public boolean enterCatchNode(final CatchNode catchNode) {
434        final IdentNode exception = catchNode.getException();
435        final Block     block     = lc.getCurrentBlock();
436
437        start(catchNode);
438
439        // define block-local exception variable
440        final String exname = exception.getName();
441        // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its
442        // symbol is naturally internal, and should be treated as such.
443        final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName());
444        defineSymbol(block, exname, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE);
445
446        return true;
447    }
448
449    private void enterFunctionBody() {
450        final FunctionNode functionNode = lc.getCurrentFunction();
451        final Block body = lc.getCurrentBlock();
452
453        initFunctionWideVariables(functionNode, body);
454
455        if (functionNode.isProgram()) {
456            initGlobalSymbols(body);
457        } else if (!functionNode.isDeclared() && !functionNode.isAnonymous()) {
458            // It's neither declared nor program - it's a function expression then; assign it a self-symbol unless it's
459            // anonymous.
460            final String name = functionNode.getIdent().getName();
461            assert name != null;
462            assert body.getExistingSymbol(name) == null;
463            defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE);
464            if(functionNode.allVarsInScope()) { // basically, has deep eval
465                lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
466            }
467        }
468
469        acceptDeclarations(functionNode, body);
470    }
471
472    @Override
473    public boolean enterFunctionNode(final FunctionNode functionNode) {
474        // TODO: once we have information on symbols used by nested functions, we can stop descending into nested
475        // functions with on-demand compilation, e.g. add
476        // if(!thisProperties.isEmpty() && env.isOnDemandCompilation()) {
477        //    return false;
478        // }
479        start(functionNode, false);
480
481        thisProperties.push(new HashSet<String>());
482
483        //an outermost function in our lexical context that is not a program
484        //is possible - it is a function being compiled lazily
485        if (functionNode.isDeclared()) {
486            final Iterator<Block> blocks = lc.getBlocks();
487            if (blocks.hasNext()) {
488                defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR | (functionNode.isAnonymous()? IS_INTERNAL : 0));
489            }
490        }
491
492        return true;
493    }
494
495    @Override
496    public boolean enterVarNode(final VarNode varNode) {
497        start(varNode);
498        defineSymbol(lc.getCurrentBlock(), varNode.getName().getName(), IS_VAR | (lc.getCurrentFunction().isProgram() ? IS_SCOPE : 0));
499        return true;
500    }
501
502    private Symbol exceptionSymbol() {
503        return newObjectInternal(EXCEPTION_PREFIX);
504    }
505
506    /**
507     * This has to run before fix assignment types, store any type specializations for
508     * paramters, then turn then to objects for the generic version of this method
509     *
510     * @param functionNode functionNode
511     */
512    private FunctionNode finalizeParameters(final FunctionNode functionNode) {
513        final List<IdentNode> newParams = new ArrayList<>();
514        final boolean isVarArg = functionNode.isVarArg();
515
516        final Block body = functionNode.getBody();
517        for (final IdentNode param : functionNode.getParameters()) {
518            final Symbol paramSymbol = body.getExistingSymbol(param.getName());
519            assert paramSymbol != null;
520            assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags();
521            newParams.add(param.setSymbol(paramSymbol));
522
523            // parameters should not be slots for a function that uses variable arity signature
524            if (isVarArg) {
525                paramSymbol.setNeedsSlot(false);
526            }
527        }
528
529        return functionNode.setParameters(lc, newParams);
530    }
531
532    /**
533     * Search for symbol in the lexical context starting from the given block.
534     * @param name Symbol name.
535     * @return Found symbol or null if not found.
536     */
537    private Symbol findSymbol(final Block block, final String name) {
538        for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) {
539            final Symbol symbol = blocks.next().getExistingSymbol(name);
540            if (symbol != null) {
541                return symbol;
542            }
543        }
544        return null;
545    }
546
547    /**
548     * Marks the current function as one using any global symbol. The function and all its parent functions will all be
549     * marked as needing parent scope.
550     * @see FunctionNode#needsParentScope()
551     */
552    private void functionUsesGlobalSymbol() {
553        for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) {
554            lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
555        }
556    }
557
558    /**
559     * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing
560     * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all
561     * functions up to (but not including) the function containing the defining block will be marked as needing parent
562     * function scope.
563     * @see FunctionNode#needsParentScope()
564     */
565    private void functionUsesScopeSymbol(final Symbol symbol) {
566        final String name = symbol.getName();
567        for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) {
568            final LexicalContextNode node = contextNodeIter.next();
569            if (node instanceof Block) {
570                final Block block = (Block)node;
571                if (block.getExistingSymbol(name) != null) {
572                    assert lc.contains(block);
573                    lc.setBlockNeedsScope(block);
574                    break;
575                }
576            } else if (node instanceof FunctionNode) {
577                lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
578            }
579        }
580    }
581
582    /**
583     * Declares that the current function is using the symbol.
584     * @param symbol the symbol used by the current function.
585     */
586    private void functionUsesSymbol(final Symbol symbol) {
587        assert symbol != null;
588        if (symbol.isScope()) {
589            if (symbol.isGlobal()) {
590                functionUsesGlobalSymbol();
591            } else {
592                functionUsesScopeSymbol(symbol);
593            }
594        } else {
595            assert !symbol.isGlobal(); // Every global is also scope
596        }
597    }
598
599    private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) {
600        defineSymbol(block, cc.symbolName(), flags).setNeedsSlot(true);
601    }
602
603    private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) {
604        initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
605        initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE);
606
607        if (functionNode.isVarArg()) {
608            initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
609            if (functionNode.needsArguments()) {
610                initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
611                defineSymbol(body, ARGUMENTS_VAR.symbolName(), IS_VAR | HAS_OBJECT_VALUE);
612            }
613        }
614
615        initParameters(functionNode, body);
616        initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
617        initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL);
618    }
619
620
621    /**
622     * Move any properties from the global map into the scope of this function (which must be a program function).
623     * @param block the function node body for which to init scope vars
624     */
625    private void initGlobalSymbols(final Block block) {
626        final PropertyMap map = Context.getGlobalMap();
627
628        for (final Property property : map.getProperties()) {
629            final Symbol symbol = defineGlobalSymbol(block, property.getKey());
630            log.info("Added global symbol from property map ", symbol);
631        }
632    }
633
634    /**
635     * Initialize parameters for function node.
636     * @param functionNode the function node
637     */
638    private void initParameters(final FunctionNode functionNode, final Block body) {
639        final boolean isVarArg = functionNode.isVarArg();
640        final boolean scopeParams = functionNode.allVarsInScope() || isVarArg;
641        for (final IdentNode param : functionNode.getParameters()) {
642            final Symbol symbol = defineSymbol(body, param.getName(), IS_PARAM);
643            if(scopeParams) {
644                // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored.
645                // It will force creation of scopes where they would otherwise not necessarily be needed (functions
646                // using arguments object and other variable arity functions). Tracked by JDK-8038942.
647                symbol.setIsScope();
648                assert symbol.hasSlot();
649                if(isVarArg) {
650                    symbol.setNeedsSlot(false);
651                }
652            }
653        }
654    }
655
656    /**
657     * Is the symbol local to (that is, defined in) the specified function?
658     * @param function the function
659     * @param symbol the symbol
660     * @return true if the symbol is defined in the specified function
661     */
662    private boolean isLocal(final FunctionNode function, final Symbol symbol) {
663        final FunctionNode definingFn = lc.getDefiningFunction(symbol);
664        assert definingFn != null;
665        return definingFn == function;
666    }
667
668    @Override
669    public Node leaveASSIGN(final BinaryNode binaryNode) {
670        // If we're assigning a property of the this object ("this.foo = ..."), record it.
671
672        final Expression lhs = binaryNode.lhs();
673        if (lhs instanceof AccessNode) {
674            final AccessNode accessNode = (AccessNode) lhs;
675            final Expression base = accessNode.getBase();
676            if (base instanceof IdentNode) {
677                final Symbol symbol = ((IdentNode)base).getSymbol();
678                if(symbol.isThis()) {
679                    thisProperties.peek().add(accessNode.getProperty());
680                }
681            }
682        }
683        return binaryNode;
684    }
685
686    @Override
687    public Node leaveBlock(final Block block) {
688        // It's not necessary to guard the marking of symbols as locals with this "if"condition for correctness, it's
689        // just an optimization -- runtime type calculation is not used when the compilation is not an on-demand
690        // optimistic compilation, so we can skip locals marking then.
691        if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) {
692            for (final Symbol symbol: block.getSymbols()) {
693                if (!symbol.isScope()) {
694                    assert symbol.isVar() || symbol.isParam();
695                    compiler.declareLocalSymbol(symbol.getName());
696                }
697            }
698        }
699        return block;
700    }
701
702    @Override
703    public Node leaveDELETE(final UnaryNode unaryNode) {
704        final FunctionNode currentFunctionNode = lc.getCurrentFunction();
705        final boolean      strictMode          = currentFunctionNode.isStrict();
706        final Expression   rhs                 = unaryNode.getExpression();
707        final Expression   strictFlagNode      = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this);
708
709        Request request = Request.DELETE;
710        final List<Expression> args = new ArrayList<>();
711
712        if (rhs instanceof IdentNode) {
713            final IdentNode ident = (IdentNode)rhs;
714            // If this is a declared variable or a function parameter, delete always fails (except for globals).
715            final String name = ident.getName();
716            final Symbol symbol = ident.getSymbol();
717            final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
718
719            if (failDelete && symbol.isThis()) {
720                return LiteralNode.newInstance(unaryNode, true).accept(this);
721            }
722            final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
723
724            if (!failDelete) {
725                args.add(compilerConstantIdentifier(SCOPE));
726            }
727            args.add(literalNode);
728            args.add(strictFlagNode);
729
730            if (failDelete) {
731                request = Request.FAIL_DELETE;
732            }
733        } else if (rhs instanceof AccessNode) {
734            final Expression base     = ((AccessNode)rhs).getBase();
735            final String     property = ((AccessNode)rhs).getProperty();
736
737            args.add(base);
738            args.add((Expression)LiteralNode.newInstance(unaryNode, property).accept(this));
739            args.add(strictFlagNode);
740
741        } else if (rhs instanceof IndexNode) {
742            final IndexNode indexNode = (IndexNode)rhs;
743            final Expression base  = indexNode.getBase();
744            final Expression index = indexNode.getIndex();
745
746            args.add(base);
747            args.add(index);
748            args.add(strictFlagNode);
749
750        } else {
751            return LiteralNode.newInstance(unaryNode, true).accept(this);
752        }
753        return new RuntimeNode(unaryNode, request, args).accept(this);
754    }
755
756    @Override
757    public Node leaveForNode(final ForNode forNode) {
758        if (forNode.isForIn()) {
759            forNode.setIterator(newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
760        }
761
762        return end(forNode);
763    }
764
765    @Override
766    public Node leaveFunctionNode(final FunctionNode functionNode) {
767
768        return markProgramBlock(
769               removeUnusedSlots(
770               createSyntheticInitializers(
771               finalizeParameters(
772                       lc.applyTopFlags(functionNode))))
773                       .setThisProperties(lc, thisProperties.pop().size())
774                       .setState(lc, CompilationState.SYMBOLS_ASSIGNED));
775    }
776
777    @Override
778    public Node leaveIdentNode(final IdentNode identNode) {
779        final String name = identNode.getName();
780
781        if (identNode.isPropertyName()) {
782            return identNode;
783        }
784
785        final Block block = lc.getCurrentBlock();
786
787        Symbol symbol = findSymbol(block, name);
788
789        //If an existing symbol with the name is found, use that otherwise, declare a new one
790        if (symbol != null) {
791            log.info("Existing symbol = ", symbol);
792            if (symbol.isFunctionSelf()) {
793                final FunctionNode functionNode = lc.getDefiningFunction(symbol);
794                assert functionNode != null;
795                assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null;
796                lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
797            }
798
799            // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already)
800            maybeForceScope(symbol);
801        } else {
802            log.info("No symbol exists. Declare as global: ", symbol);
803            symbol = defineGlobalSymbol(block, name);
804            Symbol.setSymbolIsScope(lc, symbol);
805        }
806
807        functionUsesSymbol(symbol);
808
809        if (!identNode.isInitializedHere()) {
810            symbol.increaseUseCount();
811        }
812
813        return end(identNode.setSymbol(symbol));
814    }
815
816    @Override
817    public Node leaveSwitchNode(final SwitchNode switchNode) {
818        // We only need a symbol for the tag if it's not an integer switch node
819        if(!switchNode.isInteger()) {
820            switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX));
821        }
822        return switchNode;
823    }
824
825    @Override
826    public Node leaveTryNode(final TryNode tryNode) {
827        tryNode.setException(exceptionSymbol());
828        if (tryNode.getFinallyBody() != null) {
829            tryNode.setFinallyCatchAll(exceptionSymbol());
830        }
831
832        end(tryNode);
833
834        return tryNode;
835    }
836
837    @Override
838    public Node leaveTYPEOF(final UnaryNode unaryNode) {
839        final Expression rhs = unaryNode.getExpression();
840
841        final List<Expression> args = new ArrayList<>();
842        if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) {
843            args.add(compilerConstantIdentifier(SCOPE));
844            args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
845        } else {
846            args.add(rhs);
847            args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
848        }
849
850        final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this);
851
852        end(unaryNode);
853
854        return runtimeNode;
855    }
856
857    private FunctionNode markProgramBlock(final FunctionNode functionNode) {
858        if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) {
859            return functionNode;
860        }
861
862        assert functionNode.getId() == 1;
863        return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE));
864    }
865
866    /**
867     * If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is
868     * promoted to a scope symbol and its block marked as needing a scope.
869     * @param symbol the symbol that might be scoped
870     */
871    private void maybeForceScope(final Symbol symbol) {
872        if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) {
873            Symbol.setSymbolIsScope(lc, symbol);
874        }
875    }
876
877    private Symbol newInternal(final CompilerConstants cc, final int flags) {
878        return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), IS_VAR | IS_INTERNAL | flags); //NASHORN-73
879    }
880
881    private Symbol newObjectInternal(final CompilerConstants cc) {
882        return newInternal(cc, HAS_OBJECT_VALUE);
883    }
884
885    private boolean start(final Node node) {
886        return start(node, true);
887    }
888
889    private boolean start(final Node node, final boolean printNode) {
890        if (debug) {
891            final StringBuilder sb = new StringBuilder();
892
893            sb.append("[ENTER ").
894                append(name(node)).
895                append("] ").
896                append(printNode ? node.toString() : "").
897                append(" in '").
898                append(lc.getCurrentFunction().getName()).
899                append("'");
900            log.info(sb);
901            log.indent();
902        }
903
904        return true;
905    }
906
907    /**
908     * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only
909     * be reached from the current block by traversing a function node, a split node, or a with node.
910     * @param symbol the symbol checked for needing to be a scope symbol
911     * @return true if the symbol has to be a scope symbol.
912     */
913    private boolean symbolNeedsToBeScope(final Symbol symbol) {
914        if (symbol.isThis() || symbol.isInternal()) {
915            return false;
916        }
917
918        if (lc.getCurrentFunction().allVarsInScope()) {
919            return true;
920        }
921
922        boolean previousWasBlock = false;
923        for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
924            final LexicalContextNode node = it.next();
925            if (node instanceof FunctionNode || node instanceof SplitNode || isSplitArray(node)) {
926                // We reached the function boundary or a splitting boundary without seeing a definition for the symbol.
927                // It needs to be in scope.
928                return true;
929            } else if (node instanceof WithNode) {
930                if (previousWasBlock) {
931                    // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately
932                    // preceded by a block, this means we're currently processing its expression, not its body,
933                    // therefore it doesn't count.
934                    return true;
935                }
936                previousWasBlock = false;
937            } else if (node instanceof Block) {
938                if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) {
939                    // We reached the block that defines the symbol without reaching either the function boundary, or a
940                    // WithNode. The symbol need not be scoped.
941                    return false;
942                }
943                previousWasBlock = true;
944            } else {
945                previousWasBlock = false;
946            }
947        }
948        throw new AssertionError();
949    }
950
951    private static boolean isSplitArray(final LexicalContextNode expr) {
952        if(!(expr instanceof ArrayLiteralNode)) {
953            return false;
954        }
955        final List<ArrayUnit> units = ((ArrayLiteralNode)expr).getUnits();
956        return !(units == null || units.isEmpty());
957    }
958}
959