Assign(Node assignNode, Node nameNode, boolean isPropertyAssign) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); this.assignNode = assignNode; this.nameNode = nameNode; this.isPropertyAssign = isPropertyAssign; this.mayHaveSecondarySideEffects = assignNode.getParent().getType() != Token.EXPR_RESULT || NodeUtil.mayHaveSideEffects(assignNode.getFirstChild()) || NodeUtil.mayHaveSideEffects(assignNode.getLastChild()); }
/** Remove all the following parameters without side-effects */ private void tryRemoveAllFollowingArgs(Node function, final int argIndex) { Definition definition = getFunctionDefinition(function); for (UseSite site : defFinder.getUseSites(definition)) { if (!isModifiableCallSite(site)) { continue; } Node arg = getArgumentForCallOrNewOrDotCall(site, argIndex + 1); while (arg != null) { if (!NodeUtil.mayHaveSideEffects(arg)) { toRemove.add(arg); } arg = arg.getNext(); } } }
/** * Remove all references to a parameter if possible otherwise simplify the side-effect free * parameters. */ private void tryRemoveArgFromCallSites(Node function, int argIndex, boolean canModifyAllSites) { Definition definition = getFunctionDefinition(function); for (UseSite site : defFinder.getUseSites(definition)) { if (isModifiableCallSite(site)) { Node arg = getArgumentForCallOrNewOrDotCall(site, argIndex); if (arg != null) { Node argParent = arg.getParent(); // Even if we can't change the signature in general we can always // remove an unused value off the end of the parameter list. if (canModifyAllSites || (arg.getNext() == null && !NodeUtil.mayHaveSideEffects(arg, compiler))) { toRemove.add(arg); } else { // replace the node in the arg with 0 if (!NodeUtil.mayHaveSideEffects(arg, compiler) && (arg.getType() != Token.NUMBER || arg.getDouble() != 0)) { toReplaceWithZero.add(arg); } } } } } }
/** * Remove all references to a parameter, otherwise simplify the known references. * * @return Whether all the references were removed. */ private boolean canRemoveArgFromCallSites(Node function, int argIndex) { Definition definition = getFunctionDefinition(function); // Check all the call sites. for (UseSite site : defFinder.getUseSites(definition)) { if (isModifiableCallSite(site)) { Node arg = getArgumentForCallOrNewOrDotCall(site, argIndex); // TODO(johnlenz): try to remove parameters with side-effects by // decomposing the call expression. if (arg != null && NodeUtil.mayHaveSideEffects(arg, compiler)) { return false; } } else { return false; } } return true; }
/** * Removes any vars in the scope that were not referenced. Removes any assignments to those * variables as well. */ private void removeUnreferencedVars() { CodingConvention convention = codingConvention; for (Iterator<Var> it = maybeUnreferenced.iterator(); it.hasNext(); ) { Var var = it.next(); // Remove calls to inheritance-defining functions where the unreferenced // class is the subclass. for (Node exprCallNode : inheritsCalls.get(var)) { NodeUtil.removeChild(exprCallNode.getParent(), exprCallNode); compiler.reportCodeChange(); } // Regardless of what happens to the original declaration, // we need to remove all assigns, because they may contain references // to other unreferenced variables. removeAllAssigns(var); compiler.addToDebugLog("Unreferenced var: " + var.name); Node nameNode = var.nameNode; Node toRemove = nameNode.getParent(); Node parent = toRemove.getParent(); Preconditions.checkState( toRemove.getType() == Token.VAR || toRemove.getType() == Token.FUNCTION || toRemove.getType() == Token.LP && parent.getType() == Token.FUNCTION, "We should only declare vars and functions and function args"); if (toRemove.getType() == Token.LP && parent.getType() == Token.FUNCTION) { // Don't remove function arguments here. That's a special case // that's taken care of in removeUnreferencedFunctionArgs. } else if (NodeUtil.isFunctionExpression(toRemove)) { if (!preserveFunctionExpressionNames) { toRemove.getFirstChild().setString(""); compiler.reportCodeChange(); } // Don't remove bleeding functions. } else if (parent != null && parent.getType() == Token.FOR && parent.getChildCount() < 4) { // foreach iterations have 3 children. Leave them alone. } else if (toRemove.getType() == Token.VAR && nameNode.hasChildren() && NodeUtil.mayHaveSideEffects(nameNode.getFirstChild())) { // If this is a single var declaration, we can at least remove the // declaration itself and just leave the value, e.g., // var a = foo(); => foo(); if (toRemove.getChildCount() == 1) { parent.replaceChild(toRemove, new Node(Token.EXPR_RESULT, nameNode.removeFirstChild())); compiler.reportCodeChange(); } } else if (toRemove.getType() == Token.VAR && toRemove.getChildCount() > 1) { // For var declarations with multiple names (i.e. var a, b, c), // only remove the unreferenced name toRemove.removeChild(nameNode); compiler.reportCodeChange(); } else if (parent != null) { NodeUtil.removeChild(parent, toRemove); compiler.reportCodeChange(); } } }
/** * Traverses everything in the current scope and marks variables that are referenced. * * <p>During traversal, we identify subtrees that will only be referenced if their enclosing * variables are referenced. Instead of traversing those subtrees, we create a continuation for * them, and traverse them lazily. */ private void traverseNode(Node n, Node parent, Scope scope) { int type = n.getType(); Var var = null; switch (type) { case Token.FUNCTION: // If this function is a removable var, then create a continuation // for it instead of traversing immediately. if (NodeUtil.isFunctionDeclaration(n)) { var = scope.getVar(n.getFirstChild().getString()); } if (var != null && isRemovableVar(var)) { continuations.put(var, new Continuation(n, scope)); } else { traverseFunction(n, scope); } return; case Token.ASSIGN: Assign maybeAssign = Assign.maybeCreateAssign(n); if (maybeAssign != null) { // Put this in the assign map. It might count as a reference, // but we won't know that until we have an index of all assigns. var = scope.getVar(maybeAssign.nameNode.getString()); if (var != null) { assignsByVar.put(var, maybeAssign); assignsByNode.put(maybeAssign.nameNode, maybeAssign); if (isRemovableVar(var) && !maybeAssign.mayHaveSecondarySideEffects) { // If the var is unreferenced and performing this assign has // no secondary side effects, then we can create a continuation // for it instead of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } } break; case Token.CALL: // Look for calls to inheritance-defining calls (such as goog.inherits). SubclassRelationship subclassRelationship = codingConvention.getClassesDefinedByCall(n); if (subclassRelationship != null) { Var subclassVar = scope.getVar(subclassRelationship.subclassName); // Don't try to track the inheritance calls for non-globals. It would // be more correct to only not track when the subclass does not // reference a constructor, but checking that it is a global is // easier and mostly the same. if (subclassVar != null && subclassVar.isGlobal() && !referenced.contains(subclassVar)) { // Save a reference to the EXPR node. inheritsCalls.put(subclassVar, parent); continuations.put(subclassVar, new Continuation(n, scope)); return; } } break; case Token.NAME: var = scope.getVar(n.getString()); if (parent.getType() == Token.VAR) { Node value = n.getFirstChild(); if (value != null && var != null && isRemovableVar(var) && !NodeUtil.mayHaveSideEffects(value)) { // If the var is unreferenced and creating its value has no side // effects, then we can create a continuation for it instead // of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } else { // If arguments is escaped, we just assume the worst and continue // on all the parameters. if ("arguments".equals(n.getString()) && scope.isLocal()) { Node lp = scope.getRootNode().getFirstChild().getNext(); for (Node a = lp.getFirstChild(); a != null; a = a.getNext()) { markReferencedVar(scope.getVar(a.getString())); } } // All name references that aren't declarations or assigns // are references to other vars. if (var != null) { // If that var hasn't already been marked referenced, then // start tracking it. If this is an assign, do nothing // for now. if (isRemovableVar(var)) { if (!assignsByNode.containsKey(n)) { markReferencedVar(var); } } else { markReferencedVar(var); } } } break; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { traverseNode(c, n, scope); } }