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.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.javascript.jscomp.GlobalNamespace.Name; import com.google.javascript.jscomp.GlobalNamespace.Ref; import com.google.javascript.jscomp.GlobalNamespace.Ref.Type; import com.google.javascript.jscomp.ReferenceCollectingCallback; import com.google.javascript.jscomp.ReferenceCollectingCallback.ReferenceCollection; import com.google.javascript.jscomp.Scope; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.JSType;  import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Set;  //**  * Flattens global objects//namespaces by replacing each '.' with '$' in  * their names. This reduces the number of property lookups the browser has  * to do and allows the {@link RenameVars} pass to shorten namespaced names.  * For example, goog.events.handleEvent() -> goog$events$handleEvent() -> Za().  *  * <p>If a global object's name is assigned to more than once, or if a property  * is added to the global object in a complex expression, then none of its  * properties will be collapsed (for safety//correctness).  *  * <p>If, after a global object is declared, it is never referenced except when  * its properties are read or set, then the object will be removed after its  * properties have been collapsed.  *  * <p>Uninitialized variable stubs are created at a global object's declaration  * site for any of its properties that are added late in a local scope.  *  * <p>If, after an object is declared, it is referenced directly in a way that  * might create an alias for it, then none of its properties will be collapsed.  * This behavior is a safeguard to prevent the values associated with the  * flattened names from getting out of sync with the object's actual property  * values. For example, in the following case, an alias a$b, if created, could  * easily keep the value 0 even after a.b became 5:  * <code> a = {b: 0}; c = a; c.b = 5; <//code>.  *  * <p>This pass doesn't flatten property accesses of the form: a[b].  *  * <p>For lots of examples, see the unit test.  *  *// class CollapseProperties implements CompilerPass {      static final DiagnosticType UNSAFE_NAMESPACE_WARNING =       DiagnosticType.warning(           "JSC_UNSAFE_NAMESPACE",           "incomplete alias created for namespace {0}");    static final DiagnosticType NAMESPACE_REDEFINED_WARNING =       DiagnosticType.warning(           "JSC_NAMESPACE_REDEFINED",           "namespace {0} should not be redefined");    static final DiagnosticType UNSAFE_THIS = DiagnosticType.warning(       "JSC_UNSAFE_THIS",       "dangerous use of 'this' in static method {0}");    private AbstractCompiler compiler;    //** Global namespace tree *//   private List<Name> globalNames;    //** Maps names (e.g. "a.b.c") to nodes in the global namespace tree *//   private Map<String, Name> nameMap;    private final boolean collapsePropertiesOnExternTypes;   private final boolean inlineAliases;    //**    * Creates an instance.    *    * @param compiler The JSCompiler, for reporting code changes    * @param collapsePropertiesOnExternTypes if true, will rename user-defined    *     static properties on externed typed. E.g. String.foo.    * @param inlineAliases Whether we're allowed to inline local aliases of    *     namespaces, etc.    *//   CollapseProperties(AbstractCompiler compiler,       boolean collapsePropertiesOnExternTypes, boolean inlineAliases) {     this.compiler = compiler;     this.collapsePropertiesOnExternTypes = collapsePropertiesOnExternTypes;     this.inlineAliases = inlineAliases;   }    @Override   public void process(Node externs, Node root) {     GlobalNamespace namespace;     if (collapsePropertiesOnExternTypes) {       namespace = new GlobalNamespace(compiler, externs, root);     } else {       namespace = new GlobalNamespace(compiler, root);     }      if (inlineAliases) {       inlineAliases(namespace);     }     nameMap = namespace.getNameIndex();     globalNames = namespace.getNameForest();     checkNamespaces();      for (Name n : globalNames) {       flattenReferencesToCollapsibleDescendantNames(n, n.getBaseName());     }                  for (Name n : globalNames) {       collapseDeclarationOfNameAndDescendants(n, n.getBaseName());     }   }    //**    * For each qualified name N in the global scope, we check if:    * (a) No ancestor of N is ever aliased or assigned an unknown value type.    *     (If N = "a.b.c", "a" and "a.b" are never aliased).    * (b) N has exactly one write, and it lives in the global scope.    * (c) N is aliased in a local scope.    *    * If (a) is true, then GlobalNamespace must know all the writes to N.    * If (a) and (b) are true, then N cannot change during the execution of    *    a local scope.    * If (a) and (b) and (c) are true, then the alias can be inlined if the    *    alias obeys the usual rules for how we decide whether a variable is    *    inlineable.    * @see InlineVariables    *//   private void inlineAliases(GlobalNamespace namespace) {         Deque<Name> workList = new ArrayDeque<Name>(namespace.getNameForest());     while (!workList.isEmpty()) {       Name name = workList.pop();              if (name.type == Name.Type.GET || name.type == Name.Type.SET) {         continue;       }        <START_BUG> if (name.globalSets == 1 && name.localSets == 0 && <END_BUG>           name.aliasingGets > 0) {                         List<Ref> refs = Lists.newArrayList(name.getRefs());         for (Ref ref : refs) {           if (ref.type == Type.ALIASING_GET && ref.scope.isLocal()) {                         if (inlineAliasIfPossible(ref, namespace)) {               name.removeRef(ref);             }           }         }       }                    if ((name.type == Name.Type.OBJECTLIT ||            name.type == Name.Type.FUNCTION) &&           name.aliasingGets == 0 && name.props != null) {                         workList.addAll(name.props);       }     }   }    private boolean inlineAliasIfPossible(Ref alias, GlobalNamespace namespace) {                     Node aliasParent = alias.node.getParent();     if (aliasParent.isName()) {             Scope scope = alias.scope;       Var aliasVar = scope.getVar(aliasParent.getString());       ReferenceCollectingCallback collector =           new ReferenceCollectingCallback(compiler,               ReferenceCollectingCallback.DO_NOTHING_BEHAVIOR,               Predicates.<Var>equalTo(aliasVar));       (new NodeTraversal(compiler, collector)).traverseAtScope(scope);        ReferenceCollection aliasRefs = collector.getReferences(aliasVar);       if (aliasRefs.isWellDefined()           && aliasRefs.firstReferenceIsAssigningDeclaration()           && aliasRefs.isAssignedOnceInLifetime()) {                 int size = aliasRefs.references.size();         Set<Node> newNodes = Sets.newHashSetWithExpectedSize(size - 1);         for (int i = 1; i < size; i++) {           ReferenceCollectingCallback.Reference aliasRef =               aliasRefs.references.get(i);            Node newNode = alias.node.cloneTree();           aliasRef.getParent().replaceChild(aliasRef.getNode(), newNode);           newNodes.add(newNode);         }                  aliasParent.replaceChild(alias.node, IR.nullNode());         compiler.reportCodeChange();                          namespace.scanNewNodes(alias.scope, newNodes);         return true;       }     }      return false;   }    //**    * Runs through all namespaces (prefixes of classes and enums), and checks if    * any of them have been used in an unsafe way.    *//   private void checkNamespaces() {     for (Name name : nameMap.values()) {       if (name.isNamespace() &&           (name.aliasingGets > 0 || name.localSets + name.globalSets > 1 ||            name.deleteProps > 0)) {         boolean initialized = name.getDeclaration() != null;         for (Ref ref : name.getRefs()) {           if (ref == name.getDeclaration()) {             continue;           }            if (ref.type == Ref.Type.DELETE_PROP) {             if (initialized) {               warnAboutNamespaceRedefinition(name, ref);             }           } else if (               ref.type == Ref.Type.SET_FROM_GLOBAL ||               ref.type == Ref.Type.SET_FROM_LOCAL) {             if (initialized) {               warnAboutNamespaceRedefinition(name, ref);             }              initialized = true;           } else if (ref.type == Ref.Type.ALIASING_GET) {             warnAboutNamespaceAliasing(name, ref);           }         }       }     }   }    //**    * Reports a warning because a namespace was aliased.    *    * @param nameObj A namespace that is being aliased    * @param ref The reference that forced the alias    *//   private void warnAboutNamespaceAliasing(Name nameObj, Ref ref) {     compiler.report(         JSError.make(ref.getSourceName(), ref.node,                      UNSAFE_NAMESPACE_WARNING, nameObj.getFullName()));   }    //**    * Reports a warning because a namespace was redefined.    *    * @param nameObj A namespace that is being redefined    * @param ref The reference that set the namespace    *//   private void warnAboutNamespaceRedefinition(Name nameObj, Ref ref) {     compiler.report(         JSError.make(ref.getSourceName(), ref.node,                      NAMESPACE_REDEFINED_WARNING, nameObj.getFullName()));   }    //**    * Flattens all references to collapsible properties of a global name except    * their initial definitions. Recurses on subnames.    *    * @param n An object representing a global name    * @param alias The flattened name for {@code n}    *//   private void flattenReferencesToCollapsibleDescendantNames(       Name n, String alias) {     if (n.props == null) return;      for (Name p : n.props) {       String propAlias = appendPropForAlias(alias, p.getBaseName());        if (p.canCollapse()) {         flattenReferencesTo(p, propAlias);       } else if (p.isSimpleStubDeclaration()) {         flattenSimpleStubDeclaration(p, propAlias);       }        flattenReferencesToCollapsibleDescendantNames(p, propAlias);     }   }     //**    * Flattens a stub declaration.    * This is mostly a hack to support legacy users.    *//   private void flattenSimpleStubDeclaration(Name name, String alias) {     Ref ref = Iterables.getOnlyElement(name.getRefs());     Node nameNode = NodeUtil.newName(         compiler.getCodingConvention(), alias, ref.node,         name.getFullName());     Node varNode = IR.var(nameNode).copyInformationFrom(nameNode);      Preconditions.checkState(         ref.node.getParent().isExprResult());     Node parent = ref.node.getParent();     Node gramps = parent.getParent();     gramps.replaceChild(parent, varNode);     compiler.reportCodeChange();   }     //**    * Flattens all references to a collapsible property of a global name except    * its initial definition.    *    * @param n A global property name (e.g. "a.b" or "a.b.c.d")    * @param alias The flattened name (e.g. "a$b" or "a$b$c$d")    *//   private void flattenReferencesTo(Name n, String alias) {     String originalName = n.getFullName();     for (Ref r : n.getRefs()) {       if (r == n.getDeclaration()) {                 continue;       }        Node rParent = r.node.getParent();                                      if (!NodeUtil.isObjectLitKey(r.node) &&           (r.getTwin() == null || r.isSet())) {         flattenNameRef(alias, r.node, rParent, originalName);       }     }                  if (n.props != null) {       for (Name p : n.props) {         flattenPrefixes(alias, p, 1);       }     }   }    //**    * Flattens all occurrences of a name as a prefix of subnames beginning    * with a particular subname.    *    * @param n A global property name (e.g. "a.b.c.d")    * @param alias A flattened prefix name (e.g. "a$b")    * @param depth The difference in depth between the property name and    *    the prefix name (e.g. 2)    *//   private void flattenPrefixes(String alias, Name n, int depth) {             String originalName = n.getFullName();     Ref decl = n.getDeclaration();     if (decl != null && decl.node != null &&         decl.node.isGetProp()) {       flattenNameRefAtDepth(alias, decl.node, depth, originalName);     }      for (Ref r : n.getRefs()) {       if (r == decl) {                 continue;       }                    if (r.getTwin() == null || r.isSet()) {         flattenNameRefAtDepth(alias, r.node, depth, originalName);       }     }      if (n.props != null) {       for (Name p : n.props) {         flattenPrefixes(alias, p, depth + 1);       }     }   }    //**    * Flattens a particular prefix of a single name reference.    *    * @param alias A flattened prefix name (e.g. "a$b")    * @param n The node corresponding to a subproperty name (e.g. "a.b.c.d")    * @param depth The difference in depth between the property name and    *    the prefix name (e.g. 2)    * @param originalName String version of the property name.    *//   private void flattenNameRefAtDepth(String alias, Node n, int depth,       String originalName) {                 int nType = n.getType();     boolean isQName = nType == Token.NAME || nType == Token.GETPROP;     boolean isObjKey = NodeUtil.isObjectLitKey(n);     Preconditions.checkState(isObjKey || isQName);     if (isQName) {       for (int i = 1; i < depth && n.hasChildren(); i++) {         n = n.getFirstChild();       }       if (n.hasChildren()) {         flattenNameRef(alias, n.getFirstChild(), n, originalName);       }     }   }    //**    * Replaces a GETPROP a.b.c with a NAME a$b$c.    *    * @param alias A flattened prefix name (e.g. "a$b")    * @param n The GETPROP node corresponding to the original name (e.g. "a.b")    * @param parent {@code n}'s parent    * @param originalName String version of the property name.    *//   private void flattenNameRef(String alias, Node n, Node parent,       String originalName) {                                     Node ref = NodeUtil.newName(         compiler.getCodingConvention(), alias, n, originalName);     NodeUtil.copyNameAnnotations(n.getLastChild(), ref);     if (parent.isCall() && n == parent.getFirstChild()) {                   parent.putBooleanProp(Node.FREE_CALL, true);     }      JSType type = n.getJSType();     if (type != null) {       ref.setJSType(type);     }     parent.replaceChild(n, ref);     compiler.reportCodeChange();   }    //**    * Collapses definitions of the collapsible properties of a global name.    * Recurses on subnames that also represent JavaScript objects with    * collapsible properties.    *    * @param n A node representing a global name    * @param alias The flattened name for {@code n}    *//   private void collapseDeclarationOfNameAndDescendants(Name n, String alias) {     boolean canCollapseChildNames = n.canCollapseUnannotatedChildNames();          if (n.canCollapse()) {       updateObjLitOrFunctionDeclaration(n, alias, canCollapseChildNames);     }      if (n.props != null) {       for (Name p : n.props) {                 collapseDeclarationOfNameAndDescendants(             p, appendPropForAlias(alias, p.getBaseName()));          if (!p.inExterns && canCollapseChildNames &&             p.getDeclaration() != null &&             p.canCollapse() &&             p.getDeclaration().node != null &&             p.getDeclaration().node.getParent() != null &&             p.getDeclaration().node.getParent().isAssign()) {           updateSimpleDeclaration(               appendPropForAlias(alias, p.getBaseName()), p, p.getDeclaration());         }       }     }   }    //**    * Updates the initial assignment to a collapsible property at global scope    * by changing it to a variable declaration (e.g. a.b = 1 -> var a$b = 1).    * The property's value may either be a primitive or an object literal or    * function whose properties aren't collapsible.    *    * @param alias The flattened property name (e.g. "a$b")    * @param refName The name for the reference being updated.    * @param ref An object containing information about the assignment getting    *     updated    *//   private void updateSimpleDeclaration(String alias, Name refName, Ref ref) {     Node rvalue = ref.node.getNext();     Node parent = ref.node.getParent();     Node gramps = parent.getParent();     Node greatGramps = gramps.getParent();      if (rvalue != null && rvalue.isFunction()) {       checkForHosedThisReferences(rvalue, refName.docInfo, refName);     }          Node nameNode = NodeUtil.newName(         compiler.getCodingConvention(), alias, gramps.getFirstChild(),         refName.getFullName());     NodeUtil.copyNameAnnotations(ref.node.getLastChild(), nameNode);      if (gramps.isExprResult()) {                                                                                            parent.removeChild(rvalue);       nameNode.addChildToFront(rvalue);        Node varNode = IR.var(nameNode);       greatGramps.replaceChild(gramps, varNode);     } else {             Preconditions.checkNotNull(ref.getTwin());                                             Node current = gramps;       Node currentParent = gramps.getParent();       for (; !currentParent.isScript() &&              !currentParent.isBlock();            current = currentParent,            currentParent = currentParent.getParent()) {}                    Node stubVar = IR.var(nameNode.cloneTree())           .copyInformationFrom(nameNode);       currentParent.addChildBefore(stubVar, current);        parent.replaceChild(ref.node, nameNode);     }      compiler.reportCodeChange();   }    //**    * Updates the first initialization (a.k.a "declaration") of a global name.    * This involves flattening the global name (if it's not just a global    * variable name already), collapsing object literal keys into global    * variables, declaring stub global variables for properties added later    * in a local scope.    *    * It may seem odd that this function also takes care of declaring stubs    * for direct children. The ultimate goal of this function is to eliminate    * the global name entirely (when possible), so that "middlemen" namespaces    * disappear, and to do that we need to make sure that all the direct children    * will be collapsed as well.    *    * @param n An object representing a global name (e.g. "a", "a.b.c")    * @param alias The flattened name for {@code n} (e.g. "a", "a$b$c")    * @param canCollapseChildNames Whether it's possible to collapse children of    *     this name. (This is mostly passed for convenience; it's equivalent to    *     n.canCollapseChildNames()).    *//   private void updateObjLitOrFunctionDeclaration(       Name n, String alias, boolean canCollapseChildNames) {     Ref decl = n.getDeclaration();     if (decl == null) {                   return;     }      if (decl.getTwin() != null) {                   return;     }      switch (decl.node.getParent().getType()) {       case Token.ASSIGN:         updateObjLitOrFunctionDeclarationAtAssignNode(             n, alias, canCollapseChildNames);         break;       case Token.VAR:         updateObjLitOrFunctionDeclarationAtVarNode(n, canCollapseChildNames);         break;       case Token.FUNCTION:         updateFunctionDeclarationAtFunctionNode(n, canCollapseChildNames);         break;     }   }    //**    * Updates the first initialization (a.k.a "declaration") of a global name    * that occurs at an ASSIGN node. See comment for    * {@link #updateObjLitOrFunctionDeclaration}.    *    * @param n An object representing a global name (e.g. "a", "a.b.c")    * @param alias The flattened name for {@code n} (e.g. "a", "a$b$c")    *//   private void updateObjLitOrFunctionDeclarationAtAssignNode(       Name n, String alias, boolean canCollapseChildNames) {                  Ref ref = n.getDeclaration();     Node rvalue = ref.node.getNext();     Node varNode = new Node(Token.VAR);     Node varParent = ref.node.getAncestor(3);     Node gramps = ref.node.getAncestor(2);     boolean isObjLit = rvalue.isObjectLit();     boolean insertedVarNode = false;      if (isObjLit && n.canEliminate()) {             varParent.replaceChild(gramps, varNode);       ref.node = null;       insertedVarNode = true;      } else if (!n.isSimpleName()) {             if (rvalue.isFunction()) {         checkForHosedThisReferences(rvalue, n.docInfo, n);       }        ref.node.getParent().removeChild(rvalue);        Node nameNode = NodeUtil.newName(           compiler.getCodingConvention(),           alias, ref.node.getAncestor(2), n.getFullName());        JSDocInfo info = ref.node.getParent().getJSDocInfo();       if (ref.node.getLastChild().getBooleanProp(Node.IS_CONSTANT_NAME) ||           (info != null && info.isConstant())) {         nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);       }        if (info != null) {         varNode.setJSDocInfo(info);       }       varNode.addChildToBack(nameNode);       nameNode.addChildToFront(rvalue);       varParent.replaceChild(gramps, varNode);              ref.node = nameNode;       insertedVarNode = true;     }      if (canCollapseChildNames) {       if (isObjLit) {         declareVarsForObjLitValues(             n, alias, rvalue,             varNode, varParent.getChildBefore(varNode), varParent);       }        addStubsForUndeclaredProperties(n, alias, varParent, varNode);     }      if (insertedVarNode) {       if (!varNode.hasChildren()) {         varParent.removeChild(varNode);       }       compiler.reportCodeChange();     }   }    //**    * Warns about any references to "this" in the given FUNCTION. The function    * is getting collapsed, so the references will change.    *//   private void checkForHosedThisReferences(Node function, JSDocInfo docInfo,       final Name name) {             if (docInfo == null ||         (!docInfo.isConstructor() && !docInfo.hasThisType())) {       NodeTraversal.traverse(compiler, function.getLastChild(),           new NodeTraversal.AbstractShallowCallback() {             @Override             public void visit(NodeTraversal t, Node n, Node parent) {               if (n.isThis()) {                 compiler.report(                     JSError.make(name.getDeclaration().getSourceName(), n,                         UNSAFE_THIS, name.getFullName()));               }             }           });     }   }    //**    * Updates the first initialization (a.k.a "declaration") of a global name    * that occurs at a VAR node. See comment for    * {@link #updateObjLitOrFunctionDeclaration}.    *    * @param n An object representing a global name (e.g. "a")    *//   private void updateObjLitOrFunctionDeclarationAtVarNode(       Name n, boolean canCollapseChildNames) {     if (!canCollapseChildNames) {       return;     }      Ref ref = n.getDeclaration();     String name = ref.node.getString();     Node rvalue = ref.node.getFirstChild();     Node varNode = ref.node.getParent();     Node gramps = varNode.getParent();      boolean isObjLit = rvalue.isObjectLit();     int numChanges = 0;      if (isObjLit) {       numChanges += declareVarsForObjLitValues(           n, name, rvalue, varNode, gramps.getChildBefore(varNode),           gramps);     }      numChanges += addStubsForUndeclaredProperties(n, name, gramps, varNode);      if (isObjLit && n.canEliminate()) {       varNode.removeChild(ref.node);       if (!varNode.hasChildren()) {         gramps.removeChild(varNode);       }       numChanges++;                    ref.node = null;     }      if (numChanges > 0) {       compiler.reportCodeChange();     }   }    //**    * Updates the first initialization (a.k.a "declaration") of a global name    * that occurs at a FUNCTION node. See comment for    * {@link #updateObjLitOrFunctionDeclaration}.    *    * @param n An object representing a global name (e.g. "a")    *//   private void updateFunctionDeclarationAtFunctionNode(       Name n, boolean canCollapseChildNames) {     if (!canCollapseChildNames) {       return;     }      Ref ref = n.getDeclaration();     String fnName = ref.node.getString();     addStubsForUndeclaredProperties(         n, fnName, ref.node.getAncestor(2), ref.node.getParent());   }    //**    * Declares global variables to serve as aliases for the values in an object    * literal, optionally removing all of the object literal's keys and values.    *    * @param alias The object literal's flattened name (e.g. "a$b$c")    * @param objlit The OBJLIT node    * @param varNode The VAR node to which new global variables should be added    *     as children    * @param nameToAddAfter The child of {@code varNode} after which new    *     variables should be added (may be null)    * @param varParent {@code varNode}'s parent    * @return The number of variables added    *//   private int declareVarsForObjLitValues(       Name objlitName, String alias, Node objlit, Node varNode,       Node nameToAddAfter, Node varParent) {     int numVars = 0;     int arbitraryNameCounter = 0;     boolean discardKeys = !objlitName.shouldKeepKeys();      for (Node key = objlit.getFirstChild(), nextKey; key != null;          key = nextKey) {       Node value = key.getFirstChild();       nextKey = key.getNext();              if (key.isGetterDef() || key.isSetterDef()) {         continue;       }                                      boolean isJsIdentifier = !key.isNumber() &&                                TokenStream.isJSIdentifier(key.getString());       String propName = isJsIdentifier ?           key.getString() : String.valueOf(++arbitraryNameCounter);              String qName = objlitName.getFullName() + '.' + propName;       Name p = nameMap.get(qName);       if (p != null && !p.canCollapse()) {         continue;       }        String propAlias = appendPropForAlias(alias, propName);       Node refNode = null;       if (discardKeys) {         objlit.removeChild(key);         value.detachFromParent();       } else {                 refNode = IR.name(propAlias);         if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) {           refNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);         }          key.replaceChild(value, refNode);       }              Node nameNode = IR.name(propAlias);       nameNode.addChildToFront(value);       if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) {         nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);       }       Node newVar = IR.var(nameNode)           .copyInformationFromForTree(key);       if (nameToAddAfter != null) {         varParent.addChildAfter(newVar, nameToAddAfter);       } else {         varParent.addChildBefore(newVar, varNode);       }       compiler.reportCodeChange();       nameToAddAfter = newVar;                          if (isJsIdentifier && p != null) {         if (!discardKeys) {           Ref newAlias =               p.getDeclaration().cloneAndReclassify(Ref.Type.ALIASING_GET);           newAlias.node = refNode;           p.addRef(newAlias);         }          p.getDeclaration().node = nameNode;          if (value.isFunction()) {           checkForHosedThisReferences(value, value.getJSDocInfo(), p);         }       }        numVars++;     }     return numVars;   }    //**    * Adds global variable "stubs" for any properties of a global name that are    * only set in a local scope or read but never set.    *    * @param n An object representing a global name (e.g. "a", "a.b.c")    * @param alias The flattened name of the object whose properties we are    *     adding stubs for (e.g. "a$b$c")    * @param parent The node to which new global variables should be added    *     as children    * @param addAfter The child of after which new    *     variables should be added (may be null)    * @return The number of variables added    *//   private int addStubsForUndeclaredProperties(       Name n, String alias, Node parent, Node addAfter) {     Preconditions.checkState(n.canCollapseUnannotatedChildNames());     Preconditions.checkArgument(NodeUtil.isStatementBlock(parent));     Preconditions.checkNotNull(addAfter);     int numStubs = 0;     if (n.props != null) {       for (Name p : n.props) {         if (p.needsToBeStubbed()) {           String propAlias = appendPropForAlias(alias, p.getBaseName());           Node nameNode = IR.name(propAlias);           Node newVar = IR.var(nameNode)               .copyInformationFromForTree(addAfter);           parent.addChildAfter(newVar, addAfter);           addAfter = newVar;           numStubs++;           compiler.reportCodeChange();                                if (p.getRefs().get(0).node.getLastChild().getBooleanProp(                   Node.IS_CONSTANT_NAME)) {             nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);           }         }       }     }     return numStubs;   }    private static String appendPropForAlias(String root, String prop) {     if (prop.indexOf('$') != -1) {                         prop = prop.replace("$", "$0");     }     return root + '$' + prop;   } } 

Fix:
if (!name.inExterns && name.globalSets == 1 && name.localSets == 0 &&