| /* |
| * 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.LITERAL_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.IS_ALWAYS_DEFINED; |
| import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT; |
| 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_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.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.ir.AccessNode; |
| import jdk.nashorn.internal.ir.BinaryNode; |
| import jdk.nashorn.internal.ir.Block; |
| import jdk.nashorn.internal.ir.CallNode; |
| import jdk.nashorn.internal.ir.CaseNode; |
| 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.FunctionNode.CompilationState; |
| import jdk.nashorn.internal.ir.IdentNode; |
| import jdk.nashorn.internal.ir.IndexNode; |
| import jdk.nashorn.internal.ir.LexicalContext; |
| import jdk.nashorn.internal.ir.LexicalContextNode; |
| import jdk.nashorn.internal.ir.LiteralNode; |
| import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; |
| import jdk.nashorn.internal.ir.Node; |
| import jdk.nashorn.internal.ir.ObjectNode; |
| import jdk.nashorn.internal.ir.ReturnNode; |
| import jdk.nashorn.internal.ir.RuntimeNode; |
| import jdk.nashorn.internal.ir.RuntimeNode.Request; |
| import jdk.nashorn.internal.ir.Statement; |
| import jdk.nashorn.internal.ir.SwitchNode; |
| import jdk.nashorn.internal.ir.Symbol; |
| import jdk.nashorn.internal.ir.TemporarySymbols; |
| import jdk.nashorn.internal.ir.TernaryNode; |
| 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.NodeOperatorVisitor; |
| import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
| import jdk.nashorn.internal.parser.TokenType; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.Debug; |
| import jdk.nashorn.internal.runtime.DebugLogger; |
| import jdk.nashorn.internal.runtime.JSType; |
| import jdk.nashorn.internal.runtime.Property; |
| import jdk.nashorn.internal.runtime.PropertyMap; |
| |
| /** |
| * This is the attribution pass of the code generator. Attr takes Lowered IR, |
| * that is, IR where control flow has been computed and high level to low level |
| * substitions for operations have been performed. |
| * |
| * After Attr, every symbol will have a conservative correct type. |
| * |
| * Any expression that requires temporary storage as part of computation will |
| * also be detected here and give a temporary symbol |
| * |
| * Types can be narrowed after Attr by Access Specialization in FinalizeTypes, |
| * but in general, this is where the main symbol type information is |
| * computed. |
| */ |
| |
| final class Attr extends NodeOperatorVisitor<LexicalContext> { |
| |
| /** |
| * Local definitions in current block (to discriminate from function |
| * declarations always defined in the function scope. This is for |
| * "can be undefined" analysis. |
| */ |
| private final Deque<Set<String>> localDefs; |
| |
| /** |
| * Local definitions in current block to guard against cases like |
| * NASHORN-467 when things can be undefined as they are used before |
| * their local var definition. *sigh* JavaScript... |
| */ |
| private final Deque<Set<String>> localUses; |
| |
| private final Deque<Type> returnTypes; |
| |
| private int catchNestingLevel; |
| |
| private static final DebugLogger LOG = new DebugLogger("attr"); |
| private static final boolean DEBUG = LOG.isEnabled(); |
| |
| private final TemporarySymbols temporarySymbols; |
| |
| /** |
| * Constructor. |
| */ |
| Attr(final TemporarySymbols temporarySymbols) { |
| super(new LexicalContext()); |
| this.temporarySymbols = temporarySymbols; |
| this.localDefs = new ArrayDeque<>(); |
| this.localUses = new ArrayDeque<>(); |
| this.returnTypes = new ArrayDeque<>(); |
| } |
| |
| @Override |
| protected boolean enterDefault(final Node node) { |
| return start(node); |
| } |
| |
| @Override |
| protected Node leaveDefault(final Node node) { |
| return end(node); |
| } |
| |
| @Override |
| public Node leaveAccessNode(final AccessNode accessNode) { |
| //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this, that |
| //is why we can't set the access node base to be an object here, that will ruin access specialization |
| //for example for a.x | 17. |
| return end(ensureSymbol(Type.OBJECT, accessNode)); |
| } |
| |
| private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { |
| initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL); |
| initCompileConstant(THIS, body, IS_PARAM | IS_THIS, Type.OBJECT); |
| |
| if (functionNode.isVarArg()) { |
| initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL); |
| if (functionNode.needsArguments()) { |
| initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED); |
| final String argumentsName = ARGUMENTS_VAR.symbolName(); |
| newType(defineSymbol(body, argumentsName, IS_VAR | IS_ALWAYS_DEFINED), Type.typeFor(ARGUMENTS_VAR.type())); |
| addLocalDef(argumentsName); |
| } |
| } |
| |
| initParameters(functionNode, body); |
| initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED); |
| initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED, Type.OBJECT); |
| } |
| |
| |
| /** |
| * This pushes all declarations (except for non-statements, i.e. for |
| * node temporaries) to 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, except function declarations (which are taken care |
| // in a separate step above) and "var" declarations in for loop initializers. |
| // |
| // It also handles the case that a variable can be undefined, e.g |
| // if (cond) { |
| // x = x.y; |
| // } |
| // var x = 17; |
| // |
| // by making sure that no identifier has been found earlier in the body than the |
| // declaration - if such is the case the identifier is flagged as caBeUndefined to |
| // be safe if it turns into a local var. Otherwise corrupt bytecode results |
| |
| body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
| private final Set<String> uses = new HashSet<>(); |
| private final Set<String> canBeUndefined = new HashSet<>(); |
| |
| @Override |
| public boolean enterFunctionNode(final FunctionNode nestedFn) { |
| return false; |
| } |
| |
| @Override |
| public Node leaveIdentNode(final IdentNode identNode) { |
| uses.add(identNode.getName()); |
| return identNode; |
| } |
| |
| @Override |
| public boolean enterVarNode(final VarNode varNode) { |
| final String name = varNode.getName().getName(); |
| //if this is used before the var node, the var node symbol needs to be tagged as can be undefined |
| if (uses.contains(name)) { |
| canBeUndefined.add(name); |
| } |
| |
| // all uses of the declared varnode inside the var node are potentially undefined |
| // however this is a bit conservative as e.g. var x = 17; var x = 1 + x; does work |
| if (!varNode.isFunctionDeclaration() && varNode.getInit() != null) { |
| varNode.getInit().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
| @Override |
| public boolean enterIdentNode(final IdentNode identNode) { |
| if (name.equals(identNode.getName())) { |
| canBeUndefined.add(name); |
| } |
| return false; |
| } |
| }); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public Node leaveVarNode(final VarNode varNode) { |
| // any declared symbols that aren't visited need to be typed as well, hence the list |
| if (varNode.isStatement()) { |
| final IdentNode ident = varNode.getName(); |
| final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR); |
| if (canBeUndefined.contains(ident.getName())) { |
| symbol.setType(Type.OBJECT); |
| symbol.setCanBeUndefined(); |
| } |
| functionNode.addDeclaredSymbol(symbol); |
| if (varNode.isFunctionDeclaration()) { |
| newType(symbol, FunctionNode.FUNCTION_TYPE); |
| } |
| return varNode.setName((IdentNode)ident.setSymbol(lc, symbol)); |
| } |
| |
| return varNode; |
| } |
| }); |
| } |
| |
| private void enterFunctionBody() { |
| |
| final FunctionNode functionNode = lc.getCurrentFunction(); |
| final Block body = lc.getCurrentBlock(); |
| |
| initFunctionWideVariables(functionNode, body); |
| |
| if (functionNode.isProgram()) { |
| initFromPropertyMap(body); |
| } else if (!functionNode.isDeclared()) { |
| // It's neither declared nor program - it's a function expression then; assign it a self-symbol. |
| assert functionNode.getSymbol() == null; |
| |
| final boolean anonymous = functionNode.isAnonymous(); |
| final String name = anonymous ? null : functionNode.getIdent().getName(); |
| if (!(anonymous || body.getExistingSymbol(name) != null)) { |
| assert !anonymous && name != null; |
| newType(defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF), Type.OBJECT); |
| } |
| } |
| |
| acceptDeclarations(functionNode, body); |
| } |
| |
| @Override |
| public boolean enterBlock(final Block block) { |
| start(block); |
| //ensure that we don't use information from a previous compile. This is very ugly TODO |
| //the symbols in the block should really be stateless |
| block.clearSymbols(); |
| |
| if (lc.isFunctionBody()) { |
| enterFunctionBody(); |
| } |
| pushLocalsBlock(); |
| |
| return true; |
| } |
| |
| @Override |
| public Node leaveBlock(final Block block) { |
| popLocals(); |
| return end(block); |
| } |
| |
| @Override |
| public boolean enterCallNode(final CallNode callNode) { |
| return start(callNode); |
| } |
| |
| @Override |
| public Node leaveCallNode(final CallNode callNode) { |
| return end(ensureSymbol(callNode.getType(), callNode)); |
| } |
| |
| @Override |
| public boolean enterCatchNode(final CatchNode catchNode) { |
| final IdentNode exception = catchNode.getException(); |
| final Block block = lc.getCurrentBlock(); |
| |
| start(catchNode); |
| catchNestingLevel++; |
| |
| // define block-local exception variable |
| final String exname = exception.getName(); |
| final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED); |
| newType(def, Type.OBJECT); //we can catch anything, not just ecma exceptions |
| |
| addLocalDef(exname); |
| |
| return true; |
| } |
| |
| @Override |
| public Node leaveCatchNode(final CatchNode catchNode) { |
| final IdentNode exception = catchNode.getException(); |
| final Block block = lc.getCurrentBlock(); |
| final Symbol symbol = findSymbol(block, exception.getName()); |
| |
| catchNestingLevel--; |
| |
| assert symbol != null; |
| return end(catchNode.setException((IdentNode)exception.setSymbol(lc, symbol))); |
| } |
| |
| /** |
| * Declare the definition of a new symbol. |
| * |
| * @param name Name of symbol. |
| * @param symbolFlags Symbol flags. |
| * |
| * @return Symbol for given name or null for redefinition. |
| */ |
| private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) { |
| int flags = symbolFlags; |
| Symbol symbol = findSymbol(block, name); // Locate symbol. |
| |
| if ((flags & KINDMASK) == IS_GLOBAL) { |
| flags |= IS_SCOPE; |
| } |
| |
| final FunctionNode function = lc.getFunction(block); |
| if (symbol != null) { |
| // Symbol was already defined. Check if it needs to be redefined. |
| if ((flags & KINDMASK) == IS_PARAM) { |
| 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. |
| assert false : "duplicate parameter"; |
| return null; |
| } |
| } else if ((flags & KINDMASK) == IS_VAR) { |
| if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) { |
| // Always create a new definition. |
| symbol = null; |
| } else { |
| // 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. |
| Block symbolBlock; |
| |
| // Determine where to create it. |
| if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) { |
| symbolBlock = block; //internal vars are always defined in the block closest to them |
| } else { |
| symbolBlock = lc.getFunctionBody(function); |
| } |
| |
| // Create and add to appropriate block. |
| symbol = new Symbol(name, flags); |
| symbolBlock.putSymbol(lc, symbol); |
| |
| if ((flags & Symbol.KINDMASK) != IS_GLOBAL) { |
| symbol.setNeedsSlot(true); |
| } |
| } else if (symbol.less(flags)) { |
| symbol.setFlags(flags); |
| } |
| |
| return symbol; |
| } |
| |
| @Override |
| public boolean enterFunctionNode(final FunctionNode functionNode) { |
| start(functionNode, false); |
| |
| if (functionNode.isLazy()) { |
| return false; |
| } |
| |
| //an outermost function in our lexical context that is not a program (runScript) |
| //is possible - it is a function being compiled lazily |
| if (functionNode.isDeclared()) { |
| final Iterator<Block> blocks = lc.getBlocks(); |
| if (blocks.hasNext()) { |
| defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR); |
| } |
| } |
| |
| returnTypes.push(functionNode.getReturnType()); |
| pushLocalsFunction(); |
| |
| return true; |
| } |
| |
| @Override |
| public Node leaveFunctionNode(final FunctionNode functionNode) { |
| FunctionNode newFunctionNode = functionNode; |
| |
| final Block body = newFunctionNode.getBody(); |
| |
| //look for this function in the parent block |
| if (functionNode.isDeclared()) { |
| final Iterator<Block> blocks = lc.getBlocks(); |
| if (blocks.hasNext()) { |
| newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, findSymbol(blocks.next(), functionNode.getIdent().getName())); |
| } |
| } else if (!functionNode.isProgram()) { |
| final boolean anonymous = functionNode.isAnonymous(); |
| final String name = anonymous ? null : functionNode.getIdent().getName(); |
| if (anonymous || body.getExistingSymbol(name) != null) { |
| newFunctionNode = (FunctionNode)ensureSymbol(FunctionNode.FUNCTION_TYPE, newFunctionNode); |
| } else { |
| assert name != null; |
| final Symbol self = body.getExistingSymbol(name); |
| assert self != null && self.isFunctionSelf(); |
| newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, body.getExistingSymbol(name)); |
| } |
| } |
| |
| //unknown parameters are promoted to object type. |
| newFunctionNode = finalizeParameters(newFunctionNode); |
| newFunctionNode = finalizeTypes(newFunctionNode); |
| for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) { |
| if (symbol.getSymbolType().isUnknown()) { |
| symbol.setType(Type.OBJECT); |
| symbol.setCanBeUndefined(); |
| } |
| } |
| |
| if (newFunctionNode.hasLazyChildren()) { |
| //the final body has already been assigned as we have left the function node block body by now |
| objectifySymbols(body); |
| } |
| |
| List<VarNode> syntheticInitializers = null; |
| |
| if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) { |
| syntheticInitializers = new ArrayList<>(2); |
| LOG.info("Accepting self symbol init for ", newFunctionNode.getName()); |
| // "var fn = :callee" |
| syntheticInitializers.add(createSyntheticInitializer(newFunctionNode.getIdent(), CALLEE, newFunctionNode)); |
| } |
| |
| if(newFunctionNode.needsArguments()) { |
| if(syntheticInitializers == null) { |
| syntheticInitializers = new ArrayList<>(1); |
| } |
| // "var arguments = :arguments" |
| syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), |
| ARGUMENTS, newFunctionNode)); |
| } |
| |
| if(syntheticInitializers != null) { |
| final List<Statement> stmts = body.getStatements(); |
| final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); |
| newStatements.addAll(syntheticInitializers); |
| newStatements.addAll(stmts); |
| newFunctionNode = newFunctionNode.setBody(lc, body.setStatements(lc, newStatements)); |
| } |
| |
| if (returnTypes.peek().isUnknown()) { |
| LOG.info("Unknown return type promoted to object"); |
| newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT); |
| } |
| final Type returnType = returnTypes.pop(); |
| newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType); |
| newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR); |
| |
| popLocals(); |
| |
| end(newFunctionNode, false); |
| |
| return newFunctionNode; |
| } |
| |
| /** |
| * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically |
| * used to create assignmnent 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 = compilerConstant(initConstant); |
| assert init.getSymbol() != null && init.getSymbol().hasSlot(); |
| |
| VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); |
| |
| final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); |
| assert nameSymbol != null; |
| |
| return synthVar.setName((IdentNode)name.setSymbol(lc, nameSymbol)); |
| } |
| |
| @Override |
| public Node leaveCONVERT(final UnaryNode unaryNode) { |
| assert false : "There should be no convert operators in IR during Attribution"; |
| return end(unaryNode); |
| } |
| |
| @Override |
| public Node leaveIdentNode(final IdentNode identNode) { |
| final String name = identNode.getName(); |
| |
| if (identNode.isPropertyName()) { |
| // assign a pseudo symbol to property name |
| final Symbol pseudoSymbol = pseudoSymbol(name); |
| LOG.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol); |
| LOG.unindent(); |
| return end(identNode.setSymbol(lc, pseudoSymbol)); |
| } |
| |
| 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.getBody(), Block.NEEDS_SELF_SYMBOL); |
| newType(symbol, FunctionNode.FUNCTION_TYPE); |
| } else if (!identNode.isInitializedHere()) { |
| /* |
| * See NASHORN-448, JDK-8016235 |
| * |
| * Here is a use outside the local def scope |
| * the inCatch check is a conservative approach to handle things that might have only been |
| * defined in the try block, but with variable declarations, which due to JavaScript rules |
| * have to be lifted up into the function scope outside the try block anyway, but as the |
| * flow can fault at almost any place in the try block and get us to the catch block, all we |
| * know is that we have a declaration, not a definition. This can be made better and less |
| * conservative once we superimpose a CFG onto the AST. |
| */ |
| if (!isLocalDef(name) || inCatch()) { |
| newType(symbol, Type.OBJECT); |
| symbol.setCanBeUndefined(); |
| } |
| } |
| |
| // 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 undefined: ", symbol); |
| symbol = defineSymbol(block, name, IS_GLOBAL); |
| // we have never seen this before, it can be undefined |
| newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway? |
| symbol.setCanBeUndefined(); |
| Symbol.setSymbolIsScope(lc, symbol); |
| } |
| |
| setBlockScope(name, symbol); |
| |
| if (!identNode.isInitializedHere()) { |
| symbol.increaseUseCount(); |
| } |
| addLocalUse(identNode.getName()); |
| |
| return end(identNode.setSymbol(lc, symbol)); |
| } |
| |
| private boolean inCatch() { |
| return catchNestingLevel > 0; |
| } |
| |
| /** |
| * If the symbol isn't already a scope symbol, and it is either not local to the current function, or it is being |
| * referenced from within a with block, we force it to be a scope symbol. |
| * @param symbol the symbol that might be scoped |
| */ |
| private void maybeForceScope(final Symbol symbol) { |
| if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) { |
| Symbol.setSymbolIsScope(lc, symbol); |
| } |
| } |
| |
| private boolean symbolNeedsToBeScope(Symbol symbol) { |
| if (symbol.isThis() || symbol.isInternal()) { |
| return false; |
| } |
| boolean previousWasBlock = false; |
| for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { |
| final LexicalContextNode node = it.next(); |
| if (node instanceof FunctionNode) { |
| // We reached the function 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 void setBlockScope(final String name, final Symbol symbol) { |
| assert symbol != null; |
| if (symbol.isGlobal()) { |
| setUsesGlobalSymbol(); |
| return; |
| } |
| |
| if (symbol.isScope()) { |
| Block scopeBlock = null; |
| for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { |
| final LexicalContextNode node = contextNodeIter.next(); |
| if (node instanceof Block) { |
| if (((Block)node).getExistingSymbol(name) != null) { |
| scopeBlock = (Block)node; |
| break; |
| } |
| } else if (node instanceof FunctionNode) { |
| lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); |
| } |
| } |
| |
| if (scopeBlock != null) { |
| assert lc.contains(scopeBlock); |
| lc.setBlockNeedsScope(scopeBlock); |
| } |
| } |
| } |
| |
| /** |
| * 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 #needsParentScope() |
| */ |
| private void setUsesGlobalSymbol() { |
| for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) { |
| lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); |
| } |
| } |
| |
| /** |
| * 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) { |
| // Search up block chain to locate symbol. |
| |
| for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { |
| // Find name. |
| final Symbol symbol = blocks.next().getExistingSymbol(name); |
| // If found then we are good. |
| if (symbol != null) { |
| return symbol; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Node leaveIndexNode(final IndexNode indexNode) { |
| return end(ensureSymbol(Type.OBJECT, indexNode)); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public Node leaveLiteralNode(final LiteralNode literalNode) { |
| assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens |
| assert literalNode instanceof ArrayLiteralNode || !(literalNode.getValue() instanceof Node) : "literals with Node values not supported"; |
| final Symbol symbol = new Symbol(lc.getCurrentFunction().uniqueName(LITERAL_PREFIX.symbolName()), IS_CONSTANT, literalNode.getType()); |
| if (literalNode instanceof ArrayLiteralNode) { |
| ((ArrayLiteralNode)literalNode).analyze(); |
| } |
| return end(literalNode.setSymbol(lc, symbol)); |
| } |
| |
| @Override |
| public boolean enterObjectNode(final ObjectNode objectNode) { |
| return start(objectNode); |
| } |
| |
| @Override |
| public Node leaveObjectNode(final ObjectNode objectNode) { |
| return end(ensureSymbol(Type.OBJECT, objectNode)); |
| } |
| |
| @Override |
| public Node leaveReturnNode(final ReturnNode returnNode) { |
| final Expression expr = returnNode.getExpression(); |
| final Type returnType; |
| |
| if (expr != null) { |
| //we can't do parameter specialization if we return something that hasn't been typed yet |
| final Symbol symbol = expr.getSymbol(); |
| if (expr.getType().isUnknown() && symbol.isParam()) { |
| symbol.setType(Type.OBJECT); |
| } |
| |
| returnType = Type.widest(returnTypes.pop(), symbol.getSymbolType()); |
| } else { |
| returnType = Type.OBJECT; //undefined |
| } |
| LOG.info("Returntype is now ", returnType); |
| returnTypes.push(returnType); |
| |
| end(returnNode); |
| |
| return returnNode; |
| } |
| |
| @Override |
| public Node leaveSwitchNode(final SwitchNode switchNode) { |
| Type type = Type.UNKNOWN; |
| |
| final List<CaseNode> newCases = new ArrayList<>(); |
| for (final CaseNode caseNode : switchNode.getCases()) { |
| final Node test = caseNode.getTest(); |
| |
| CaseNode newCaseNode = caseNode; |
| if (test != null) { |
| if (test instanceof LiteralNode) { |
| //go down to integers if we can |
| final LiteralNode<?> lit = (LiteralNode<?>)test; |
| if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) { |
| if (JSType.isRepresentableAsInt(lit.getNumber())) { |
| newCaseNode = caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this)); |
| } |
| } |
| } else { |
| // the "all integer" case that CodeGenerator optimizes for currently assumes literals only |
| type = Type.OBJECT; |
| } |
| |
| type = Type.widest(type, newCaseNode.getTest().getType()); |
| if (type.isBoolean()) { |
| type = Type.OBJECT; //booleans and integers aren't assignment compatible |
| } |
| } |
| |
| newCases.add(newCaseNode); |
| } |
| |
| //only optimize for all integers |
| if (!type.isInteger()) { |
| type = Type.OBJECT; |
| } |
| |
| switchNode.setTag(newInternal(lc.getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type)); |
| |
| end(switchNode); |
| |
| return switchNode.setCases(lc, newCases); |
| } |
| |
| @Override |
| public Node leaveTryNode(final TryNode tryNode) { |
| tryNode.setException(exceptionSymbol()); |
| |
| if (tryNode.getFinallyBody() != null) { |
| tryNode.setFinallyCatchAll(exceptionSymbol()); |
| } |
| |
| end(tryNode); |
| |
| return tryNode; |
| } |
| |
| @Override |
| public boolean enterVarNode(final VarNode varNode) { |
| start(varNode); |
| |
| final IdentNode ident = varNode.getName(); |
| final String name = ident.getName(); |
| |
| final Symbol symbol = defineSymbol(lc.getCurrentBlock(), name, IS_VAR); |
| assert symbol != null; |
| |
| // NASHORN-467 - use before definition of vars - conservative |
| if (isLocalUse(ident.getName())) { |
| newType(symbol, Type.OBJECT); |
| symbol.setCanBeUndefined(); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public Node leaveVarNode(final VarNode varNode) { |
| final Expression init = varNode.getInit(); |
| final IdentNode ident = varNode.getName(); |
| final String name = ident.getName(); |
| |
| final Symbol symbol = findSymbol(lc.getCurrentBlock(), name); |
| assert ident.getSymbol() == symbol; |
| |
| if (init == null) { |
| // var x; with no init will be treated like a use of x by |
| // leaveIdentNode unless we remove the name from the localdef list. |
| removeLocalDef(name); |
| return end(varNode); |
| } |
| |
| addLocalDef(name); |
| |
| assert symbol != null; |
| |
| final IdentNode newIdent = (IdentNode)ident.setSymbol(lc, symbol); |
| |
| final VarNode newVarNode = varNode.setName(newIdent); |
| |
| final boolean isScript = lc.getDefiningFunction(symbol).isProgram(); //see NASHORN-56 |
| if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) { |
| // Forbid integers as local vars for now as we have no way to treat them as undefined |
| newType(symbol, init.getType()); |
| } else { |
| newType(symbol, Type.OBJECT); |
| } |
| |
| assert newVarNode.getName().hasType() : newVarNode + " has no type"; |
| |
| return end(newVarNode); |
| } |
| |
| @Override |
| public Node leaveADD(final UnaryNode unaryNode) { |
| return end(ensureSymbol(arithType(), unaryNode)); |
| } |
| |
| @Override |
| public Node leaveBIT_NOT(final UnaryNode unaryNode) { |
| return end(ensureSymbol(Type.INT, unaryNode)); |
| } |
| |
| @Override |
| public Node leaveDECINC(final UnaryNode unaryNode) { |
| // @see assignOffset |
| final Type type = arithType(); |
| newType(unaryNode.rhs().getSymbol(), type); |
| return end(ensureSymbol(type, unaryNode)); |
| } |
| |
| @Override |
| public Node leaveDELETE(final UnaryNode unaryNode) { |
| final FunctionNode currentFunctionNode = lc.getCurrentFunction(); |
| final boolean strictMode = currentFunctionNode.isStrict(); |
| final Expression rhs = unaryNode.rhs(); |
| final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); |
| |
| Request request = Request.DELETE; |
| final List<Expression> args = new ArrayList<>(); |
| |
| if (rhs instanceof IdentNode) { |
| // If this is a declared variable or a function parameter, delete always fails (except for globals). |
| final String name = ((IdentNode)rhs).getName(); |
| |
| final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name)); |
| |
| if (failDelete && rhs.getSymbol().isThis()) { |
| return LiteralNode.newInstance(unaryNode, true).accept(this); |
| } |
| final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this); |
| |
| if (!failDelete) { |
| args.add(compilerConstant(SCOPE)); |
| } |
| args.add(literalNode); |
| args.add(strictFlagNode); |
| |
| if (failDelete) { |
| request = Request.FAIL_DELETE; |
| } |
| } else if (rhs instanceof AccessNode) { |
| final Expression base = ((AccessNode)rhs).getBase(); |
| final IdentNode property = ((AccessNode)rhs).getProperty(); |
| |
| args.add(base); |
| args.add((Expression)LiteralNode.newInstance(unaryNode, property.getName()).accept(this)); |
| 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).accept(this); |
| } |
| |
| final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args); |
| assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this |
| |
| return leaveRuntimeNode(runtimeNode); |
| } |
| |
| /** |
| * Is the symbol denoted by the specified name in the current lexical context defined in the program level |
| * @param name the name of the symbol |
| * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level. |
| */ |
| private boolean isProgramLevelSymbol(final String name) { |
| for(final Iterator<Block> it = lc.getBlocks(); it.hasNext();) { |
| final Block next = it.next(); |
| if(next.getExistingSymbol(name) != null) { |
| return next == lc.getFunctionBody(lc.getOutermostFunction()); |
| } |
| } |
| throw new AssertionError("Couldn't find symbol " + name + " in the context"); |
| } |
| |
| @Override |
| public Node leaveNEW(final UnaryNode unaryNode) { |
| return end(ensureSymbol(Type.OBJECT, unaryNode)); |
| } |
| |
| @Override |
| public Node leaveNOT(final UnaryNode unaryNode) { |
| return end(ensureSymbol(Type.BOOLEAN, unaryNode)); |
| } |
| |
| private IdentNode compilerConstant(CompilerConstants cc) { |
| return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc, 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); |
| } |
| |
| @Override |
| public Node leaveTYPEOF(final UnaryNode unaryNode) { |
| final Expression rhs = unaryNode.rhs(); |
| |
| List<Expression> args = new ArrayList<>(); |
| if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) { |
| args.add(compilerConstant(SCOPE)); |
| args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null |
| } else { |
| args.add(rhs); |
| args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' |
| } |
| |
| RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); |
| assert runtimeNode.getSymbol() == unaryNode.getSymbol(); |
| |
| runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode); |
| |
| end(unaryNode); |
| |
| return runtimeNode; |
| } |
| |
| @Override |
| public Node leaveRuntimeNode(final RuntimeNode runtimeNode) { |
| return end(ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode)); |
| } |
| |
| @Override |
| public Node leaveSUB(final UnaryNode unaryNode) { |
| return end(ensureSymbol(arithType(), unaryNode)); |
| } |
| |
| @Override |
| public Node leaveVOID(final UnaryNode unaryNode) { |
| return end(ensureSymbol(Type.OBJECT, unaryNode)); |
| } |
| |
| /** |
| * Add is a special binary, as it works not only on arithmetic, but for |
| * strings etc as well. |
| */ |
| @Override |
| public Node leaveADD(final BinaryNode binaryNode) { |
| final Expression lhs = binaryNode.lhs(); |
| final Expression rhs = binaryNode.rhs(); |
| |
| ensureTypeNotUnknown(lhs); |
| ensureTypeNotUnknown(rhs); |
| //even if we are adding two known types, this can overflow. i.e. |
| //int and number -> number. |
| //int and int are also number though. |
| //something and object is object |
| return end(ensureSymbol(Type.widest(arithType(), Type.widest(lhs.getType(), rhs.getType())), binaryNode)); |
| } |
| |
| @Override |
| public Node leaveAND(final BinaryNode binaryNode) { |
| return end(ensureSymbol(Type.OBJECT, binaryNode)); |
| } |
| |
| /** |
| * This is a helper called before an assignment. |
| * @param binaryNode assignment node |
| */ |
| private boolean enterAssignmentNode(final BinaryNode binaryNode) { |
| start(binaryNode); |
| |
| final Node lhs = binaryNode.lhs(); |
| |
| if (lhs instanceof IdentNode) { |
| final Block block = lc.getCurrentBlock(); |
| final IdentNode ident = (IdentNode)lhs; |
| final String name = ident.getName(); |
| |
| Symbol symbol = findSymbol(block, name); |
| |
| if (symbol == null) { |
| symbol = defineSymbol(block, name, IS_GLOBAL); |
| } else { |
| maybeForceScope(symbol); |
| } |
| |
| addLocalDef(name); |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * This assign helper is called after an assignment, when all children of |
| * the assign has been processed. It fixes the types and recursively makes |
| * sure that everyhing has slots that should have them in the chain. |
| * |
| * @param binaryNode assignment node |
| */ |
| private Node leaveAssignmentNode(final BinaryNode binaryNode) { |
| BinaryNode newBinaryNode = binaryNode; |
| |
| final Expression lhs = binaryNode.lhs(); |
| final Expression rhs = binaryNode.rhs(); |
| final Type type; |
| |
| if (rhs.getType().isNumeric()) { |
| type = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); |
| } else { |
| type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too. |
| } |
| |
| newType(lhs.getSymbol(), type); |
| return end(ensureSymbol(type, newBinaryNode)); |
| } |
| |
| private boolean isLocal(FunctionNode function, Symbol symbol) { |
| final FunctionNode definingFn = lc.getDefiningFunction(symbol); |
| // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local |
| return definingFn == null || definingFn == function; |
| } |
| |
| @Override |
| public boolean enterASSIGN(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN(final BinaryNode binaryNode) { |
| return leaveAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_ADD(final BinaryNode binaryNode) { |
| final Expression lhs = binaryNode.lhs(); |
| final Expression rhs = binaryNode.rhs(); |
| |
| final Type widest = Type.widest(lhs.getType(), rhs.getType()); |
| //Type.NUMBER if we can't prove that the add doesn't overflow. todo |
| return leaveSelfModifyingAssignmentNode(binaryNode, widest.isNumeric() ? Type.NUMBER : Type.OBJECT); |
| } |
| |
| @Override |
| public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_DIV(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_MOD(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_MUL(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_SAR(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_SHL(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_SHR(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { |
| return enterAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveASSIGN_SUB(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode); |
| } |
| |
| @Override |
| public Node leaveBIT_AND(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.INT)); |
| } |
| |
| @Override |
| public Node leaveBIT_OR(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.INT)); |
| } |
| |
| @Override |
| public Node leaveBIT_XOR(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.INT)); |
| } |
| |
| @Override |
| public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { |
| return end(ensureSymbol(binaryNode.rhs().getType(), binaryNode)); |
| } |
| |
| @Override |
| public Node leaveCOMMALEFT(final BinaryNode binaryNode) { |
| return end(ensureSymbol(binaryNode.lhs().getType(), binaryNode)); |
| } |
| |
| @Override |
| public Node leaveDIV(final BinaryNode binaryNode) { |
| return leaveBinaryArithmetic(binaryNode); |
| } |
| |
| private Node leaveCmp(final BinaryNode binaryNode) { |
| ensureTypeNotUnknown(binaryNode.lhs()); |
| ensureTypeNotUnknown(binaryNode.rhs()); |
| |
| return end(ensureSymbol(Type.BOOLEAN, binaryNode)); |
| } |
| |
| private Node coerce(final BinaryNode binaryNode, final Type operandType, final Type destType) { |
| // TODO we currently don't support changing inferred type based on uses, only on |
| // definitions. we would need some additional logic. We probably want to do that |
| // in the future, if e.g. a specialized method gets parameter that is only used |
| // as, say, an int : function(x) { return x & 4711 }, and x is not defined in |
| // the function. to make this work, uncomment the following two type inferences |
| // and debug. |
| //newType(binaryNode.lhs().getSymbol(), operandType); |
| //newType(binaryNode.rhs().getSymbol(), operandType); |
| return ensureSymbol(destType, binaryNode); |
| } |
| |
| private Node coerce(final BinaryNode binaryNode, final Type type) { |
| return coerce(binaryNode, type, type); |
| } |
| |
| //leave a binary node and inherit the widest type of lhs , rhs |
| private Node leaveBinaryArithmetic(final BinaryNode binaryNode) { |
| assert !Compiler.shouldUseIntegerArithmetic(); |
| return end(coerce(binaryNode, Type.NUMBER)); |
| } |
| |
| @Override |
| public Node leaveEQ(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveEQ_STRICT(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveGE(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveGT(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveIN(final BinaryNode binaryNode) { |
| return leaveBinaryRuntimeOperator(binaryNode, Request.IN); |
| } |
| |
| @Override |
| public Node leaveINSTANCEOF(final BinaryNode binaryNode) { |
| return leaveBinaryRuntimeOperator(binaryNode, Request.INSTANCEOF); |
| } |
| |
| private Node leaveBinaryRuntimeOperator(final BinaryNode binaryNode, final Request request) { |
| try { |
| // Don't do a full RuntimeNode.accept, as we don't want to double-visit the binary node operands |
| return leaveRuntimeNode(new RuntimeNode(binaryNode, request)); |
| } finally { |
| end(binaryNode); |
| } |
| } |
| |
| @Override |
| public Node leaveLE(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveLT(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveMOD(final BinaryNode binaryNode) { |
| return leaveBinaryArithmetic(binaryNode); |
| } |
| |
| @Override |
| public Node leaveMUL(final BinaryNode binaryNode) { |
| return leaveBinaryArithmetic(binaryNode); |
| } |
| |
| @Override |
| public Node leaveNE(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveNE_STRICT(final BinaryNode binaryNode) { |
| return leaveCmp(binaryNode); |
| } |
| |
| @Override |
| public Node leaveOR(final BinaryNode binaryNode) { |
| return end(ensureSymbol(Type.OBJECT, binaryNode)); |
| } |
| |
| @Override |
| public Node leaveSAR(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.INT)); |
| } |
| |
| @Override |
| public Node leaveSHL(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.INT)); |
| } |
| |
| @Override |
| public Node leaveSHR(final BinaryNode binaryNode) { |
| return end(coerce(binaryNode, Type.LONG)); |
| } |
| |
| @Override |
| public Node leaveSUB(final BinaryNode binaryNode) { |
| return leaveBinaryArithmetic(binaryNode); |
| } |
| |
| @Override |
| public Node leaveForNode(final ForNode forNode) { |
| if (forNode.isForIn()) { |
| forNode.setIterator(newInternal(lc.getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.typeFor(ITERATOR_PREFIX.type()))); //NASHORN-73 |
| /* |
| * Iterators return objects, so we need to widen the scope of the |
| * init variable if it, for example, has been assigned double type |
| * see NASHORN-50 |
| */ |
| newType(forNode.getInit().getSymbol(), Type.OBJECT); |
| } |
| |
| end(forNode); |
| |
| return forNode; |
| } |
| |
| @Override |
| public Node leaveTernaryNode(final TernaryNode ternaryNode) { |
| final Expression trueExpr = ternaryNode.getTrueExpression(); |
| final Expression falseExpr = ternaryNode.getFalseExpression(); |
| |
| ensureTypeNotUnknown(trueExpr); |
| ensureTypeNotUnknown(falseExpr); |
| |
| final Type type = Type.widest(trueExpr.getType(), falseExpr.getType()); |
| return end(ensureSymbol(type, ternaryNode)); |
| } |
| |
| private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { |
| final Class<?> type = cc.type(); |
| // Must not call this method for constants with no explicit types; use the one with (..., Type) signature instead. |
| assert type != null; |
| initCompileConstant(cc, block, flags, Type.typeFor(type)); |
| } |
| |
| private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags, final Type type) { |
| final Symbol symbol = defineSymbol(block, cc.symbolName(), flags); |
| symbol.setTypeOverride(type); |
| symbol.setNeedsSlot(true); |
| } |
| |
| /** |
| * Initialize parameters for function node. This may require specializing |
| * types if a specialization profile is known |
| * |
| * @param functionNode the function node |
| */ |
| private void initParameters(final FunctionNode functionNode, final Block body) { |
| int pos = 0; |
| for (final IdentNode param : functionNode.getParameters()) { |
| addLocalDef(param.getName()); |
| |
| final Type callSiteParamType = functionNode.getHints().getParameterType(pos); |
| int flags = IS_PARAM; |
| if (callSiteParamType != null) { |
| LOG.info("Param ", param, " has a callsite type ", callSiteParamType, ". Using that."); |
| flags |= Symbol.IS_SPECIALIZED_PARAM; |
| } |
| |
| final Symbol paramSymbol = defineSymbol(body, param.getName(), flags); |
| assert paramSymbol != null; |
| |
| newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType); |
| |
| LOG.info("Initialized param ", pos, "=", paramSymbol); |
| pos++; |
| } |
| |
| } |
| |
| /** |
| * This has to run before fix assignment types, store any type specializations for |
| * paramters, then turn then to 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 int nparams = functionNode.getParameters().size(); |
| |
| int specialize = 0; |
| int pos = 0; |
| for (final IdentNode param : functionNode.getParameters()) { |
| final Symbol paramSymbol = functionNode.getBody().getExistingSymbol(param.getName()); |
| assert paramSymbol != null; |
| assert paramSymbol.isParam(); |
| newParams.add((IdentNode)param.setSymbol(lc, paramSymbol)); |
| |
| assert paramSymbol != null; |
| Type type = functionNode.getHints().getParameterType(pos); |
| if (type == null) { |
| type = Type.OBJECT; |
| } |
| |
| // if we know that a parameter is only used as a certain type throughout |
| // this function, we can tell the runtime system that no matter what the |
| // call site is, use this information: |
| // we also need more than half of the parameters to be specializable |
| // for the heuristic to be worth it, and we need more than one use of |
| // the parameter to consider it, i.e. function(x) { call(x); } doens't count |
| if (paramSymbol.getUseCount() > 1 && !paramSymbol.getSymbolType().isObject()) { |
| LOG.finest("Parameter ", param, " could profit from specialization to ", paramSymbol.getSymbolType()); |
| specialize++; |
| } |
| |
| newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType())); |
| |
| // parameters should not be slots for a function that uses variable arity signature |
| if (isVarArg) { |
| paramSymbol.setNeedsSlot(false); |
| } |
| |
| pos++; |
| } |
| |
| FunctionNode newFunctionNode = functionNode; |
| |
| if (nparams == 0 || (specialize * 2) < nparams) { |
| newFunctionNode = newFunctionNode.clearSnapshot(lc); |
| } |
| |
| return newFunctionNode.setParameters(lc, newParams); |
| } |
| |
| /** |
| * Move any properties from a global map into the scope of this method |
| * @param block the function node body for which to init scope vars |
| */ |
| private void initFromPropertyMap(final Block block) { |
| // For a script, add scope symbols as defined in the property map |
| |
| final PropertyMap map = Context.getGlobalMap(); |
| |
| for (final Property property : map.getProperties()) { |
| final String key = property.getKey(); |
| final Symbol symbol = defineSymbol(block, key, IS_GLOBAL); |
| newType(symbol, Type.OBJECT); |
| LOG.info("Added global symbol from property map ", symbol); |
| } |
| } |
| |
| private static void ensureTypeNotUnknown(final Expression node) { |
| |
| final Symbol symbol = node.getSymbol(); |
| |
| LOG.info("Ensure type not unknown for: ", symbol); |
| |
| /* |
| * Note that not just unknowns, but params need to be blown |
| * up to objects, because we can have something like |
| * |
| * function f(a) { |
| * var b = ~a; //b and a are inferred to be int |
| * return b; |
| * } |
| * |
| * In this case, it would be correct to say that "if you have |
| * an int at the callsite, just pass it". |
| * |
| * However |
| * |
| * function f(a) { |
| * var b = ~a; //b and a are inferred to be int |
| * return b == 17; //b is still inferred to be int. |
| * } |
| * |
| * can be called with f("17") and if we assume that b is an |
| * int and don't blow it up to an object in the comparison, we |
| * are screwed. I hate JavaScript. |
| * |
| * This check has to be done for any operation that might take |
| * objects as parameters, for example +, but not *, which is known |
| * to coerce types into doubles |
| */ |
| if (node.getType().isUnknown() || (symbol.isParam() && !symbol.isSpecializedParam())) { |
| newType(symbol, Type.OBJECT); |
| symbol.setCanBeUndefined(); |
| } |
| } |
| |
| private static Symbol pseudoSymbol(final String name) { |
| return new Symbol(name, 0, Type.OBJECT); |
| } |
| |
| private Symbol exceptionSymbol() { |
| return newInternal(lc.getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(EXCEPTION_PREFIX.type())); |
| } |
| |
| /** |
| * Return the type that arithmetic ops should use. Until we have implemented better type |
| * analysis (range based) or overflow checks that are fast enough for int arithmetic, |
| * this is the number type |
| * @return the arithetic type |
| */ |
| private static Type arithType() { |
| return Compiler.shouldUseIntegerArithmetic() ? Type.INT : Type.NUMBER; |
| } |
| |
| /** |
| * If types have changed, we can have failed to update vars. For example |
| * |
| * var x = 17; //x is int |
| * x = "apa"; //x is object. This will be converted fine |
| * |
| * @param functionNode |
| */ |
| private FunctionNode finalizeTypes(final FunctionNode functionNode) { |
| final Set<Node> changed = new HashSet<>(); |
| FunctionNode currentFunctionNode = functionNode; |
| do { |
| changed.clear(); |
| final FunctionNode newFunctionNode = (FunctionNode)currentFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
| |
| private Expression widen(final Expression node, final Type to) { |
| if (node instanceof LiteralNode) { |
| return node; |
| } |
| Type from = node.getType(); |
| if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) { |
| LOG.fine("Had to post pass widen '", node, "' ", Debug.id(node), " from ", node.getType(), " to ", to); |
| Symbol symbol = node.getSymbol(); |
| if(symbol.isShared() && symbol.wouldChangeType(to)) { |
| symbol = temporarySymbols.getTypedTemporarySymbol(to); |
| } |
| newType(symbol, to); |
| final Expression newNode = node.setSymbol(lc, symbol); |
| changed.add(newNode); |
| return newNode; |
| } |
| return node; |
| } |
| |
| @Override |
| public boolean enterFunctionNode(final FunctionNode node) { |
| return !node.isLazy(); |
| } |
| |
| /** |
| * Eg. |
| * |
| * var d = 17; |
| * var e; |
| * e = d; //initially typed as int for node type, should retype as double |
| * e = object; |
| * |
| * var d = 17; |
| * var e; |
| * e -= d; //initially type number, should number remain with a final conversion supplied by Store. ugly, but the computation result of the sub is numeric |
| * e = object; |
| * |
| */ |
| @SuppressWarnings("fallthrough") |
| @Override |
| public Node leaveBinaryNode(final BinaryNode binaryNode) { |
| final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); |
| BinaryNode newBinaryNode = binaryNode; |
| switch (binaryNode.tokenType()) { |
| default: |
| if (!binaryNode.isAssignment() || binaryNode.isSelfModifying()) { |
| break; |
| } |
| newBinaryNode = newBinaryNode.setLHS(widen(newBinaryNode.lhs(), widest)); |
| case ADD: |
| newBinaryNode = (BinaryNode)widen(newBinaryNode, widest); |
| } |
| return newBinaryNode; |
| } |
| }); |
| lc.replace(currentFunctionNode, newFunctionNode); |
| currentFunctionNode = newFunctionNode; |
| } while (!changed.isEmpty()); |
| return currentFunctionNode; |
| } |
| |
| private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode) { |
| return leaveSelfModifyingAssignmentNode(binaryNode, binaryNode.getWidestOperationType()); |
| } |
| |
| private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type destType) { |
| //e.g. for -=, Number, no wider, destType (binaryNode.getWidestOperationType()) is the coerce type |
| final Expression lhs = binaryNode.lhs(); |
| |
| newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType |
| // ensureSymbol(destType, binaryNode); //for OP= nodes, the node can carry a narrower types than its lhs rhs. This is perfectly fine |
| |
| return end(ensureSymbol(destType, binaryNode)); |
| } |
| |
| private Expression ensureSymbol(final Type type, final Expression expr) { |
| LOG.info("New TEMPORARY added to ", lc.getCurrentFunction().getName(), " type=", type); |
| return temporarySymbols.ensureSymbol(lc, type, expr); |
| } |
| |
| private Symbol newInternal(final String name, final Type type) { |
| final Symbol iter = defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL); |
| iter.setType(type); // NASHORN-73 |
| return iter; |
| } |
| |
| private static void newType(final Symbol symbol, final Type type) { |
| final Type oldType = symbol.getSymbolType(); |
| symbol.setType(type); |
| |
| if (symbol.getSymbolType() != oldType) { |
| LOG.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")"); |
| } |
| |
| if (symbol.isParam()) { |
| symbol.setType(type); |
| LOG.info("Param type change ", symbol); |
| } |
| } |
| |
| private void pushLocalsFunction() { |
| localDefs.push(new HashSet<String>()); |
| localUses.push(new HashSet<String>()); |
| } |
| |
| private void pushLocalsBlock() { |
| localDefs.push(new HashSet<>(localDefs.peek())); |
| localUses.push(new HashSet<>(localUses.peek())); |
| } |
| |
| private void popLocals() { |
| localDefs.pop(); |
| localUses.pop(); |
| } |
| |
| private boolean isLocalDef(final String name) { |
| return localDefs.peek().contains(name); |
| } |
| |
| private void addLocalDef(final String name) { |
| LOG.info("Adding local def of symbol: '", name, "'"); |
| localDefs.peek().add(name); |
| } |
| |
| private void removeLocalDef(final String name) { |
| LOG.info("Removing local def of symbol: '", name, "'"); |
| localDefs.peek().remove(name); |
| } |
| |
| private boolean isLocalUse(final String name) { |
| return localUses.peek().contains(name); |
| } |
| |
| private void addLocalUse(final String name) { |
| LOG.info("Adding local use of symbol: '", name, "'"); |
| localUses.peek().add(name); |
| } |
| |
| /** |
| * Pessimistically promote all symbols in current function node to Object types |
| * This is done when the function contains unevaluated black boxes such as |
| * lazy sub-function nodes that have not been compiled. |
| * |
| * @param body body for the function node we are leaving |
| */ |
| private static void objectifySymbols(final Block body) { |
| body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { |
| private void toObject(final Block block) { |
| for (final Symbol symbol : block.getSymbols()) { |
| if (!symbol.isTemp()) { |
| newType(symbol, Type.OBJECT); |
| } |
| } |
| } |
| |
| @Override |
| public boolean enterBlock(final Block block) { |
| toObject(block); |
| return true; |
| } |
| |
| @Override |
| public boolean enterFunctionNode(final FunctionNode node) { |
| return false; |
| } |
| }); |
| } |
| |
| private static String name(final Node node) { |
| final String cn = node.getClass().getName(); |
| int lastDot = cn.lastIndexOf('.'); |
| if (lastDot == -1) { |
| return cn; |
| } |
| return cn.substring(lastDot + 1); |
| } |
| |
| 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; |
| } |
| |
| 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(node instanceof Statement) { |
| // If we're done with a statement, all temporaries can be reused. |
| temporarySymbols.reuse(); |
| } |
| 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 Expression) { |
| final Symbol symbol = ((Expression)node).getSymbol(); |
| if (symbol == null) { |
| sb.append(" <NO SYMBOL>"); |
| } else { |
| sb.append(" <symbol=").append(symbol).append('>'); |
| } |
| } |
| |
| LOG.unindent(); |
| LOG.info(sb); |
| } |
| |
| return node; |
| } |
| } |