/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.codegen;

import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR;
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
import static jdk.nashorn.internal.ir.Symbol.HAS_OBJECT_VALUE;
import static jdk.nashorn.internal.ir.Symbol.IS_CONST;
import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF;
import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
import static jdk.nashorn.internal.ir.Symbol.IS_LET;
import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
import static jdk.nashorn.internal.ir.Symbol.IS_PROGRAM_LEVEL;
import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
import static jdk.nashorn.internal.ir.Symbol.IS_THIS;
import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
import static jdk.nashorn.internal.ir.Symbol.KINDMASK;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.CatchNode;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.TryNode;
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.WithNode;
import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
import jdk.nashorn.internal.parser.TokenType;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ECMAErrors;
import jdk.nashorn.internal.runtime.ErrorManager;
import jdk.nashorn.internal.runtime.JSErrorType;
import jdk.nashorn.internal.runtime.ParserException;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;

This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate visitor.
/** * This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only * possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime * nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable * for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types * during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate * visitor. */
@Logger(name="symbols") final class AssignSymbols extends SimpleNodeVisitor implements Loggable { private final DebugLogger log; private final boolean debug; private static boolean isParamOrVar(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); return symbol.isParam() || symbol.isVar(); } private static String name(final Node node) { final String cn = node.getClass().getName(); final int lastDot = cn.lastIndexOf('.'); if (lastDot == -1) { return cn; } return cn.substring(lastDot + 1); }
Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not needing a slot after all.
Params:
  • functionNode – the function node
Returns:the passed in node, for easy chaining
/** * Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not * needing a slot after all. * @param functionNode the function node * @return the passed in node, for easy chaining */
private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) { if (!functionNode.needsCallee()) { functionNode.compilerConstant(CALLEE).setNeedsSlot(false); } if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) { functionNode.compilerConstant(SCOPE).setNeedsSlot(false); } // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol. if(functionNode.isNamedFunctionExpression() && !functionNode.usesSelfSymbol()) { final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName()); if(selfSymbol != null && selfSymbol.isFunctionSelf()) { selfSymbol.setNeedsSlot(false); selfSymbol.clearFlag(Symbol.IS_VAR); } } return functionNode; } private final Deque<Set<String>> thisProperties = new ArrayDeque<>(); private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol private final Compiler compiler; private final boolean isOnDemand; public AssignSymbols(final Compiler compiler) { this.compiler = compiler; this.log = initLogger(compiler.getContext()); this.debug = log.isEnabled(); this.isOnDemand = compiler.isOnDemandCompilation(); } @Override public DebugLogger getLogger() { return log; } @Override public DebugLogger initLogger(final Context context) { return context.getLogger(this.getClass()); }
Define symbols for all variable declarations at the top of the function scope. This way we can get around problems like while (true) { break; if (true) { var s; } } to an arbitrary nesting depth. see NASHORN-73
Params:
  • functionNode – the FunctionNode we are entering
  • body – the body of the FunctionNode we are entering
