Buggy code:
//* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http: * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *//package com.google.javascript.jscomp;import com.google.common.base.Preconditions;import com.google.common.base.Predicate;import com.google.common.collect.ImmutableSet;import com.google.common.collect.LinkedListMultimap;import com.google.common.collect.ListMultimap;import com.google.common.collect.Lists;import com.google.common.collect.Maps;import com.google.common.collect.Sets;import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;import com.google.javascript.jscomp.GatherSideEffectSubexpressionsCallback.GetReplacementSideEffectSubexpressions;import com.google.javascript.jscomp.GatherSideEffectSubexpressionsCallback.SideEffectAccumulator;import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;import com.google.javascript.jscomp.NodeTraversal.Callback;import com.google.javascript.jscomp.Scope.Var;import com.google.javascript.jscomp.graph.DiGraph;import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;import com.google.javascript.jscomp.graph.FixedPointGraphTraversal;import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback;import com.google.javascript.jscomp.graph.LinkedDirectedGraph;import com.google.javascript.rhino.IR;import com.google.javascript.rhino.Node;import com.google.javascript.rhino.Token;import java.util.Collections;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;//** * This pass identifies all global names, simple (e.g. <code>a<//code>) or * qualified (e.g. <code>a.b.c<//code>), and the dependencies between them, then * removes code associated with unreferenced names. It starts by assuming that * only externally accessible names (e.g. <code>window<//code>) are referenced, * then iteratively marks additional names as referenced (e.g. <code>Foo<//code> * in <code>window['foo'] = new Foo();<//code>). This makes it possible to * eliminate code containing circular references. * * <p>Qualified names can be defined using dotted or object literal syntax * (<code>a.b.c = x;<//code> or <code>a.b = {c: x};<//code>, respectively). * * <p>Removal of prototype classes is currently all or nothing. In other words, * prototype properties and methods are never individually removed. * * <p>Optionally generates pretty HTML output of data so that it is easy to * analyze dependencies. * * <p>Only operates on names defined in the global scope, but it would be easy * to extend the pass to names defined in local scopes. * * TODO(nicksantos): In the initial implementation of this pass, it was * important to understand namespaced names (e.g., that a.b is distinct from * a.b.c). Now that this pass comes after CollapseProperties, this is no longer * necessary. For now, I've changed so that {@code referenceParentNames} * creates a two-way reference between a.b and a.b.c, so that they're * effectively the same name. When someone has the time, we should completely * rip out all the logic that understands namespaces. * *//final class NameAnalyzer implements CompilerPass {  //** Reference to the JS compiler *//  private final AbstractCompiler compiler;  //** Map of all JS names found *//  private final Map<String, JsName> allNames = Maps.newTreeMap();  //** Reference dependency graph *//  private DiGraph<JsName, RefType> referenceGraph =      LinkedDirectedGraph.createWithoutAnnotations();  //**   * Map of name scopes - all children of the Node key have a dependency on the   * name value.   *   * If scopes.get(node).equals(name) && node2 is a child of node, then node2   * will not get executed unless name is referenced via a get operation   *//  private final ListMultimap<Node, NameInformation> scopes =      LinkedListMultimap.create();  //** Used to parse prototype names *//  private static final String PROTOTYPE_SUBSTRING = ".prototype.";  private static final int PROTOTYPE_SUBSTRING_LEN =      PROTOTYPE_SUBSTRING.length();  private static final int PROTOTYPE_SUFFIX_LEN = ".prototype".length();  //** Window root *//  private static final String WINDOW = "window";  //** Function class name *//  private static final String FUNCTION = "Function";  //** All of these refer to global scope. These can be moved to config *//  static final Set<String> DEFAULT_GLOBAL_NAMES = ImmutableSet.of(      "window", "goog.global");  //** Whether to remove unreferenced variables in main pass *//  private final boolean removeUnreferenced;  //** Names that refer to the global scope *//  private final Set<String> globalNames;  //** Ast change helper *//  private final AstChangeProxy changeProxy;  //** Names that are externally defined *//  private final Set<String> externalNames = Sets.newHashSet();  //** Name declarations or assignments, in post-order traversal order *//  private final List<RefNode> refNodes = Lists.newArrayList();  //**   * When multiple names in the global scope point to the same object, we   * call them aliases. Store a map from each alias name to the alias set.   *//  private final Map<String, AliasSet> aliases = Maps.newHashMap();  //**   * All the aliases in a program form a graph, where each global name is   * a node in the graph, and two names are connected if one directly aliases   * the other.   *   * An {@code AliasSet} represents a connected component in that graph. We do   * not explicitly track the graph--we just track the connected components.   *//  private static class AliasSet {    Set<String> names = Sets.newHashSet();        AliasSet(String name1, String name2) {      names.add(name1);      names.add(name2);    }  }  //**   * Relationship between the two names.   * Currently only two different reference types exists:   * goog.inherits class relations and all other references.   *//  private static enum RefType {    REGULAR,    INHERITANCE,  }  //**   * Callback that propagates reference information.   *//  private static class ReferencePropagationCallback      implements EdgeCallback<JsName, RefType> {    @Override    public boolean traverseEdge(JsName from,                                RefType callSite,                                JsName to) {      if (from.referenced && !to.referenced) {        to.referenced = true;        return true;      } else {        return false;      }    }  }  //**   * Class to hold information that can be determined from a node tree about a   * given name   *//  private static class NameInformation {    //** Fully qualified name *//    String name;    //** Whether the name is guaranteed to be externally referenceable *//    boolean isExternallyReferenceable = false;    //** Whether this name is a prototype function *//    boolean isPrototype = false;    //** Name of the prototype class, i.e. "a" if name is "a.prototype.b" *//    String prototypeClass = null;    //** Local name of prototype property i.e. "b" if name is "a.prototype.b" *//    String prototypeProperty = null;    //** Name of the super class of name *//    String superclass = null;    //** Whether this is a call that only affects the class definition *//    boolean onlyAffectsClassDef = false;  }  //**   * Struct to hold information about a fully qualified JS name   *//  private static class JsName implements Comparable<JsName> {    //** Fully qualified name *//    String name;    //** Name of prototype functions attached to this name *//    List<String> prototypeNames = Lists.newArrayList();    //** Whether this is an externally defined name *//    boolean externallyDefined = false;    //** Whether this node is referenced *//    boolean referenced = false;    //** Whether the name has descendants that are written to. *//    boolean hasWrittenDescendants = false;    //** Whether the name is used in a instanceof check *//    boolean hasInstanceOfReference = false;    //**     * Output the node as a string     *     * @return Node as a string     *//    @Override    public String toString() {      StringBuilder out = new StringBuilder();      out.append(name);      if (prototypeNames.size() > 0) {        out.append(" (CLASS)\n");        out.append(" - FUNCTIONS: ");        Iterator<String> pIter = prototypeNames.iterator();        while (pIter.hasNext()) {          out.append(pIter.next());          if (pIter.hasNext()) {            out.append(", ");          }        }      }      return out.toString();    }    @Override    public int compareTo(JsName rhs) {      return this.name.compareTo(rhs.name);    }  }  //**   * Interface to get information about and remove unreferenced names.   *//  interface RefNode {    JsName name();    void remove();  }  //**   * Class for nodes that reference a fully-qualified JS name. Fully qualified   * names are of form A or A.B (A.B.C, etc.). References can get the value or   * set the value of the JS name.   *//  private class JsNameRefNode implements RefNode {    //** JsName node for this reference *//    JsName name;    //**     * Top GETPROP or NAME or STRING [objlit key] node defining the name of     * this node     *//    @SuppressWarnings("unused")    Node node;    //**     * Parent node of the name access     * (ASSIGN, VAR, FUNCTION, OBJECTLIT, or CALL)     *//    Node parent;    //**     * Create a node that refers to a name     *     * @param name The name     * @param node The top node representing the name (GETPROP, NAME, STRING)     *//    JsNameRefNode(JsName name, Node node) {      this.name = name;      this.node = node;      this.parent = node.getParent();    }    @Override    public JsName name() {      return name;    }    @Override    public void remove() {                  Node containingNode = parent.getParent();      switch (parent.getType()) {        case Token.VAR:          Preconditions.checkState(parent.hasOneChild());          replaceWithRhs(containingNode, parent);          break;        case Token.FUNCTION:          replaceWithRhs(containingNode, parent);          break;        case Token.ASSIGN:          if (containingNode.isExprResult()) {            replaceWithRhs(containingNode.getParent(), containingNode);          } else {            replaceWithRhs(containingNode, parent);          }          break;        case Token.OBJECTLIT:                                        break;      }    }  }  //**   * Class for nodes that set prototype properties or methods.   *//  private class PrototypeSetNode extends JsNameRefNode {    //**     * Create a set node from the name & setter node     *     * @param name The name     * @param parent Parent node that assigns the expression (an ASSIGN)     *//    PrototypeSetNode(JsName name, Node parent) {      super(name, parent.getFirstChild());      Preconditions.checkState(parent.isAssign());    }    @Override public void remove() {      Node gramps = parent.getParent();      if (gramps.isExprResult()) {                changeProxy.removeChild(gramps.getParent(), gramps);      } else {                changeProxy.replaceWith(gramps, parent,                                parent.getLastChild().detachFromParent());      }    }  }  //**   * Base class for special reference nodes.   *//  private abstract class SpecialReferenceNode implements RefNode {    //** JsName node for the function *//    JsName name;    //** The CALL node *//    Node node;    //**     * Create a special reference node.     *     * @param name The name     * @param node The CALL node     *//    SpecialReferenceNode(JsName name, Node node) {      this.name = name;      this.node = node;    }    @Override    public JsName name() {      return name;    }    Node getParent() {      return node.getParent();    }    Node getGramps() {      return node.getParent() == null ? null : node.getParent().getParent();    }  }  //**   * Class for nodes that are function calls that may change a function's   * prototype   *//  private class ClassDefiningFunctionNode extends SpecialReferenceNode {    //**     * Create a class defining function node from the name & setter node     *     * @param name The name     * @param node The CALL node     *//    ClassDefiningFunctionNode(JsName name, Node node) {      super(name, node);      Preconditions.checkState(node.isCall());    }    @Override    public void remove() {      Preconditions.checkState(node.isCall());      Node parent = getParent();      if (parent.isExprResult()) {        changeProxy.removeChild(getGramps(), parent);      } else {        changeProxy.replaceWith(parent, node, IR.voidNode(IR.number(0)));      }    }  }  //**   * Class for nodes that check instanceof   *//  private class InstanceOfCheckNode extends SpecialReferenceNode {    //**     * Create an instanceof node from the name and parent node     *     * @param name The name     * @param node The qualified name node     *//    InstanceOfCheckNode(JsName name, Node node) {      super(name, node);      Preconditions.checkState(node.isQualifiedName());      Preconditions.checkState(getParent().isInstanceOf());    }    @Override    public void remove() {      changeProxy.replaceWith(getGramps(), getParent(), IR.falseNode());    }  }  //**   * Walk through externs and mark nodes as externally declared if declared   *//  private class ProcessExternals extends AbstractPostOrderCallback {    @Override    public void visit(NodeTraversal t, Node n, Node parent) {      NameInformation ns = null;      if (NodeUtil.isVarDeclaration(n)) {        ns = createNameInformation(t, n);      } else if (NodeUtil.isFunctionDeclaration(n)) {        ns = createNameInformation(t, n.getFirstChild());      }      if (ns != null) {        JsName jsName = getName(ns.name, true);        jsName.externallyDefined = true;        externalNames.add(ns.name);      }    }  }  //**   * <p>Identifies all dependency scopes.   *   * <p>A dependency scope is a relationship between a node tree and a name that   * implies that the node tree will not execute (and thus can be eliminated) if   * the name is never referenced.   *   * <p>The entire parse tree is ultimately in a dependency scope relationship   * with <code>window<//code> (or an equivalent name for the global scope), but   * the goal here is to find finer-grained relationships. This callback creates   * dependency scopes for every assignment statement, variable declaration, and   * function call in the global scope.   *   * <p>Note that dependency scope node trees aren't necessarily disjoint.   * In the following code snippet, for example, the function definition   * forms a dependency scope with the name <code>f<//code> and the assignment   * inside the function forms a dependency scope with the name <code>x<//code>.   * <pre>   * var x; function f() { x = 1; }   * <//pre>   *//  private class FindDependencyScopes extends AbstractPostOrderCallback {    @Override    public void visit(NodeTraversal t, Node n, Node parent) {      if (!t.inGlobalScope()) {        return;      }      if (n.isAssign()) {        recordAssignment(t, n, n);        if (!NodeUtil.isImmutableResult(n.getLastChild())) {          recordConsumers(t, n, n);        }      } else if (NodeUtil.isVarDeclaration(n)) {        NameInformation ns = createNameInformation(t, n);        recordDepScope(n, ns);      } else if (NodeUtil.isFunctionDeclaration(n)) {        NameInformation ns = createNameInformation(t, n.getFirstChild());        recordDepScope(n, ns);      } else if (NodeUtil.isExprCall(n)) {        Node callNode = n.getFirstChild();        Node nameNode = callNode.getFirstChild();        NameInformation ns = createNameInformation(t, nameNode);        if (ns != null && ns.onlyAffectsClassDef) {          recordDepScope(n, ns);        }      }    }    private void recordConsumers(NodeTraversal t, Node n, Node recordNode) {      Node parent = n.getParent();      switch (parent.getType()) {        case Token.ASSIGN:          if (n == parent.getLastChild()) {            recordAssignment(t, parent, recordNode);          }          recordConsumers(t, parent, recordNode);          break;        case Token.NAME:          NameInformation ns = createNameInformation(t, parent);          recordDepScope(recordNode, ns);          break;        case Token.OR:          recordConsumers(t, parent, recordNode);          break;        case Token.AND:                            case Token.COMMA:        case Token.HOOK:          if (n != parent.getFirstChild()) {            recordConsumers(t, parent, recordNode);          }          break;      }    }    private void recordAssignment(NodeTraversal t, Node n, Node recordNode) {      Node nameNode = n.getFirstChild();      Node parent = n.getParent();      NameInformation ns = createNameInformation(t, nameNode);      if (ns != null) {        if (parent.isFor() && !NodeUtil.isForIn(parent)) {                                                                                                    if (parent.getFirstChild().getNext() != n) {            recordDepScope(recordNode, ns);          } else {            recordDepScope(nameNode, ns);          }      <START_BUG>  } else { <END_BUG>                                                                     recordDepScope(recordNode, ns);        }      }    }    //**     * Defines a dependency scope.     *//    private void recordDepScope(Node node, NameInformation name) {      Preconditions.checkNotNull(name);      scopes.put(node, name);    }  }  //**   * Create JsName objects for variable and function declarations in   * the global scope before computing name references.  In JavaScript   * it is legal to refer to variable and function names before the   * actual declaration.   *//  private class HoistVariableAndFunctionDeclarations      extends NodeTraversal.AbstractShallowCallback {    @Override    public void visit(NodeTraversal t, Node n, Node parent) {      if (NodeUtil.isVarDeclaration(n)) {        NameInformation ns = createNameInformation(t, n);        Preconditions.checkNotNull(ns, "NameInformation is null");        createName(ns.name);      } else if (NodeUtil.isFunctionDeclaration(n)) {        Node nameNode = n.getFirstChild();        NameInformation ns = createNameInformation(t, nameNode);        Preconditions.checkNotNull(ns, "NameInformation is null");        createName(nameNode.getString());      }    }  }  //**   * Identifies all declarations of global names and setter statements   * affecting global symbols (assignments to global names).   *   * All declarations and setters must be gathered in a single   * traversal and stored in traversal order so "removeUnreferenced"   * can perform modifications in traversal order.   *//  private class FindDeclarationsAndSetters extends AbstractPostOrderCallback {    @Override    public void visit(NodeTraversal t, Node n, Node parent) {            if (t.inGlobalScope()) {        if (NodeUtil.isVarDeclaration(n)) {          NameInformation ns = createNameInformation(t, n);          Preconditions.checkNotNull(ns);          recordSet(ns.name, n);        } else if (NodeUtil.isFunctionDeclaration(n)) {          Node nameNode = n.getFirstChild();          NameInformation ns = createNameInformation(t, nameNode);          if (ns != null) {            JsName nameInfo = getName(nameNode.getString(), true);            recordSet(nameInfo.name, nameNode);          }        } else if (NodeUtil.isObjectLitKey(n)) {          NameInformation ns = createNameInformation(t, n);          if (ns != null) {            recordSet(ns.name, n);          }        }      }            if (n.isAssign()) {        Node nameNode = n.getFirstChild();        NameInformation ns = createNameInformation(t, nameNode);        if (ns != null) {          if (ns.isPrototype) {            recordPrototypeSet(ns.prototypeClass, ns.prototypeProperty, n);          } else {            recordSet(ns.name, nameNode);          }        }      } else if (n.isCall()) {        Node nameNode = n.getFirstChild();        NameInformation ns = createNameInformation(t, nameNode);        if (ns != null && ns.onlyAffectsClassDef) {          JsName name = getName(ns.name, true);          refNodes.add(new ClassDefiningFunctionNode(name, n));        }      }    }    //**     * Records the assignment of a value to a global name.     *     * @param name Fully qualified name     * @param node The top node representing the name (GETPROP, NAME, or STRING     * [objlit key])     *//    private void recordSet(String name, Node node) {      JsName jsn = getName(name, true);      JsNameRefNode nameRefNode = new JsNameRefNode(jsn, node);      refNodes.add(nameRefNode);                  if (node.isGetElem()) {        recordWriteOnProperties(name);      } else if (name.indexOf('.') != -1) {        recordWriteOnProperties(name.substring(0, name.lastIndexOf('.')));      }    }    //**     * Records the assignment to a prototype property of a global name,     * if possible.     *     * @param className The name of the class.     * @param prototypeProperty The name of the prototype property.     * @param node The top node representing the name (GETPROP)     *//    private void recordPrototypeSet(String className, String prototypeProperty,        Node node) {      JsName name = getName(className, true);      name.prototypeNames.add(prototypeProperty);      refNodes.add(new PrototypeSetNode(name, node));      recordWriteOnProperties(className);    }    //**     * Record that the properties of this name have been written to.     *//    private void recordWriteOnProperties(String parentName) {      do {        JsName parent = getName(parentName, true);        if (parent.hasWrittenDescendants) {                              return;        } else {          parent.hasWrittenDescendants = true;        }        if (parentName.indexOf('.') == -1) {          return;        }        parentName = parentName.substring(0, parentName.lastIndexOf('.'));      } while(true);    }  }  private static final Predicate<Node> NON_LOCAL_RESULT_PREDICATE =      new Predicate<Node>() {        @Override        public boolean apply(Node input) {          if (input.isCall()) {            return false;          }                                        return true;        }      };  //**   * <p>Identifies all references between global names.   *   * <p>A reference from a name <code>f<//code> to a name <code>g<//code> means   * that if the name <code>f<//code> must be defined, then the name   * <code>g<//code> must also be defined. This would be the case if, for   * example, <code>f<//code> were a function that called <code>g<//code>.   *//  private class FindReferences implements Callback {    Set<Node> nodesToKeep;    FindReferences() {      nodesToKeep = Sets.newHashSet();    }    private void addAllChildren(Node n) {      nodesToKeep.add(n);      for (Node child = n.getFirstChild();           child != null;           child = child.getNext()) {        addAllChildren(child);      }    }    private void addSimplifiedChildren(Node n) {      NodeTraversal.traverse(          compiler, n,          new GatherSideEffectSubexpressionsCallback(              compiler, new NodeAccumulator()));    }    private void addSimplifiedExpression(Node n, Node parent) {      if (parent.isVar()) {        Node value = n.getFirstChild();        if (value != null) {          addSimplifiedChildren(value);        }      } else if (n.isAssign() &&          (parent.isExprResult() ||           parent.isFor() ||           parent.isReturn())) {        for (Node child : n.children()) {          addSimplifiedChildren(child);        }      } else if (n.isCall() &&                 parent.isExprResult()) {        addSimplifiedChildren(n);      } else {        addAllChildren(n);      }    }    @Override    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {      if (parent == null) {        return true;      }                              if (n.isFor()) {        if (!NodeUtil.isForIn(n)) {          Node decl = n.getFirstChild();          Node pred = decl.getNext();          Node step = pred.getNext();          addSimplifiedExpression(decl, n);          addSimplifiedExpression(pred, n);          addSimplifiedExpression(step, n);        } else {           Node decl = n.getFirstChild();          Node iter = decl.getNext();          addAllChildren(decl);          addAllChildren(iter);        }      }      if (parent.isVar() ||          parent.isExprResult() ||          parent.isReturn() ||          parent.isThrow()) {        addSimplifiedExpression(n, parent);      }      if ((parent.isIf() ||           parent.isWhile() ||           parent.isWith() ||           parent.isSwitch() ||           parent.isCase()) &&          parent.getFirstChild() == n) {        addAllChildren(n);      }      if (parent.isDo() && parent.getLastChild() == n) {        addAllChildren(n);      }      return true;    }    @Override    public void visit(NodeTraversal t, Node n, Node parent) {      if (!(n.isName() || (NodeUtil.isGet(n) && !parent.isGetProp()))) {                return;      }      NameInformation nameInfo = createNameInformation(t, n);      if (nameInfo == null) {                return;      }      if (nameInfo.onlyAffectsClassDef) {        if (nameInfo.superclass != null) {          recordReference(              nameInfo.name, nameInfo.superclass, RefType.INHERITANCE);        }                                String nodeName = n.getQualifiedName();        if (nodeName != null) {          recordReference(              nameInfo.name, nodeName, RefType.REGULAR);        }        return;      }      if (parent.isInstanceOf() &&          parent.getLastChild() == n &&                    n.isQualifiedName()) {        JsName checkedClass = getName(nameInfo.name, true);        refNodes.add(new InstanceOfCheckNode(checkedClass, n));        checkedClass.hasInstanceOfReference = true;        return;      }                              List<NameInformation> referers = getDependencyScope(n);      if (referers.isEmpty()) {        maybeRecordReferenceOrAlias(t, n, parent, nameInfo, null);      } else {        for (NameInformation referring : referers) {          maybeRecordReferenceOrAlias(t, n, parent, nameInfo, referring);        }        recordAliases(referers);      }    }    private void maybeRecordReferenceOrAlias(        NodeTraversal t, Node n, Node parent,        NameInformation nameInfo, NameInformation referring) {      String referringName = "";      if (referring != null) {        referringName = referring.isPrototype                      ? referring.prototypeClass                      : referring.name;      }      String name = nameInfo.name;                              if (maybeHiddenAlias(n)) {        recordAlias(name, WINDOW);      }                  if (nameInfo.isExternallyReferenceable) {        recordReference(WINDOW, name, RefType.REGULAR);        maybeRecordAlias(name, parent, referring, referringName);        return;      }                  if (NodeUtil.isVarOrSimpleAssignLhs(n, parent)) {        if (referring != null) {          recordReference(referringName, name, RefType.REGULAR);        }        return;      }      if (nodesToKeep.contains(n)) {        List<NameInformation> functionScopes =            getEnclosingFunctionDependencyScope(t);        if (!functionScopes.isEmpty()) {          for (NameInformation functionScope : functionScopes) {            recordReference(functionScope.name, name, RefType.REGULAR);          }        } else {          recordReference(WINDOW, name, RefType.REGULAR);          if (referring != null) {            maybeRecordAlias(name, parent, referring, referringName);          }        }      } else if (referring != null) {        if (!maybeRecordAlias(name, parent, referring, referringName)) {          RefType depType = referring.onlyAffectsClassDef ?              RefType.INHERITANCE : RefType.REGULAR;          recordReference(referringName, name, depType);        }      } else {                                        for (Node ancestor : n.getAncestors()) {          if (NodeUtil.isAssignmentOp(ancestor) ||              ancestor.isFunction()) {            recordReference(WINDOW, name, RefType.REGULAR);            break;          }        }      }    }    private void recordAliases(List<NameInformation> referers) {      int size = referers.size();      for (int i = 0; i < size; i++) {        for (int j = i + 1; j < size; j++) {          recordAlias(referers.get(i).name, referers.get(j).name);          recordAlias(referers.get(j).name, referers.get(i).name);        }      }    }    //**     * A value whose result is the return value of a function call     * can be an alias to global object. The dependency on the call target will     * prevent the removal of the function and its dependent values, but won't     * prevent the alias' removal.     *//    private boolean maybeHiddenAlias(Node n) {      Node parent = n.getParent();      if (NodeUtil.isVarOrSimpleAssignLhs(n, parent)) {        Node rhs = (parent.isVar())            ? n.getFirstChild() : parent.getLastChild();        return (rhs != null && !NodeUtil.evaluatesToLocalValue(            rhs, NON_LOCAL_RESULT_PREDICATE));      }      return false;    }    //**     * @return Whether the alias was recorded.     *//    private boolean maybeRecordAlias(        String name, Node parent,        NameInformation referring, String referringName) {                                                                                                      boolean isPrototypePropAssignment =          parent.isAssign()          && NodeUtil.isPrototypeProperty(parent.getFirstChild());      if ((parent.isName() ||          parent.isAssign()) &&          !isPrototypePropAssignment &&          referring != null &&          scopes.get(parent).contains(referring)) {        recordAlias(referringName, name);        return true;      }      return false;    }    //**     * Helper class that gathers the list of nodes that would be left     * behind after simplification.     *//    private class NodeAccumulator        implements SideEffectAccumulator {      @Override      public boolean classDefiningCallsHaveSideEffects() {        return false;      }      @Override      public void keepSubTree(Node original) {        addAllChildren(original);      }      @Override      public void keepSimplifiedShortCircuitExpression(Node original) {        Node condition = original.getFirstChild();        Node thenBranch = condition.getNext();        addAllChildren(condition);        addSimplifiedChildren(thenBranch);      }      @Override      public void keepSimplifiedHookExpression(Node hook,                                               boolean thenHasSideEffects,                                               boolean elseHasSideEffects) {        Node condition = hook.getFirstChild();        Node thenBranch = condition.getNext();        Node elseBranch = thenBranch.getNext();        addAllChildren(condition);        if (thenHasSideEffects) {          addSimplifiedChildren(thenBranch);        }        if (elseHasSideEffects) {          addSimplifiedChildren(elseBranch);        }      }    }  }  private class RemoveListener implements AstChangeProxy.ChangeListener {    @Override    public void nodeRemoved(Node n) {      compiler.reportCodeChange();    }  }  //**   * Creates a name analyzer, with option to remove unreferenced variables when   * calling process().   *   * The analyzer make a best guess at whether functions affect global scope   * based on usage (no assignment of return value means that a function has   * side effects).   *   * @param compiler The AbstractCompiler   * @param removeUnreferenced If true, remove unreferenced variables during   *        process()   *//  NameAnalyzer(AbstractCompiler compiler, boolean removeUnreferenced) {    this.compiler = compiler;    this.removeUnreferenced = removeUnreferenced;    this.globalNames = DEFAULT_GLOBAL_NAMES;    this.changeProxy = new AstChangeProxy();  }  @Override  public void process(Node externs, Node root) {    NodeTraversal.traverse(compiler, externs, new ProcessExternals());    NodeTraversal.traverse(compiler, root, new FindDependencyScopes());    NodeTraversal.traverse(        compiler, root, new HoistVariableAndFunctionDeclarations());    NodeTraversal.traverse(compiler, root, new FindDeclarationsAndSetters());    NodeTraversal.traverse(compiler, root, new FindReferences());            referenceParentNames();            referenceAliases();    calculateReferences();    if (removeUnreferenced) {      removeUnreferenced();    }  }  //**   * Records an alias of one name to another name.   *//  private void recordAlias(String fromName, String toName) {    recordReference(fromName, toName, RefType.REGULAR);                                        AliasSet toNameAliasSet = aliases.get(toName);    AliasSet fromNameAliasSet = aliases.get(fromName);    AliasSet resultSet = null;    if (toNameAliasSet == null && fromNameAliasSet == null) {      resultSet = new AliasSet(toName, fromName);    } else if (toNameAliasSet != null && fromNameAliasSet != null) {      resultSet = toNameAliasSet;      resultSet.names.addAll(fromNameAliasSet.names);      for (String name : fromNameAliasSet.names) {        aliases.put(name, resultSet);      }    } else if (toNameAliasSet != null) {      resultSet = toNameAliasSet;      resultSet.names.add(fromName);    } else {      resultSet = fromNameAliasSet;      resultSet.names.add(toName);    }    aliases.put(fromName, resultSet);    aliases.put(toName, resultSet);  }  //**   * Records a reference from one name to another name.   *//  private void recordReference(String fromName, String toName,                               RefType depType) {    if (fromName.equals(toName)) {            return;    }    JsName from = getName(fromName, true);    JsName to = getName(toName, true);    referenceGraph.createNode(from);    referenceGraph.createNode(to);    if (!referenceGraph.isConnectedInDirection(from, depType, to)) {      referenceGraph.connect(from, depType, to);    }  }  //**   * Removes all unreferenced variables.   *//  void removeUnreferenced() {    RemoveListener listener = new RemoveListener();    changeProxy.registerListener(listener);    for (RefNode refNode : refNodes) {      JsName name = refNode.name();      if (!name.referenced && !name.externallyDefined) {        refNode.remove();      }    }    changeProxy.unregisterListener(listener);  }  //**   * Generates an HTML report   *   * @return The report   *//  String getHtmlReport() {    StringBuilder sb = new StringBuilder();    sb.append("<html><body><style type=\"text//css\">"        + "body, td, p {font-family: Arial; font-size: 83%} "        + "ul {margin-top:2px; margin-left:0px; padding-left:1em;} "        + "li {margin-top:3px; margin-left:24px; padding-left:0px;"        + "padding-bottom: 4px}<//style>");    sb.append("OVERALL STATS<ul>");    appendListItem(sb, "Total Names: " + countOf(TriState.BOTH, TriState.BOTH));    appendListItem(sb, "Total Classes: "        + countOf(TriState.TRUE, TriState.BOTH));    appendListItem(sb, "Total Static Functions: "        + countOf(TriState.FALSE, TriState.BOTH));    appendListItem(sb, "Referenced Names: "        + countOf(TriState.BOTH, TriState.TRUE));    appendListItem(sb, "Referenced Classes: "        + countOf(TriState.TRUE, TriState.TRUE));    appendListItem(sb, "Referenced Functions: "        + countOf(TriState.FALSE, TriState.TRUE));    sb.append("<//ul>");    sb.append("ALL NAMES<ul>\n");    for (JsName node : allNames.values()) {      sb.append("<li>" + nameAnchor(node.name) + "<ul>");      if (node.prototypeNames.size() > 0) {        sb.append("<li>PROTOTYPES: ");        Iterator<String> protoIter = node.prototypeNames.iterator();        while (protoIter.hasNext()) {          sb.append(protoIter.next());          if (protoIter.hasNext()) {            sb.append(", ");          }        }      }      if (referenceGraph.hasNode(node)) {        List<DiGraphEdge<JsName, RefType>> refersTo =            referenceGraph.getOutEdges(node);        if (refersTo.size() > 0) {          sb.append("<li>REFERS TO: ");          Iterator<DiGraphEdge<JsName, RefType>> toIter = refersTo.iterator();          while (toIter.hasNext()) {            sb.append(nameLink(toIter.next().getDestination().getValue().name));            if (toIter.hasNext()) {              sb.append(", ");            }          }        }        List<DiGraphEdge<JsName, RefType>> referencedBy =            referenceGraph.getInEdges(node);        if (referencedBy.size() > 0) {          sb.append("<li>REFERENCED BY: ");          Iterator<DiGraphEdge<JsName, RefType>> fromIter = refersTo.iterator();          while (fromIter.hasNext()) {            sb.append(                nameLink(fromIter.next().getDestination().getValue().name));            if (fromIter.hasNext()) {              sb.append(", ");            }          }        }      }      sb.append("<//li>");      sb.append("<//ul><//li>");    }    sb.append("<//ul>");    sb.append("<//body><//html>");    return sb.toString();  }  private void appendListItem(StringBuilder sb, String text) {    sb.append("<li>" + text + "<//li>\n");  }  private String nameLink(String name) {    return "<a href=\"#" + name + "\">" + name + "<//a>";  }  private String nameAnchor(String name) {    return "<a name=\"" + name + "\">" + name + "<//a>";  }  //**   * Looks up a {@link JsName} by name, optionally creating one if it doesn't   * already exist.   *   * @param name A fully qualified name   * @param canCreate Whether to create the object if necessary   * @return The {@code JsName} object, or null if one can't be found and   *   can't be created.   *//  private JsName getName(String name, boolean canCreate) {    if (canCreate) {      createName(name);    }    return allNames.get(name);  }  //**   * Creates a {@link JsName} for the given name if it doesn't already   * exist.   *   * @param name A fully qualified name   *//  private void createName(String name) {    JsName jsn = allNames.get(name);    if (jsn == null) {      jsn = new JsName();      jsn.name = name;      allNames.put(name, jsn);    }  }  //**   * The NameAnalyzer algorithm works best when all objects have a canonical   * name in the global scope. When multiple names in the global scope   * point to the same object, things start to break down.   *   * For example, if we have   * <code>   * var a = {};   * var b = a;   * a.foo = 3;   * alert(b.foo);   * <//code>   * then a.foo and b.foo are the same name, even though NameAnalyzer doesn't   * represent them as such.   *   * To handle this case, we look at all the aliases in the program.   * If descendant properties of that alias are assigned, then we create a   * directional reference from the original name to the alias. For example,   * in this case, the assign to {@code a.foo} triggers a reference from   * {@code b} to {@code a}, but NOT from a to b.   *   * Similarly, "instanceof" checks do not prevent the removal   * of a unaliased name but an instanceof check on an alias can only be removed   * if the other aliases are also removed, so we add a connection here.   *//  private void referenceAliases() {    for (Map.Entry<String, AliasSet> entry : aliases.entrySet()) {      JsName name = getName(entry.getKey(), false);      if (name.hasWrittenDescendants || name.hasInstanceOfReference) {        for (String alias : entry.getValue().names) {          recordReference(alias, entry.getKey(), RefType.REGULAR);        }      }    }  }  //**   * Adds mutual references between all known global names and their parent   * names. (e.g. between <code>a.b.c<//code> and <code>a.b<//code>).   *//  private void referenceParentNames() {            Set<JsName> allNamesCopy = Sets.newHashSet(allNames.values());    for (JsName name : allNamesCopy) {      String curName = name.name;      JsName curJsName = name;      while (curName.indexOf('.') != -1) {        String parentName = curName.substring(0, curName.lastIndexOf('.'));        if (!globalNames.contains(parentName)) {          JsName parentJsName = getName(parentName, true);          recordReference(curJsName.name, parentJsName.name, RefType.REGULAR);          recordReference(parentJsName.name, curJsName.name, RefType.REGULAR);          curJsName = parentJsName;        }        curName = parentName;      }    }  }  //**   * Creates name information for the current node during a traversal.   *   * @param t The node traversal   * @param n The current node   * @return The name information, or null if the name is irrelevant to this   *     pass   *//  private NameInformation createNameInformation(NodeTraversal t, Node n) {    Node parent = n.getParent();            String name = "";    Node rootNameNode = n;    boolean bNameWasShortened = false;    while (true) {      if (NodeUtil.isGet(rootNameNode)) {        Node prop = rootNameNode.getLastChild();        if (rootNameNode.isGetProp()) {          name = "." + prop.getString() + name;        } else {                    bNameWasShortened = true;          name = "";        }        rootNameNode = rootNameNode.getFirstChild();      } else if (NodeUtil.isObjectLitKey(rootNameNode)) {        name = "." + rootNameNode.getString() + name;                Node objLit = rootNameNode.getParent();        Node objLitParent = objLit.getParent();        if (objLitParent.isAssign()) {                    rootNameNode = objLitParent.getFirstChild();        } else if (objLitParent.isName()) {                    rootNameNode = objLitParent;        } else if (objLitParent.isStringKey()) {                    rootNameNode = objLitParent;        } else {          return null;        }      } else {        break;      }    }            if (parent.isCall() && t.inGlobalScope()) {      CodingConvention convention = compiler.getCodingConvention();      SubclassRelationship classes = convention.getClassesDefinedByCall(parent);      if (classes != null) {        NameInformation nameInfo = new NameInformation();        nameInfo.name = classes.subclassName;        nameInfo.onlyAffectsClassDef = true;        nameInfo.superclass = classes.superclassName;        return nameInfo;      }      String singletonGetterClass =          convention.getSingletonGetterClassName(parent);      if (singletonGetterClass != null) {        NameInformation nameInfo = new NameInformation();        nameInfo.name = singletonGetterClass;        nameInfo.onlyAffectsClassDef = true;        return nameInfo;      }    }    switch (rootNameNode.getType()) {      case Token.NAME:                        if (!bNameWasShortened &&            n.isGetProp() &&            parent.isAssign() &&            "prototype".equals(n.getLastChild().getString())) {          if (createNameInformation(t, n.getFirstChild()) != null) {            name = rootNameNode.getString() + name;            name = name.substring(0, name.length() - PROTOTYPE_SUFFIX_LEN);            NameInformation nameInfo = new NameInformation();            nameInfo.name = name;            return nameInfo;          } else {            return null;          }        }        return createNameInformation(            rootNameNode.getString() + name, t.getScope(), rootNameNode);      case Token.THIS:        if (t.inGlobalScope()) {          NameInformation nameInfo = new NameInformation();          if (name.indexOf('.') == 0) {            nameInfo.name = name.substring(1);            } else {            nameInfo.name = name;          }          nameInfo.isExternallyReferenceable = true;          return nameInfo;        }        return null;      default:        return null;    }  }  //**   * Creates name information for a particular qualified name that occurs in a   * particular scope.   *   * @param name A qualified name (e.g. "x" or "a.b.c")   * @param scope The scope in which {@code name} occurs   * @param rootNameNode The NAME node for the first token of {@code name}   * @return The name information, or null if the name is irrelevant to this   *     pass   *//  private NameInformation createNameInformation(      String name, Scope scope, Node rootNameNode) {        String rootName = rootNameNode.getString();    Var v = scope.getVar(rootName);    boolean isExtern = (v == null && externalNames.contains(rootName));    boolean isGlobalRef = (v != null && v.isGlobal()) || isExtern ||        rootName.equals(WINDOW);    if (!isGlobalRef) {      return null;    }    NameInformation nameInfo = new NameInformation();        int idx = name.indexOf(PROTOTYPE_SUBSTRING);    if (idx != -1) {      nameInfo.isPrototype = true;      nameInfo.prototypeClass = name.substring(0, idx);      nameInfo.prototypeProperty = name.substring(          idx + PROTOTYPE_SUBSTRING_LEN);    }    nameInfo.name = name;    nameInfo.isExternallyReferenceable =        isExtern || isExternallyReferenceable(scope, name);    return nameInfo;  }  //**   * Checks whether a name can be referenced outside of the compiled code.   * These names will be the root of dependency trees.   *   * @param scope The current variable scope   * @param name The name   * @return True if can be referenced outside   *//  private boolean isExternallyReferenceable(Scope scope, String name) {    if (compiler.getCodingConvention().isExported(name)) {      return true;    }    if (scope.isLocal()) {      return false;    }    for (String s : globalNames) {      if (name.startsWith(s)) {        return true;      }    }    return false;  }  //**   * Gets the nearest enclosing dependency scope, or null if there isn't one.   *//  private List<NameInformation> getDependencyScope(Node n) {    for (Node node : n.getAncestors()) {      List<NameInformation> refs = scopes.get(node);      if (!refs.isEmpty()) {        return refs;      }    }    return Collections.emptyList();  }  //**   * Get dependency scope defined by the enclosing function, or null.   * If enclosing function is a function expression, determine scope based on   * its parent if the parent node is a variable declaration or   * assignment.   *//  private List<NameInformation> getEnclosingFunctionDependencyScope(      NodeTraversal t) {    Node function = t.getEnclosingFunction();    if (function == null) {      return Collections.emptyList();    }    List<NameInformation> refs = scopes.get(function);    if (!refs.isEmpty()) {      return refs;    }            Node parent = function.getParent();    if (parent != null) {                  while (parent.isHook()) {        parent = parent.getParent();      }      if (parent.isName()) {        return scopes.get(parent);      }      if (parent.isAssign()) {        return scopes.get(parent);      }    }    return Collections.emptyList();  }  //**   * Propagate "referenced" property down the graph.   *//  private void calculateReferences() {    JsName window = getName(WINDOW, true);    window.referenced = true;    JsName function = getName(FUNCTION, true);    function.referenced = true;        FixedPointGraphTraversal.newTraversal(new ReferencePropagationCallback())        .computeFixedPoint(referenceGraph);  }  //**   * Enum for saying a value can be true, false, or either (cleaner than using a   * Boolean with null)   *//  private enum TriState {    //** If value is true *//    TRUE,    //** If value is false *//    FALSE,    //** If value can be true or false *//    BOTH  }  //**   * Gets the count of nodes matching the criteria   *   * @param isClass Whether the node is a class   * @param referenced Whether the node is referenced   * @return Number of matches   *//  private int countOf(TriState isClass, TriState referenced) {    int count = 0;    for (JsName name : allNames.values()) {      boolean nodeIsClass = name.prototypeNames.size() > 0;      boolean classMatch = isClass == TriState.BOTH          || (nodeIsClass && isClass == TriState.TRUE)          || (!nodeIsClass && isClass == TriState.FALSE);      boolean referenceMatch = referenced == TriState.BOTH          || (name.referenced && referenced == TriState.TRUE)          || (!name.referenced && referenced == TriState.FALSE);      if (classMatch && referenceMatch && !name.externallyDefined) {        count++;      }    }    return count;  }  //**   * Extract a list of replacement nodes to use.   *//  private List<Node> getSideEffectNodes(Node n) {    List<Node> subexpressions = Lists.newArrayList();    NodeTraversal.traverse(        compiler, n,        new GatherSideEffectSubexpressionsCallback(            compiler,            new GetReplacementSideEffectSubexpressions(                compiler, subexpressions)));    List<Node> replacements =        Lists.newArrayListWithExpectedSize(subexpressions.size());    for (Node subexpression : subexpressions) {      replacements.add(NodeUtil.newExpr(subexpression));    }    return replacements;  }  //**   * Replace n with a simpler expression, while preserving program   * behavior.   *   * If the n's value is used, replace it with its RHS; otherwise   * replace it with the subexpressions that have side effects.   *//  private void replaceWithRhs(Node parent, Node n) {    if (valueConsumedByParent(n, parent)) {                  List<Node> replacements = getRhsSubexpressions(n);      List<Node> newReplacements = Lists.newArrayList();      for (int i = 0; i < replacements.size() - 1; i++) {        newReplacements.addAll(getSideEffectNodes(replacements.get(i)));      }      Node valueExpr = replacements.get(replacements.size() - 1);      valueExpr.detachFromParent();      newReplacements.add(valueExpr);      changeProxy.replaceWith(          parent, n, collapseReplacements(newReplacements));    } else if (n.isAssign() && !parent.isFor()) {                                    Node replacement = n.getLastChild();      replacement.detachFromParent();      changeProxy.replaceWith(parent, n, replacement);    } else {      replaceTopLevelExpressionWithRhs(parent, n);    }  }  //**   * Simplify a toplevel expression, while preserving program   * behavior.   *//  private void replaceTopLevelExpressionWithRhs(Node parent, Node n) {        switch (parent.getType()) {      case Token.BLOCK:      case Token.SCRIPT:      case Token.FOR:      case Token.LABEL:        break;      default:        throw new IllegalArgumentException(            "Unsupported parent node type in replaceWithRhs " +            Token.name(parent.getType()));    }    switch (n.getType()) {      case Token.EXPR_RESULT:      case Token.FUNCTION:      case Token.VAR:        break;      case Token.ASSIGN:        Preconditions.checkArgument(parent.isFor(),            "Unsupported assignment in replaceWithRhs. parent: %s",            Token.name(parent.getType()));        break;      default:        throw new IllegalArgumentException(            "Unsupported node type in replaceWithRhs " +            Token.name(n.getType()));    }        List<Node> replacements = Lists.newArrayList();    for (Node rhs : getRhsSubexpressions(n)) {      replacements.addAll(getSideEffectNodes(rhs));    }    if (parent.isFor()) {            if (replacements.isEmpty()) {        replacements.add(IR.empty());      } else {        Node expr = collapseReplacements(replacements);        replacements.clear();        replacements.add(expr);      }    }    changeProxy.replaceWith(parent, n, replacements);  }  //**   * Determine if the parent reads the value of a child expression   * directly.  This is true children used in predicates, RETURN   * statements and, RHS of variable declarations and assignments.   *   * In the case of:   * if (a) b else c   *   * This method returns true for "a", and false for "b" and "c": the   * IF expression does something special based on "a"'s value.  "b"   * and "c" are effectively outputs.  Same logic applies to FOR,   * WHILE and DO loop predicates.  AND//OR//HOOK expressions are   * syntactic sugar for IF statements; therefore this method returns   * true for the predicate and false otherwise.   *//  private boolean valueConsumedByParent(Node n, Node parent) {    if (NodeUtil.isAssignmentOp(parent)) {      return parent.getLastChild() == n;    }    switch (parent.getType()) {      case Token.NAME:      case Token.RETURN:        return true;      case Token.AND:      case Token.OR:      case Token.HOOK:        return parent.getFirstChild() == n;      case Token.FOR:        return parent.getFirstChild().getNext() == n;      case Token.IF:      case Token.WHILE:        return parent.getFirstChild() == n;      case Token.DO:        return parent.getLastChild() == n;      default:        return false;    }  }  //**   * Merge a list of nodes into a single expression.  The value of the   * new expression is determined by the last expression in the list.   *//  private Node collapseReplacements(List<Node> replacements) {    Node expr = null;    for (Node rep : replacements) {      if (rep.isExprResult()) {        rep = rep.getFirstChild();        rep.detachFromParent();      }      if (expr == null) {        expr = rep;      } else {        expr = IR.comma(expr, rep);      }    }    return expr;  }  //**   * Extract a list of subexpressions that act as right hand sides.   *//  private List<Node> getRhsSubexpressions(Node n) {    switch (n.getType()) {      case Token.EXPR_RESULT:                return getRhsSubexpressions(n.getFirstChild());      case Token.FUNCTION:                return Collections.emptyList();      case Token.NAME:        {                    Node rhs = n.getFirstChild();          if (rhs != null) {            return Lists.newArrayList(rhs);          } else {            return Collections.emptyList();          }        }      case Token.ASSIGN:        {                    Node lhs = n.getFirstChild();          Node rhs = lhs.getNext();          return Lists.newArrayList(lhs, rhs);        }      case Token.VAR:        {                    List<Node> nodes = Lists.newArrayList();          for (Node child : n.children()) {            nodes.addAll(getRhsSubexpressions(child));          }          return nodes;        }      default:        throw new IllegalArgumentException("AstChangeProxy::getRhs " + n);    }  }}


Fix:
} else if (!(parent.isCall() && parent.getFirstChild() == n)) {