Clean up RuleContext's attribute access API.

All calls now go through RuleContext.attributes(), which
guarantees both native and aspect-supplied attributes are checked.

For aspect attributes, only type queries are supported (e.g. "does
this attribute exist?", "what is its type?"). Not value queries.
This is because the code this is cleaning up accesses aspect
attributes through a String->Attribute map, which doesn't include
values. If ever needed we could further extend with something
more robust.

--
MOS_MIGRATED_REVID=140771242
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapper.java
new file mode 100644
index 0000000..6d95f55
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapper.java
@@ -0,0 +1,165 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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://www.apache.org/licenses/LICENSE-2.0
+//
+// 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.devtools.build.lib.analysis;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.syntax.Type;
+
+/**
+ * An {@link AttributeMap} that supports attribute type queries on both a rule
+ * and its aspects and attribute value queries on the rule.
+ *
+ * <p>An attribute type query is anything accessible from {@link Attribute} (i.e.
+ * anything about how the attribute is integrated into the {@link RuleClass}). An
+ * attribute value query is anything related to the actual value an attribute takes.
+ *
+ * <p>For example, given {@code deps = [":adep"]}, checking that {@code deps} exists
+ * or that it's type is {@link BuildType.LABEL_LIST} are type queries. Checking that
+ * its value is explicitly set in the BUILD File or that its value
+ * {@code [":adep"]} are value queries..
+ *
+ * <p>Value queries on aspect attributes trigger {@link UnsupportedOperationException}.
+ */
+class AspectAwareAttributeMapper implements AttributeMap {
+  private final AttributeMap ruleAttributes;
+  private final ImmutableMap<String, Attribute> aspectAttributes;
+
+  public AspectAwareAttributeMapper(AttributeMap ruleAttributes,
+      ImmutableMap<String, Attribute> aspectAttributes) {
+    this.ruleAttributes = ruleAttributes;
+    this.aspectAttributes = aspectAttributes;
+  }
+
+  /**
+   * Don't use this except where absolutely necessary. This exposes internal implementation details.
+   */
+  ImmutableMap<String, Attribute> getAspectAttributes() {
+    return aspectAttributes;
+  }
+
+  @Override
+  public String getName() {
+    return ruleAttributes.getName();
+  }
+
+  @Override
+  public Label getLabel() {
+    return ruleAttributes.getLabel();
+  }
+
+  @Override
+  public <T> T get(String attributeName, Type<T> type) {
+    if (ruleAttributes.has(attributeName, type)) {
+      return ruleAttributes.get(attributeName, type);
+    } else {
+      Attribute attribute = aspectAttributes.get(attributeName);
+      if (attribute == null) {
+        throw new IllegalArgumentException(String.format(
+            "no attribute '%s' in either %s or its aspects",
+            attributeName, ruleAttributes.getLabel()));
+      } else if (attribute.getType() != type) {
+        throw new IllegalArgumentException(String.format(
+            "attribute %s has type %s, not expected type %s",
+            attributeName, attribute.getType(), type));
+      } else {
+        throw new UnsupportedOperationException("Attribute '%s' comes from an aspect. "
+            + "Value retrieval for aspect attributes is not supported.");
+      }
+    }
+  }
+
+  @Override
+  public <T> boolean isConfigurable(String attributeName, Type<T> type) {
+    if (ruleAttributes.has(attributeName, type)) {
+      return ruleAttributes.isConfigurable(attributeName, type);
+    }
+    // Any scenario aside from a "select(...)" in a BUILD file is not configurable.
+    return false;
+  }
+
+  @Override
+  public Iterable<String> getAttributeNames() {
+    return ImmutableList.<String>builder()
+        .addAll(ruleAttributes.getAttributeNames())
+        .addAll(aspectAttributes.keySet())
+        .build();
+  }
+
+  @Override
+  public Type<?> getAttributeType(String attrName) {
+    Type<?> type = ruleAttributes.getAttributeType(attrName);
+    if (type != null) {
+      return type;
+    } else {
+      Attribute attribute = aspectAttributes.get(attrName);
+      return attribute != null ? attribute.getType() : null;
+    }
+  }
+
+  @Override
+  public Attribute getAttributeDefinition(String attrName) {
+    Attribute attribute = ruleAttributes.getAttributeDefinition(attrName);
+    if (attribute != null) {
+      return attribute;
+    } else {
+      return aspectAttributes.get(attrName);
+    }
+  }
+
+  @Override
+  public boolean isAttributeValueExplicitlySpecified(String attributeName) {
+    return ruleAttributes.isAttributeValueExplicitlySpecified(attributeName);
+  }
+
+  @Override
+  public void visitLabels(AcceptsLabelAttribute observer) throws InterruptedException {
+    throw new UnsupportedOperationException("rule + aspects label visition is not supported");
+  }
+
+  @Override
+  public String getPackageDefaultHdrsCheck() {
+    return ruleAttributes.getPackageDefaultHdrsCheck();
+  }
+
+  @Override
+  public Boolean getPackageDefaultTestOnly() {
+    return ruleAttributes.getPackageDefaultTestOnly();
+  }
+
+  @Override
+  public String getPackageDefaultDeprecation() {
+    return ruleAttributes.getPackageDefaultDeprecation();
+  }
+
+  @Override
+  public ImmutableList<String> getPackageDefaultCopts() {
+    return ruleAttributes.getPackageDefaultCopts();
+  }
+
+  @Override
+  public boolean has(String attrName, Type<?> type) {
+    if (ruleAttributes.has(attrName, type)) {
+      return true;
+    } else {
+      return aspectAttributes.containsKey(attrName)
+          && aspectAttributes.get(attrName).getType() == type;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 265ea6e..1b45009 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -154,10 +154,9 @@
   private final ListMultimap<String, ConfiguredTarget> targetMap;
   private final ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap;
   private final ImmutableMap<Label, ConfigMatchingProvider> configConditions;
-  private final AttributeMap attributes;
+  private final AspectAwareAttributeMapper attributes;
   private final ImmutableSet<String> features;
   private final String ruleClassNameForLogging;
-  private final ImmutableMap<String, Attribute> aspectAttributes;
   private final BuildConfiguration hostConfiguration;
   private final ConfigurationFragmentPolicy configurationFragmentPolicy;
   private final Class<? extends BuildConfiguration.Fragment> universalFragment;
@@ -189,10 +188,9 @@
     this.targetMap = targetMap;
     this.filesetEntryMap = filesetEntryMap;
     this.configConditions = configConditions;
-    this.attributes = attributes;
+    this.attributes = new AspectAwareAttributeMapper(attributes, aspectAttributes);
     this.features = getEnabledFeatures();
     this.ruleClassNameForLogging = ruleClassNameForLogging;
-    this.aspectAttributes = aspectAttributes;
     this.skylarkProviderRegistry = builder.skylarkProviderRegistry;
     this.hostConfiguration = builder.hostConfiguration;
     reporter = builder.reporter;
@@ -274,11 +272,17 @@
    * Attributes from aspects.
    */
   public ImmutableMap<String, Attribute> getAspectAttributes() {
-    return aspectAttributes;
+    return attributes.getAspectAttributes();
   }
 
   /**
-   * Accessor for the Rule's attribute values.
+   * Accessor for the attributes of the rule and its aspects.
+   *
+   * <p>The rule's native attributes can be queried both on their structure / existence and values
+   * Aspect attributes can only be queried on their structure.
+   *
+   * <p>This should be the sole interface for reading rule/aspect attributes in {@link RuleContext}.
+   * Don't expose other access points through new public methods.
    */
   public AttributeMap attributes() {
     return attributes;
@@ -658,25 +662,11 @@
   }
 
   /**
-   * Returns the Attribute associated with this name, if it's a valid attribute for this rule,
-   * or is associated with an attached aspect. Otherwise returns null.
-   */
-  @Nullable
-  public Attribute getAttribute(String attributeName) {
-    Attribute result = getRule().getAttributeDefinition(attributeName);
-    if (result != null) {
-      return result;
-    }
-    return aspectAttributes.get(attributeName);
-  }
-
-  /**
    * Returns true iff the rule, or any attached aspect, has an attribute with the given name and
    * type.
    */
   public boolean isAttrDefined(String attrName, Type<?> type) {
-    Attribute attribute = getAttribute(attrName);
-    return attribute != null && attribute.getType() == type;
+    return attributes().has(attrName, type);
   }
 
   /**
@@ -684,8 +674,7 @@
    * a string to a {@link TransitiveInfoCollection}.
    */
   public Map<String, TransitiveInfoCollection> getPrerequisiteMap(String attributeName) {
-    Attribute attributeDefinition = getAttribute(attributeName);
-    Preconditions.checkState(attributeDefinition.getType() == BuildType.LABEL_DICT_UNARY);
+    Preconditions.checkState(attributes().has(attributeName, BuildType.LABEL_DICT_UNARY));
 
     ImmutableMap.Builder<String, TransitiveInfoCollection> result = ImmutableMap.builder();
     Map<String, Label> dict = attributes().get(attributeName, BuildType.LABEL_DICT_UNARY);
@@ -708,7 +697,7 @@
    */
   public List<? extends TransitiveInfoCollection> getPrerequisites(String attributeName,
       Mode mode) {
-    Attribute attributeDefinition = getAttribute(attributeName);
+    Attribute attributeDefinition = attributes().getAttributeDefinition(attributeName);
     if ((mode == Mode.TARGET) && (attributeDefinition.hasSplitConfigurationTransition())) {
       // TODO(bazel-team): If you request a split-configured attribute in the target configuration,
       // we return only the list of configured targets for the first architecture; this is for
@@ -736,7 +725,7 @@
       getSplitPrerequisites(String attributeName) {
     checkAttribute(attributeName, Mode.SPLIT);
 
-    Attribute attributeDefinition = getAttribute(attributeName);
+    Attribute attributeDefinition = attributes().getAttributeDefinition(attributeName);
     @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol.
     SplitTransition<BuildOptions> transition =
         (SplitTransition<BuildOptions>) attributeDefinition.getSplitTransition(rule);
@@ -873,7 +862,7 @@
    */
   @Nullable
   public FilesToRunProvider getExecutablePrerequisite(String attributeName, Mode mode) {
-    Attribute ruleDefinition = getAttribute(attributeName);
+    Attribute ruleDefinition = attributes().getAttributeDefinition(attributeName);
 
     if (ruleDefinition == null) {
       throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName
@@ -1070,7 +1059,7 @@
   }
 
   private void checkAttribute(String attributeName, Mode mode) {
-    Attribute attributeDefinition = getAttribute(attributeName);
+    Attribute attributeDefinition = attributes.getAttributeDefinition(attributeName);
     if (attributeDefinition == null) {
       throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging()
         + " attribute " + attributeName + " is not defined");
@@ -1112,7 +1101,7 @@
    * This is intended for Skylark, where the Mode is implicitly chosen.
    */
   public Mode getAttributeMode(String attributeName) {
-    Attribute attributeDefinition = getAttribute(attributeName);
+    Attribute attributeDefinition = attributes().getAttributeDefinition(attributeName);
     if (attributeDefinition == null) {
       throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging()
         + " attribute " + attributeName + " is not defined");
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index e77b66a..f43fa83 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -532,7 +532,7 @@
     NestedSet<Artifact> filesToBuild = filesBuilder.build();
 
     Iterable<Artifact> dataDeps = ImmutableList.of();
-    if (ruleContext.getAttribute("data") != null
+    if (ruleContext.attributes().has("data", BuildType.LABEL_LIST)
         && ruleContext.getAttributeMode("data") == Mode.DATA) {
       dataDeps = ruleContext.getPrerequisiteArtifacts("data", Mode.DATA).list();
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
index 31ac2be..8dfe771 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
@@ -95,8 +95,9 @@
   public static final <T extends TransitiveInfoProvider> Iterable<T> getTransitivePrerequisites(
       RuleContext ruleContext, Mode mode, final Class<T> classType) {
     IterablesChain.Builder<T> builder = IterablesChain.builder();
+    AttributeMap attributes = ruleContext.attributes();
     for (String attr : TRANSITIVE_ATTRIBUTES) {
-      if (ruleContext.getAttribute(attr) != null) {
+      if (ruleContext.attributes().has(attr, BuildType.LABEL_LIST)) {
         builder.add(ruleContext.getPrerequisites(attr, mode, classType));
       }
     }
@@ -107,7 +108,7 @@
       RuleContext ruleContext, Mode mode) {
     ImmutableList.Builder<TransitiveInfoCollection> builder = ImmutableList.builder();
     for (String attr : TRANSITIVE_ATTRIBUTES) {
-      if (ruleContext.getAttribute(attr) != null) {
+      if (ruleContext.attributes().has(attr, BuildType.LABEL_LIST)) {
         builder.addAll(ruleContext.getPrerequisites(attr, mode));
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java b/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
index 7290259..4d13e03 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/DataBinding.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
 import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.rules.java.JavaPluginInfoProvider;
 import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
 import com.google.devtools.build.lib.syntax.Type;
@@ -204,7 +205,7 @@
     if (DataBinding.isEnabled(ruleContext)) {
       dataBindingMetadataOutputs.addAll(getMetadataOutputs(ruleContext));
     }
-    if (ruleContext.getAttribute("exports") != null) {
+    if (ruleContext.attributes().has("exports", BuildType.LABEL_LIST)) {
       for (UsesDataBindingProvider provider : ruleContext.getPrerequisites("exports",
           RuleConfiguredTarget.Mode.TARGET, UsesDataBindingProvider.class)) {
         dataBindingMetadataOutputs.addAll(provider.getMetadataOutputs());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java
index 0c4b56f..269769a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.AttributeMap;
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
 
@@ -98,11 +99,12 @@
         transitiveDependencies.build(), directDependencies.build());
   }
 
-  private static void extractFromAttributes(Iterable<String> attributes,
+  private static void extractFromAttributes(Iterable<String> attributeNames,
       RuleContext ruleContext, NestedSetBuilder<ResourceContainer> builderForTransitive,
       NestedSetBuilder<ResourceContainer> builderForDirect) {
-    for (String attr : attributes) {
-      if (ruleContext.getAttribute(attr) == null) {
+    AttributeMap attributes = ruleContext.attributes();
+    for (String attr : attributeNames) {
+      if (!attributes.has(attr, BuildType.LABEL_LIST) && !attributes.has(attr, BuildType.LABEL)) {
         continue;
       }
       for (AndroidResourcesProvider resources :
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
index f12c1fc..dc10688 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java
@@ -25,7 +25,6 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.BuildType;
 
 import java.util.Collection;
@@ -91,8 +90,7 @@
    * Collects the unvalidated proguard specs exported by this rule.
    */
   private Collection<Artifact> collectLocalProguardSpecs() {
-    Attribute attribute = ruleContext.getAttribute(LOCAL_SPEC_ATTRIBUTE);
-    if (attribute == null || attribute.getType() != BuildType.LABEL_LIST) {
+    if (!ruleContext.attributes().has(LOCAL_SPEC_ATTRIBUTE, BuildType.LABEL_LIST)) {
       return ImmutableList.of();
     }
     return ruleContext.getPrerequisiteArtifacts(LOCAL_SPEC_ATTRIBUTE, Mode.TARGET).list();
@@ -102,10 +100,8 @@
    * Collects the proguard specs exported by dependencies on the given LABEL_LIST/LABEL attribute.
    */
   private NestedSet<Artifact> collectProguardSpecsFromAttribute(String attributeName, Mode mode) {
-    Attribute attribute = ruleContext.getAttribute(attributeName);
-    if (attribute == null
-        || (attribute.getType() != BuildType.LABEL_LIST
-            && attribute.getType() != BuildType.LABEL)) {
+    if (!ruleContext.attributes().has(attributeName, BuildType.LABEL_LIST)
+        && !ruleContext.attributes().has(attributeName, BuildType.LABEL)) {
       return NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
     }
     NestedSetBuilder<Artifact> dependencySpecsBuilder = NestedSetBuilder.naiveLinkOrder();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
index 0f1bc80..b4c66c8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
@@ -555,7 +555,8 @@
     }
 
     for (Attribute dependentAttribute : dependentAttributes) {
-      if (ruleContext.getAttribute(dependentAttribute.getName()) != null) {
+      if (ruleContext.attributes().has(dependentAttribute.getName(), BuildType.LABEL_LIST)
+          || ruleContext.attributes().has(dependentAttribute.getName(), BuildType.LABEL)) {
         builder.addDepObjcProviders(ruleContext.getPrerequisites(
             dependentAttribute.getName(),
             dependentAttribute.getAccessMode(),
@@ -581,7 +582,8 @@
     xcodeSupport.addXcodeSettings(xcodeProviderBuilder, common.getObjcProvider(), LIBRARY_STATIC);
 
     for (Attribute dependentAttribute : dependentAttributes) {
-      if (ruleContext.getAttribute(dependentAttribute.getName()) != null) {
+      if (ruleContext.attributes().has(dependentAttribute.getName(), BuildType.LABEL_LIST)
+          || ruleContext.attributes().has(dependentAttribute.getName(), BuildType.LABEL)) {
         xcodeSupport.addDependencies(xcodeProviderBuilder, dependentAttribute);
       }
     }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapperTest.java
new file mode 100644
index 0000000..9ab7b76
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AspectAwareAttributeMapperTest.java
@@ -0,0 +1,136 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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://www.apache.org/licenses/LICENSE-2.0
+//
+// 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.devtools.build.lib.analysis;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link AspectAwareAttributeMapper}.
+ */
+@RunWith(JUnit4.class)
+public class AspectAwareAttributeMapperTest extends BuildViewTestCase {
+  private Rule rule;
+  private ImmutableMap<String, Attribute> aspectAttributes;
+  private AspectAwareAttributeMapper mapper;
+
+  @Before
+  public final void createMapper() throws Exception {
+    RuleConfiguredTarget ct = (RuleConfiguredTarget) scratchConfiguredTarget("foo", "myrule",
+        "cc_binary(",
+        "    name = 'myrule',",
+        "    srcs = [':a.cc'],",
+        "    linkstatic = select({'//conditions:default': 1}))");
+    rule = ct.getTarget();
+    Attribute aspectAttr = new Attribute.Builder<Label>("fromaspect", BuildType.LABEL)
+        .allowedFileTypes(FileTypeSet.ANY_FILE)
+        .build();
+    aspectAttributes = ImmutableMap.<String, Attribute>of(aspectAttr.getName(), aspectAttr);
+    mapper = new AspectAwareAttributeMapper(ConfiguredAttributeMapper.of(ct), aspectAttributes);
+  }
+
+  @Test
+  public void getName() throws Exception {
+    assertThat(mapper.getName()).isEqualTo(rule.getName());
+  }
+
+  @Test
+  public void getLabel() throws Exception {
+    assertThat(mapper.getLabel()).isEqualTo(rule.getLabel());
+  }
+
+  @Test
+  public void getRuleAttributeValue() throws Exception {
+    assertThat(mapper.get("srcs", BuildType.LABEL_LIST))
+        .containsExactly(Label.parseAbsolute("//foo:a.cc"));
+  }
+
+  @Test
+  public void getAspectAttributeValue() throws Exception {
+    try {
+      mapper.get("fromaspect", BuildType.LABEL);
+      fail("Expected failure because value queries aren't supported for aspect attributes");
+    } catch (UnsupportedOperationException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void getAspectValueWrongType() throws Exception {
+    try {
+      mapper.get("fromaspect", BuildType.LABEL_LIST);
+      fail("Expected failure on wrong-typed attribute");
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage())
+          .isEqualTo("attribute fromaspect has type label, not expected type list(label)");
+    }
+  }
+
+  @Test
+  public void getMissingAttributeValue() throws Exception {
+    try {
+      mapper.get("noexist", BuildType.LABEL);
+      fail("Expected failure on non-existent attribute");
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage())
+          .isEqualTo("no attribute 'noexist' in either //foo:myrule or its aspects");
+    }
+  }
+
+  @Test
+  public void isConfigurable() throws Exception {
+    assertThat(mapper.isConfigurable("linkstatic", Type.BOOLEAN)).isTrue();
+    assertThat(mapper.isConfigurable("fromaspect", BuildType.LABEL_LIST)).isFalse();
+  }
+
+  @Test
+  public void getAttributeNames() throws Exception {
+    assertThat(mapper.getAttributeNames()).containsAllOf("srcs", "linkstatic", "fromaspect");
+  }
+
+  @Test
+  public void getAttributeType() throws Exception {
+    assertThat(mapper.getAttributeType("srcs")).isEqualTo(BuildType.LABEL_LIST);
+    assertThat(mapper.getAttributeType("fromaspect")).isEqualTo(BuildType.LABEL);
+  }
+
+  @Test
+  public void getAttributeDefinition() throws Exception {
+    assertThat(mapper.getAttributeDefinition("srcs").getName()).isEqualTo("srcs");
+    assertThat(mapper.getAttributeDefinition("fromaspect").getName()).isEqualTo("fromaspect");
+
+  }
+
+  @Test
+  public void has() throws Exception {
+    assertThat(mapper.has("srcs", BuildType.LABEL_LIST)).isTrue();
+    assertThat(mapper.has("fromaspect", BuildType.LABEL)).isTrue();
+  }
+}
+
+