/** @return Whether the name is used in a way that might be a candidate for inlining. */ static boolean isCandidateUsage(Node name) { Node parent = name.getParent(); Preconditions.checkState(name.isName()); if (parent.isVar() || parent.isFunction()) { // This is a declaration. Duplicate declarations are handle during // function candidate gathering. return true; } if (parent.isCall() && parent.getFirstChild() == name) { // This is a normal reference to the function. return true; } // Check for a ".call" to the named function: // CALL // GETPROP/GETELEM // NAME // STRING == "call" // This-Value // Function-parameter-1 // ... if (NodeUtil.isGet(parent) && name == parent.getFirstChild() && name.getNext().isString() && name.getNext().getString().equals("call")) { Node gramps = name.getAncestor(2); if (gramps.isCall() && gramps.getFirstChild() == parent) { // Yep, a ".call". return true; } } return false; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { // Function calls case Token.CALL: Node child = n.getFirstChild(); String name = null; // NOTE: The normalization pass insures that local names do not // collide with global names. if (child.isName()) { name = child.getString(); } else if (child.isFunction()) { name = anonFunctionMap.get(child); } else if (NodeUtil.isFunctionObjectCall(n)) { Preconditions.checkState(NodeUtil.isGet(child)); Node fnIdentifingNode = child.getFirstChild(); if (fnIdentifingNode.isName()) { name = fnIdentifingNode.getString(); } else if (fnIdentifingNode.isFunction()) { name = anonFunctionMap.get(fnIdentifingNode); } } if (name != null) { FunctionState fs = functionMap.get(name); // Only visit call-sites for functions that can be inlined. if (fs != null) { callback.visitCallSite(t, n, fs); } } break; } }
/** If this is an assign to a variable or its property, return it. Otherwise, return null. */ static Assign maybeCreateAssign(Node assignNode) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); // Skip one level of GETPROPs or GETELEMs. // // Don't skip more than one level, because then we get into // situations where assigns to properties of properties will always // trigger side-effects, and the variable they're on cannot be removed. boolean isPropAssign = false; Node current = assignNode.getFirstChild(); if (NodeUtil.isGet(current)) { current = current.getFirstChild(); isPropAssign = true; if (current.getType() == Token.GETPROP && current.getLastChild().getString().equals("prototype")) { // Prototype properties sets should be considered like normal // property sets. current = current.getFirstChild(); } } if (current.getType() == Token.NAME) { return new Assign(assignNode, current, isPropAssign); } return null; }
/** * Check that Node n is a call to one of the jQuery.extend methods that we can expand. Valid calls * are single argument calls where the first argument is an object literal or two argument calls * where the first argument is a name and the second argument is an object literal. */ public static boolean isJqueryExtendCall(Node n, String qname, AbstractCompiler compiler) { if (JQUERY_EXTEND_NAMES.contains(qname)) { Node firstArgument = n.getNext(); if (firstArgument == null) { return false; } Node secondArgument = firstArgument.getNext(); if ((firstArgument.isObjectLit() && secondArgument == null) || (firstArgument.isName() || NodeUtil.isGet(firstArgument) && !NodeUtil.mayHaveSideEffects(firstArgument, compiler) && secondArgument != null && secondArgument.isObjectLit() && secondArgument.getNext() == null)) { return true; } } return false; }
/** * There are two types of calls we are interested in calls without explicit "this" values (what * we are call "free" calls) and direct call to eval. */ private static void annotateCalls(Node n) { Preconditions.checkState(n.isCall()); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); // ignore cast nodes. while (first.isCast()) { first = first.getFirstChild(); } if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.isName() && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } }
/** * @param n The node in question. * @param cfgNode The node to add * @param conditional true if the definition is not always executed. */ private void computeMustDef(Node n, Node cfgNode, MustDef output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.WHILE: case Token.DO: case Token.IF: computeMustDef(NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMustDef(NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (NodeUtil.isVar(lhs)) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (NodeUtil.isName(lhs)) { addToDefIfLocal(lhs.getString(), cfgNode, rhs, output); } } return; case Token.AND: case Token.OR: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.HOOK: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeMustDef(c.getFirstChild(), cfgNode, output, conditional); addToDefIfLocal(c.getString(), conditional ? null : cfgNode, c.getFirstChild(), output); } } return; default: if (NodeUtil.isAssignmentOp(n)) { if (NodeUtil.isName(n.getFirstChild())) { Node name = n.getFirstChild(); computeMustDef(name.getNext(), cfgNode, output, conditional); addToDefIfLocal( name.getString(), conditional ? null : cfgNode, n.getLastChild(), output); return; } else if (NodeUtil.isGet(n.getFirstChild())) { // Treat all assignments to arguments as redefining the // parameters itself. Node obj = n.getFirstChild().getFirstChild(); if (NodeUtil.isName(obj) && "arguments".equals(obj.getString())) { // TODO(user): More accuracy can be introduced // ie: We know exactly what arguments[x] is if x is a constant // number. escapeParameters(output); } } } if (NodeUtil.isName(n) && "arguments".equals(n.getString())) { escapeParameters(output); } // DEC and INC actually defines the variable. if (n.getType() == Token.DEC || n.getType() == Token.INC) { Node target = n.getFirstChild(); if (NodeUtil.isName(target)) { addToDefIfLocal(target.getString(), conditional ? null : cfgNode, null, output); return; } } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeMustDef(c, cfgNode, output, conditional); } } }
private Node tryFoldObjectPropAccess(Node n, Node left, Node right) { Preconditions.checkArgument(NodeUtil.isGet(n)); if (!left.isObjectLit() || !right.isString()) { return n; } if (NodeUtil.isAssignmentTarget(n)) { // If GETPROP/GETELEM is used as assignment target the object literal is // acting as a temporary we can't fold it here: // "{a:x}.a += 1" is not "x += 1" return n; } // find the last definition in the object literal Node key = null; Node value = null; for (Node c = left.getFirstChild(); c != null; c = c.getNext()) { if (c.getString().equals(right.getString())) { switch (c.getType()) { case SETTER_DEF: continue; case GETTER_DEF: case STRING_KEY: if (value != null && mayHaveSideEffects(value)) { // The previously found value had side-effects return n; } key = c; value = key.getFirstChild(); break; default: throw new IllegalStateException(); } } else if (mayHaveSideEffects(c.getFirstChild())) { // We don't handle the side-effects here as they might need a temporary // or need to be reordered. return n; } } // Didn't find a definition of the name in the object literal, it might // be coming from the Object prototype if (value == null) { return n; } if (value.isFunction() && NodeUtil.referencesThis(value)) { // 'this' may refer to the object we are trying to remove return n; } Node replacement = value.detachFromParent(); if (key.isGetterDef()) { replacement = IR.call(replacement); replacement.putBooleanProp(Node.FREE_CALL, true); } n.getParent().replaceChild(n, replacement); reportCodeChange(); return n; }