| /* |
| * 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. |
| */ |
| @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. |
| * @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 |
| * |
| * @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. |
| * @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 {@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. |
| * |
| * @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. |
| throw new AssertionError("duplicate parameter"); |
| } |
| } 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.getException(); |
| 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. |
| * |
| * @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. |
| * @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 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 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. |
| * @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. |
| * @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? |
| * @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.isForIn()) { |
| 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 {@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. |
| * @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); |
| } |
| } |