Further reduction of calls to AggregatingAttributeMapper.visitAttribute.
This is now not called on the regular build path except for computed defaults.

--
MOS_MIGRATED_REVID=91306062
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
index afc4b7d..dff87be 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
@@ -16,6 +16,8 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.collect.CollectionUtils;
 import com.google.devtools.build.lib.syntax.Label;
 
 import java.util.ArrayList;
@@ -23,6 +25,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -68,6 +71,11 @@
    */
   @Override
   protected void visitLabels(Attribute attribute, AcceptsLabelAttribute observer) {
+    visitLabels(attribute, true, observer);
+  }
+
+  private void visitLabels(Attribute attribute, boolean includeSelectKeys,
+    AcceptsLabelAttribute observer) {
     Type<?> type = attribute.getType();
     Type.SelectorList<?> selectorList = getSelectorList(attribute.getName(), type);
     if (selectorList == null) {
@@ -87,7 +95,7 @@
     } else {
       for (Type.Selector<?> selector : selectorList.getSelectors()) {
         for (Map.Entry<Label, ?> selectorEntry : selector.getEntries().entrySet()) {
-          if (!Type.Selector.isReservedLabel(selectorEntry.getKey())) {
+          if (includeSelectKeys && !Type.Selector.isReservedLabel(selectorEntry.getKey())) {
             observer.acceptLabelAttribute(selectorEntry.getKey(), attribute);
           }
           for (Label value : type.getLabels(selectorEntry.getValue())) {
@@ -99,23 +107,57 @@
   }
 
   /**
-   * Returns all labels reachable via the given attribute.
+   * Returns all labels reachable via the given attribute. If a label is listed multiple times,
+   * each instance appears in the returned list.
    *
-   * <p>Use this over {@link #visitAttribute} when this is sufficient, since the latter grows
-   * exponentially with respect to the number of selects in the attribute.
+   * @param includeSelectKeys whether to include config_setting keys for configurable attributes
    */
-  public Iterable<Label> getReachableLabels(String attributeName) {
+  public List<Label> getReachableLabels(String attributeName, boolean includeSelectKeys) {
     final ImmutableList.Builder<Label> builder = ImmutableList.builder();
-    visitLabels(getAttributeDefinition(attributeName), new AcceptsLabelAttribute() {
-      @Override
-      public void acceptLabelAttribute(Label label, Attribute attribute) {
-        builder.add(label);
-      }
-    });
+    visitLabels(getAttributeDefinition(attributeName), includeSelectKeys,
+        new AcceptsLabelAttribute() {
+          @Override
+          public void acceptLabelAttribute(Label label, Attribute attribute) {
+            builder.add(label);
+          }
+        });
     return builder.build();
   }
 
   /**
+   * Returns the labels that might appear multiple times in the same attribute value.
+   */
+  public Set<Label> checkForDuplicateLabels(Attribute attribute) {
+    String attrName = attribute.getName();
+    Type<?> attrType = attribute.getType();
+
+    Type.SelectorList<?> selectorList = getSelectorList(attribute.getName(), attrType);
+    if (selectorList == null || selectorList.getSelectors().size() == 1) {
+      // Three possible scenarios:
+      //  1) Plain old attribute (no selects). Without selects, visitAttribute runs efficiently.
+      //  2) Computed default, possibly depending on other attributes using select. In this case,
+      //     visitAttribute might be inefficient. But we have no choice but to iterate over all
+      //     possible values (since we have to compute them), so we take the efficiency hit.
+      //  3) "attr = select({...})". With just a single select, visitAttribute runs efficiently.
+      ImmutableSet.Builder<Label> duplicates = ImmutableSet.builder();
+      for (Object value : visitAttribute(attrName, attrType)) {
+        if (value != null) {
+          duplicates.addAll(CollectionUtils.duplicatedElementsOf(
+              ImmutableList.copyOf(attrType.getLabels(value))));
+        }
+      }
+      return duplicates.build();
+    } else {
+      // Multiple selects concatenated together. It's expensive to iterate over every possible
+      // value, so instead collect all labels across all the selects and check for duplicates.
+      // This is overly strict, since this counts duplicates across values. We can presumably
+      // relax this if necessary, but doing so would incur the value iteration expense this
+      // code path avoids.
+      return CollectionUtils.duplicatedElementsOf(getReachableLabels(attrName, false));
+    }
+  }
+
+  /**
    * Returns a list of all possible values an attribute can take for this rule.
    *
    * <p>Note that when an attribute uses multiple selects, it can potentially take on many
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 4521b33..c06b06b 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -26,7 +26,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Ordering;
-import com.google.devtools.build.lib.collect.CollectionUtils;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.syntax.Argument;
@@ -1345,19 +1344,11 @@
    */
   private static void checkForDuplicateLabels(Rule rule, Attribute attribute,
        EventHandler eventHandler) {
-    final String attrName = attribute.getName();
-    // This attribute may be selectable, so iterate over each selection possibility in turn.
-    // TODO(bazel-team): merge '*' condition into all lists when implemented.
-    AggregatingAttributeMapper attributeMap = AggregatingAttributeMapper.of(rule);
-    for (List<Label> labels : attributeMap.visitAttribute(attrName, Type.LABEL_LIST)) {
-      if (!labels.isEmpty()) {
-        Set<Label> duplicates = CollectionUtils.duplicatedElementsOf(labels);
-        for (Label label : duplicates) {
-          rule.reportError(
-              String.format("Label '%s' is duplicated in the '%s' attribute of rule '%s'",
-              label, attrName, rule.getName()), eventHandler);
-        }
-      }
+    Set<Label> duplicates = AggregatingAttributeMapper.of(rule).checkForDuplicateLabels(attribute);
+    for (Label label : duplicates) {
+      rule.reportError(
+          String.format("Label '%s' is duplicated in the '%s' attribute of rule '%s'",
+          label, attribute.getName(), rule.getName()), eventHandler);
     }
   }