/** * Define symbols for all variable declarations at the top of the function scope. This way we can get around * problems like * * while (true) { * break; * if (true) { * var s; * } * } * * to an arbitrary nesting depth. * * see NASHORN-73 * * @param functionNode the FunctionNode we are entering * @param body the body of the FunctionNode we are entering */
private void acceptDeclarations(final FunctionNode functionNode, final Block body) { // This visitor will assign symbol to all declared variables. body.accept(new SimpleNodeVisitor() { @Override protected boolean enterDefault(final Node node) { // Don't bother visiting expressions; var is a statement, it can't be inside an expression. // This will also prevent visiting nested functions (as FunctionNode is an expression). return !(node instanceof Expression); } @Override public Node leaveVarNode(final VarNode varNode) { final IdentNode ident = varNode.getName(); final boolean blockScoped = varNode.isBlockScoped(); if (blockScoped && lc.inUnprotectedSwitchContext()) { throwUnprotectedSwitchError(varNode); } final Block block = blockScoped ? lc.getCurrentBlock() : body; final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags()); if (varNode.isFunctionDeclaration()) { symbol.setIsFunctionDeclaration(); } return varNode.setName(ident.setSymbol(symbol)); } }); } private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); }
Creates an ident node for an implicit identifier within the function (one not declared in the script source code). These identifiers are defined with function's token and finish.
Params:
  • name – the name of the identifier
Returns:an ident node representing the implicit identifier.
/** * Creates an ident node for an implicit identifier within the function (one not declared in the script source * code). These identifiers are defined with function's token and finish. * @param name the name of the identifier * @return an ident node representing the implicit identifier. */
private IdentNode createImplicitIdentifier(final String name) { final FunctionNode fn = lc.getCurrentFunction(); return new IdentNode(fn.getToken(), fn.getFinish(), name); } private Symbol createSymbol(final String name, final int flags) { if ((flags & Symbol.KINDMASK) == IS_GLOBAL) { //reuse global symbols so they can be hashed Symbol global = globalSymbols.get(name); if (global == null) { global = new Symbol(name, flags); globalSymbols.put(name, global); } return global; } return new Symbol(name, flags); }
Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically used to create assignment of :callee to the function name symbol in self-referential function expressions as well as for assignment of :arguments to arguments.
Params:
  • name – the ident node identifying the variable to initialize
  • initConstant – the compiler constant it is initialized to
  • fn – the function node the assignment is for
Returns:a var node with the appropriate assignment
/** * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically * used to create assignment of {@code :callee} to the function name symbol in self-referential function * expressions as well as for assignment of {@code :arguments} to {@code arguments}. * * @param name the ident node identifying the variable to initialize * @param initConstant the compiler constant it is initialized to * @param fn the function node the assignment is for * @return a var node with the appropriate assignment */
private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) { final IdentNode init = compilerConstantIdentifier(initConstant); assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal(); final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); assert nameSymbol != null; return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); } private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { final List<VarNode> syntheticInitializers = new ArrayList<>(2); // Must visit the new var nodes in the context of the body. We could also just set the new statements into the // block and then revisit the entire block, but that seems to be too much double work. final Block body = functionNode.getBody(); lc.push(body); try { if (functionNode.usesSelfSymbol()) { // "var fn = :callee" syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode)); } if (functionNode.needsArguments()) { // "var arguments = :arguments" syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), ARGUMENTS, functionNode)); } if (syntheticInitializers.isEmpty()) { return functionNode; } for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) { it.set((VarNode)it.next().accept(this)); } } finally { lc.pop(body); } final List<Statement> stmts = body.getStatements(); final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); newStatements.addAll(syntheticInitializers); newStatements.addAll(stmts); return functionNode.setBody(lc, body.setStatements(lc, newStatements)); }
Defines a new symbol in the given block.
Params:
  • block – the block in which to define the symbol
  • name – name of symbol.
  • origin – origin node
  • symbolFlags – Symbol flags.
Returns:Symbol for given name or null for redefinition.
/** * Defines a new symbol in the given block. * * @param block the block in which to define the symbol * @param name name of symbol. * @param origin origin node * @param symbolFlags Symbol flags. * * @return Symbol for given name or null for redefinition. */
private Symbol defineSymbol(final Block block, final String name, final Node origin, final int symbolFlags) { int flags = symbolFlags; final boolean isBlockScope = (flags & IS_LET) != 0 || (flags & IS_CONST) != 0; final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL; Symbol symbol; final FunctionNode function; if (isBlockScope) { // block scoped variables always live in current block, no need to look for existing symbols in parent blocks. symbol = block.getExistingSymbol(name); function = lc.getCurrentFunction(); } else { symbol = findSymbol(block, name); function = lc.getFunction(block); } // Global variables are implicitly always scope variables too. if (isGlobal) { flags |= IS_SCOPE; } if (lc.getCurrentFunction().isProgram()) { flags |= IS_PROGRAM_LEVEL; } final boolean isParam = (flags & KINDMASK) == IS_PARAM; final boolean isVar = (flags & KINDMASK) == IS_VAR; if (symbol != null) { // Symbol was already defined. Check if it needs to be redefined. if (isParam) { if (!isLocal(function, symbol)) { // Not defined in this function. Create a new definition. symbol = null; } else if (symbol.isParam()) { // Duplicate parameter. Null return will force an error. throwParserException(ECMAErrors.getMessage("syntax.error.duplicate.parameter", name), origin); } } else if (isVar) { if (isBlockScope) { // Check redeclaration in same block if (symbol.hasBeenDeclared()) { throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin); } else { symbol.setHasBeenDeclared(); // Set scope flag on top-level block scoped symbols if (function.isProgram() && function.getBody() == block) { symbol.setIsScope(); } } } else if ((flags & IS_INTERNAL) != 0) { // Always create a new definition. symbol = null; } else { // Found LET or CONST in parent scope of same function - s SyntaxError if (symbol.isBlockScoped() && isLocal(lc.getCurrentFunction(), symbol)) { throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin); } // Not defined in this function. Create a new definition. if (!isLocal(function, symbol) || symbol.less(IS_VAR)) { symbol = null; } } } } if (symbol == null) { // If not found, then create a new one. final Block symbolBlock; // Determine where to create it. if (isVar && ((flags & IS_INTERNAL) != 0 || isBlockScope)) { symbolBlock = block; //internal vars are always defined in the block closest to them } else if (isGlobal) { symbolBlock = lc.getOutermostFunction().getBody(); } else { symbolBlock = lc.getFunctionBody(function); } // Create and add to appropriate block. symbol = createSymbol(name, flags); symbolBlock.putSymbol(symbol); if ((flags & IS_SCOPE) == 0) { // Initial assumption; symbol can lose its slot later symbol.setNeedsSlot(true); } } else if (symbol.less(flags)) { symbol.setFlags(flags); } return symbol; } private <T extends Node> T end(final T node) { return end(node, true); } private <T extends Node> T end(final T node, final boolean printNode) { if (debug) { final StringBuilder sb = new StringBuilder(); sb.append("[LEAVE "). append(name(node)). append("] "). append(printNode ? node.toString() : ""). append(" in '"). append(lc.getCurrentFunction().getName()). append('\''); if (node instanceof IdentNode) { final Symbol symbol = ((IdentNode)node).getSymbol(); if (symbol == null) { sb.append(" <NO SYMBOL>"); } else { sb.append(" <symbol=").append(symbol).append('>'); } } log.unindent(); log.info(sb); } return node; } @Override public boolean enterBlock(final Block block) { start(block); if (lc.isFunctionBody()) { assert !block.hasSymbols(); final FunctionNode fn = lc.getCurrentFunction(); if (isUnparsedFunction(fn)) { // It's a skipped nested function. Just mark the symbols being used by it as being in use. for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) { nameIsUsed(name, null); } // Don't bother descending into it, it must be empty anyway. assert block.getStatements().isEmpty(); return false; } enterFunctionBody(); } return true; } private boolean isUnparsedFunction(final FunctionNode fn) { return isOnDemand && fn != lc.getOutermostFunction(); } @Override public boolean enterCatchNode(final CatchNode catchNode) { final IdentNode exception = catchNode.getExceptionIdentifier(); final Block block = lc.getCurrentBlock(); start(catchNode); // define block-local exception variable final String exname = exception.getName(); // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its // symbol is naturally internal, and should be treated as such. final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName()); // IS_LET flag is required to make sure symbol is not visible outside catch block. However, we need to // clear the IS_LET flag after creation to allow redefinition of symbol inside the catch block. final Symbol symbol = defineSymbol(block, exname, catchNode, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE); symbol.clearFlag(IS_LET); return true; } private void enterFunctionBody() { final FunctionNode functionNode = lc.getCurrentFunction(); final Block body = lc.getCurrentBlock(); initFunctionWideVariables(functionNode, body); acceptDeclarations(functionNode, body); defineFunctionSelfSymbol(functionNode, body); } private void defineFunctionSelfSymbol(final FunctionNode functionNode, final Block body) { // Function self-symbol is only declared as a local variable for named function expressions. Declared functions // don't need it as they are local variables in their declaring scope. if (!functionNode.isNamedFunctionExpression()) { return; } final String name = functionNode.getIdent().getName(); assert name != null; // As it's a named function expression. if (body.getExistingSymbol(name) != null) { // Body already has a declaration for the name. It's either a parameter "function x(x)" or a // top-level variable "function x() { ... var x; ... }". return; } defineSymbol(body, name, functionNode, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE); if(functionNode.allVarsInScope()) { // basically, has deep eval // We must conservatively presume that eval'd code can dynamically use the function symbol. lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); } } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { start(functionNode, false); thisProperties.push(new HashSet<String>()); // Every function has a body, even the ones skipped on reparse (they have an empty one). We're // asserting this as even for those, enterBlock() must be invoked to correctly process symbols that // are used in them. assert functionNode.getBody() != null; return true; } @Override public boolean enterVarNode(final VarNode varNode) { start(varNode); // Normally, a symbol assigned in a var statement is not live for its RHS. Since we also represent function // declarations as VarNodes, they are exception to the rule, as they need to have the symbol visible to the // body of the declared function for self-reference. if (varNode.isFunctionDeclaration()) { defineVarIdent(varNode); } return true; } @Override public Node leaveVarNode(final VarNode varNode) { if (!varNode.isFunctionDeclaration()) { defineVarIdent(varNode); } return super.leaveVarNode(varNode); } private void defineVarIdent(final VarNode varNode) { final IdentNode ident = varNode.getName(); final int flags; if (!varNode.isBlockScoped() && lc.getCurrentFunction().isProgram()) { flags = IS_SCOPE; } else { flags = 0; } defineSymbol(lc.getCurrentBlock(), ident.getName(), ident, varNode.getSymbolFlags() | flags); } private Symbol exceptionSymbol() { return newObjectInternal(EXCEPTION_PREFIX); }
This has to run before fix assignment types, store any type specializations for parameters, then turn them into objects for the generic version of this method.
Params:
  • functionNode – functionNode
/** * This has to run before fix assignment types, store any type specializations for * parameters, then turn them into objects for the generic version of this method. * * @param functionNode functionNode */
private FunctionNode finalizeParameters(final FunctionNode functionNode) { final List<IdentNode> newParams = new ArrayList<>(); final boolean isVarArg = functionNode.isVarArg(); final Block body = functionNode.getBody(); for (final IdentNode param : functionNode.getParameters()) { final Symbol paramSymbol = body.getExistingSymbol(param.getName()); assert paramSymbol != null; assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); newParams.add(param.setSymbol(paramSymbol)); // parameters should not be slots for a function that uses variable arity signature if (isVarArg) { paramSymbol.setNeedsSlot(false); } } return functionNode.setParameters(lc, newParams); }
Search for symbol in the lexical context starting from the given block.
Params:
  • name – Symbol name.
Returns:Found symbol or null if not found.
/** * Search for symbol in the lexical context starting from the given block. * @param name Symbol name. * @return Found symbol or null if not found. */
private Symbol findSymbol(final Block block, final String name) { for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { final Symbol symbol = blocks.next().getExistingSymbol(name); if (symbol != null) { return symbol; } } return null; }
Marks the current function as one using any global symbol. The function and all its parent functions will all be marked as needing parent scope.
See Also:
  • needsParentScope.needsParentScope()
/** * Marks the current function as one using any global symbol. The function and all its parent functions will all be * marked as needing parent scope. * @see FunctionNode#needsParentScope() */
private void functionUsesGlobalSymbol() { for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) { lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); } }
Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing its own scope to hold the variable. If the symbol is defined outside of the current function, it and all functions up to (but not including) the function containing the defining block will be marked as needing parent function scope.
See Also:
  • needsParentScope.needsParentScope()
/** * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all * functions up to (but not including) the function containing the defining block will be marked as needing parent * function scope. * @see FunctionNode#needsParentScope() */
private void functionUsesScopeSymbol(final Symbol symbol) { final String name = symbol.getName(); for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { final LexicalContextNode node = contextNodeIter.next(); if (node instanceof Block) { final Block block = (Block)node; if (block.getExistingSymbol(name) != null) { assert lc.contains(block); lc.setBlockNeedsScope(block); break; } } else if (node instanceof FunctionNode) { lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); } } }
Declares that the current function is using the symbol.
Params:
  • symbol – the symbol used by the current function.
/** * Declares that the current function is using the symbol. * @param symbol the symbol used by the current function. */
private void functionUsesSymbol(final Symbol symbol) { assert symbol != null; if (symbol.isScope()) { if (symbol.isGlobal()) { functionUsesGlobalSymbol(); } else { functionUsesScopeSymbol(symbol); } } else { assert !symbol.isGlobal(); // Every global is also scope } } private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { defineSymbol(block, cc.symbolName(), null, flags).setNeedsSlot(true); } private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE); if (functionNode.isVarArg()) { initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); if (functionNode.needsArguments()) { initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); defineSymbol(body, ARGUMENTS_VAR.symbolName(), null, IS_VAR | HAS_OBJECT_VALUE); } } initParameters(functionNode, body); initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL); }
Initialize parameters for function node.
Params:
  • functionNode – the function node
/** * Initialize parameters for function node. * @param functionNode the function node */
private void initParameters(final FunctionNode functionNode, final Block body) { final boolean isVarArg = functionNode.isVarArg(); final boolean scopeParams = functionNode.allVarsInScope() || isVarArg; for (final IdentNode param : functionNode.getParameters()) { final Symbol symbol = defineSymbol(body, param.getName(), param, IS_PARAM); if(scopeParams) { // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored. // It will force creation of scopes where they would otherwise not necessarily be needed (functions // using arguments object and other variable arity functions). Tracked by JDK-8038942. symbol.setIsScope(); assert symbol.hasSlot(); if(isVarArg) { symbol.setNeedsSlot(false); } } } }
Is the symbol local to (that is, defined in) the specified function?
Params:
  • function – the function
  • symbol – the symbol
Returns:true if the symbol is defined in the specified function
/** * Is the symbol local to (that is, defined in) the specified function? * @param function the function * @param symbol the symbol * @return true if the symbol is defined in the specified function */
private boolean isLocal(final FunctionNode function, final Symbol symbol) { final FunctionNode definingFn = lc.getDefiningFunction(symbol); assert definingFn != null; return definingFn == function; } @Override public Node leaveBinaryNode(final BinaryNode binaryNode) { if (binaryNode.isTokenType(TokenType.ASSIGN)) { return leaveASSIGN(binaryNode); } return super.leaveBinaryNode(binaryNode); } private Node leaveASSIGN(final BinaryNode binaryNode) { // If we're assigning a property of the this object ("this.foo = ..."), record it. final Expression lhs = binaryNode.lhs(); if (lhs instanceof AccessNode) { final AccessNode accessNode = (AccessNode) lhs; final Expression base = accessNode.getBase(); if (base instanceof IdentNode) { final Symbol symbol = ((IdentNode)base).getSymbol(); if(symbol.isThis()) { thisProperties.peek().add(accessNode.getProperty()); } } } return binaryNode; } @Override public Node leaveUnaryNode(final UnaryNode unaryNode) { switch (unaryNode.tokenType()) { case DELETE: return leaveDELETE(unaryNode); case TYPEOF: return leaveTYPEOF(unaryNode); default: return super.leaveUnaryNode(unaryNode); } } private Node leaveDELETE(final UnaryNode unaryNode) { final FunctionNode currentFunctionNode = lc.getCurrentFunction(); final boolean strictMode = currentFunctionNode.isStrict(); final Expression rhs = unaryNode.getExpression(); final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); Request request = Request.DELETE; final List<Expression> args = new ArrayList<>(); if (rhs instanceof IdentNode) { final IdentNode ident = (IdentNode)rhs; // If this is a declared variable or a function parameter, delete always fails (except for globals). final String name = ident.getName(); final Symbol symbol = ident.getSymbol(); if (symbol.isThis()) { // Can't delete "this", ignore and return true return LiteralNode.newInstance(unaryNode, true); } final Expression literalNode = LiteralNode.newInstance(unaryNode, name); final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))); if (!failDelete) { args.add(compilerConstantIdentifier(SCOPE)); } args.add(literalNode); args.add(strictFlagNode); if (failDelete) { request = Request.FAIL_DELETE; } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) { request = Request.SLOW_DELETE; } } else if (rhs instanceof AccessNode) { final Expression base = ((AccessNode)rhs).getBase(); final String property = ((AccessNode)rhs).getProperty(); args.add(base); args.add(LiteralNode.newInstance(unaryNode, property)); args.add(strictFlagNode); } else if (rhs instanceof IndexNode) { final IndexNode indexNode = (IndexNode)rhs; final Expression base = indexNode.getBase(); final Expression index = indexNode.getIndex(); args.add(base); args.add(index); args.add(strictFlagNode); } else { return LiteralNode.newInstance(unaryNode, true); } return new RuntimeNode(unaryNode, request, args); } @Override public Node leaveForNode(final ForNode forNode) { if (forNode.isForInOrOf()) { return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73 } return end(forNode); } @Override public Node leaveFunctionNode(final FunctionNode functionNode) { final FunctionNode finalizedFunction; if (isUnparsedFunction(functionNode)) { finalizedFunction = functionNode; } else { finalizedFunction = markProgramBlock( removeUnusedSlots( createSyntheticInitializers( finalizeParameters( lc.applyTopFlags(functionNode)))) .setThisProperties(lc, thisProperties.pop().size())); } return finalizedFunction; } @Override public Node leaveIdentNode(final IdentNode identNode) { if (identNode.isPropertyName()) { return identNode; } final Symbol symbol = nameIsUsed(identNode.getName(), identNode); if (!identNode.isInitializedHere()) { symbol.increaseUseCount(); } IdentNode newIdentNode = identNode.setSymbol(symbol); // If a block-scoped var is used before its declaration mark it as dead. // We can only statically detect this for local vars, cross-function symbols require runtime checks. if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { newIdentNode = newIdentNode.markDead(); } return end(newIdentNode); } private Symbol nameIsUsed(final String name, final IdentNode origin) { final Block block = lc.getCurrentBlock(); Symbol symbol = findSymbol(block, name); //If an existing symbol with the name is found, use that otherwise, declare a new one if (symbol != null) { log.info("Existing symbol = ", symbol); if (symbol.isFunctionSelf()) { final FunctionNode functionNode = lc.getDefiningFunction(symbol); assert functionNode != null; assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); } // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) maybeForceScope(symbol); } else { log.info("No symbol exists. Declare as global: ", name); symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE); } functionUsesSymbol(symbol); return symbol; } @Override public Node leaveSwitchNode(final SwitchNode switchNode) { // We only need a symbol for the tag if it's not an integer switch node if(!switchNode.isUniqueInteger()) { return switchNode.setTag(lc, newObjectInternal(SWITCH_TAG_PREFIX)); } return switchNode; } @Override public Node leaveTryNode(final TryNode tryNode) { assert tryNode.getFinallyBody() == null; end(tryNode); return tryNode.setException(lc, exceptionSymbol()); } private Node leaveTYPEOF(final UnaryNode unaryNode) { final Expression rhs = unaryNode.getExpression(); final List<Expression> args = new ArrayList<>(); if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) { args.add(compilerConstantIdentifier(SCOPE)); args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName())); //null } else { args.add(rhs); args.add(LiteralNode.newInstance(unaryNode)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' } final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); end(unaryNode); return runtimeNode; } private FunctionNode markProgramBlock(final FunctionNode functionNode) { if (isOnDemand || !functionNode.isProgram()) { return functionNode; } return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); }
If the symbol isn't already a scope symbol, but it needs to be (see symbolNeedsToBeScope(Symbol), it is promoted to a scope symbol and its block marked as needing a scope.
Params:
  • symbol – the symbol that might be scoped
/** * If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is * promoted to a scope symbol and its block marked as needing a scope. * @param symbol the symbol that might be scoped */
private void maybeForceScope(final Symbol symbol) { if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) { Symbol.setSymbolIsScope(lc, symbol); } } private Symbol newInternal(final CompilerConstants cc, final int flags) { return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), null, IS_VAR | IS_INTERNAL | flags); //NASHORN-73 } private Symbol newObjectInternal(final CompilerConstants cc) { return newInternal(cc, HAS_OBJECT_VALUE); } private boolean start(final Node node) { return start(node, true); } private boolean start(final Node node, final boolean printNode) { if (debug) { final StringBuilder sb = new StringBuilder(); sb.append("[ENTER "). append(name(node)). append("] "). append(printNode ? node.toString() : ""). append(" in '"). append(lc.getCurrentFunction().getName()). append("'"); log.info(sb); log.indent(); } return true; }
Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only be reached from the current block by traversing a function node, a split node, or a with node.
Params:
  • symbol – the symbol checked for needing to be a scope symbol
Returns:true if the symbol has to be a scope symbol.
/** * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only * be reached from the current block by traversing a function node, a split node, or a with node. * @param symbol the symbol checked for needing to be a scope symbol * @return true if the symbol has to be a scope symbol. */
private boolean symbolNeedsToBeScope(final Symbol symbol) { if (symbol.isThis() || symbol.isInternal()) { return false; } final FunctionNode func = lc.getCurrentFunction(); if ( func.allVarsInScope() || (!symbol.isBlockScoped() && func.isProgram())) { return true; } boolean previousWasBlock = false; for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if (node instanceof FunctionNode || isSplitLiteral(node)) { // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. // It needs to be in scope. return true; } else if (node instanceof WithNode) { if (previousWasBlock) { // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately // preceded by a block, this means we're currently processing its expression, not its body, // therefore it doesn't count. return true; } previousWasBlock = false; } else if (node instanceof Block) { if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { // We reached the block that defines the symbol without reaching either the function boundary, or a // WithNode. The symbol need not be scoped. return false; } previousWasBlock = true; } else { previousWasBlock = false; } } throw new AssertionError(); } private static boolean isSplitLiteral(final LexicalContextNode expr) { return expr instanceof Splittable && ((Splittable) expr).getSplitRanges() != null; } private void throwUnprotectedSwitchError(final VarNode varNode) { // Block scoped declarations in switch statements without explicit blocks should be declared // in a common block that contains all the case clauses. We cannot support this without a // fundamental rewrite of how switch statements are handled (case nodes contain blocks and are // directly contained by switch node). As a temporary solution we throw a reference error here. final String msg = ECMAErrors.getMessage("syntax.error.unprotected.switch.declaration", varNode.isLet() ? "let" : "const"); throwParserException(msg, varNode); } private void throwParserException(final String message, final Node origin) { if (origin == null) { throw new ParserException(message); } final Source source = compiler.getSource(); final long token = origin.getToken(); final int line = source.getLine(origin.getStart()); final int column = source.getColumn(origin.getStart()); final String formatted = ErrorManager.format(message, source, line, column, token); throw new ParserException(JSErrorType.SYNTAX_ERROR, formatted, source, line, column, token); } }