Test kept-graph in presence of annotations

Fixes: 190598684
Change-Id: I7069aefc45b84cc937b103f93ccad13be78ff5b8
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 6146c2c..fabdd04 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -333,13 +333,17 @@
     }
   }
 
+  private boolean hasKeptGraphConsumer() {
+    return keptGraphConsumer != null;
+  }
+
   private boolean skipReporting(KeepReason reason) {
     assert reason != null;
     if (reason == KeepReasonWitness.INSTANCE) {
       return true;
     }
     assert getSourceNode(reason) != null;
-    return keptGraphConsumer == null;
+    return !hasKeptGraphConsumer();
   }
 
   public KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
@@ -359,11 +363,15 @@
 
   public KeepReasonWitness registerAnnotation(
       DexAnnotation annotation, ProgramDefinition annotatedItem) {
-    KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
+    if (hasKeptGraphConsumer()) {
+      registerEdge(
+          getAnnotationGraphNode(annotation, annotatedItem),
+          KeepReason.annotatedOn(annotatedItem.getDefinition()));
+      registerEdge(
+          getClassGraphNode(annotation.getAnnotationType()),
+          KeepReason.referencedInAnnotation(annotation, annotatedItem));
     }
-    return registerEdge(getAnnotationGraphNode(annotation, annotatedItem), reason);
+    return KeepReasonWitness.INSTANCE;
   }
 
   public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index a1a5f88..82d3e2a 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -110,6 +110,12 @@
     return self();
   }
 
+  public final <E extends Throwable> R8TestCompileResult inspectGraph(
+      ThrowingConsumer<GraphInspector, E> consumer) throws IOException, E {
+    consumer.accept(graphInspector());
+    return self();
+  }
+
   public GraphInspector graphInspector() throws IOException {
     assert graphConsumer != null;
     return new GraphInspector(graphConsumer, inspector());
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java
new file mode 100644
index 0000000..137706a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByReferenceInAnnotationTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .enableGraphInspector()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectGraph(
+            inspector -> {
+              QueryNode keepMainMethodRule = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+              QueryNode mainClassNode =
+                  inspector
+                      .clazz(Main.class)
+                      .assertPresent()
+                      .assertNotRenamed()
+                      .assertKeptBy(keepMainMethodRule);
+
+              // The main() method is kept by the -keep rule.
+              QueryNode mainMethodNode =
+                  inspector
+                      .method(MethodReferenceUtils.mainMethod(Main.class))
+                      .assertPresent()
+                      .assertNotRenamed()
+                      .assertKeptBy(keepMainMethodRule);
+
+              // MyAnnotation is referenced from main() and is also used to annotate class Main.
+              QueryNode annotationNode =
+                  inspector
+                      .clazz(MyAnnotation.class)
+                      .assertPresent()
+                      .assertRenamed()
+                      .assertKeptBy(mainMethodNode);
+              annotationNode.assertKeptByReferenceInAnnotationOn(annotationNode, mainClassNode);
+
+              // ReferencedInAnnotation is referenced from inside the annotation on class Main.
+              inspector
+                  .clazz(ReferencedInAnnotation.class)
+                  .assertPresent()
+                  .assertRenamed()
+                  .assertKeptByReferenceInAnnotationOn(annotationNode, mainClassNode);
+
+              // Check the presence of an edge from main() to the annotation node.
+              inspector
+                  .annotation(MyAnnotation.class, mainClassNode)
+                  .assertPresent()
+                  .assertKeptByAnnotationOn(mainClassNode);
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            result ->
+                result.assertSuccessWithOutputLines(
+                    result.inspector().clazz(ReferencedInAnnotation.class).getFinalName()));
+  }
+
+  @MyAnnotation(ReferencedInAnnotation.class)
+  static class Main {
+
+    public static void main(String[] args) {
+      MyAnnotation annotation = Main.class.getAnnotation(MyAnnotation.class);
+      System.out.println(annotation.value().getName());
+    }
+  }
+
+  static class ReferencedInAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+
+    Class<?> value();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index e51c162..45ffbf2 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -26,10 +26,12 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.Annotation;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -53,6 +55,10 @@
         new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
     public static final EdgeKindPredicate isLibraryMethod =
         new EdgeKindPredicate(EdgeKind.IsLibraryMethod);
+    public static final EdgeKindPredicate isAnnotatedOn =
+        new EdgeKindPredicate(EdgeKind.AnnotatedOn);
+    public static final EdgeKindPredicate isReferencedInAnnotation =
+        new EdgeKindPredicate(EdgeKind.ReferencedInAnnotation);
     public static final EdgeKindPredicate overriding =
         new EdgeKindPredicate(EdgeKind.OverridingMethod);
     public static final EdgeKindPredicate compatibilityRule =
@@ -148,6 +154,11 @@
 
     public abstract boolean isPureCompatKeptBy(QueryNode node);
 
+    public abstract boolean isKeptByAnnotationOn(QueryNode annotatedNode);
+
+    public abstract boolean isKeptByReferenceInAnnotationOn(
+        QueryNode annotationNode, QueryNode annotatedNode);
+
     public abstract boolean isKeptByLibraryMethod(QueryNode node);
 
     public abstract boolean isSatisfiedBy(QueryNode... nodes);
@@ -274,14 +285,46 @@
       if (isSatisfiedBy(nodes)) {
         return this;
       }
-      QueryNodeImpl impl = (QueryNodeImpl) this;
+      QueryNodeImpl<?> impl = (QueryNodeImpl<?>) this;
       impl.runSatisfiedBy(Assert::fail, nodes);
       throw new Unreachable();
     }
 
+    public QueryNode assertKeptByAnnotationOn(QueryNode annotatedNode) {
+      assertTrue(
+          "Invalid call to assertKeptByAnnotation with: " + annotatedNode.getNodeDescription(),
+          annotatedNode.isPresent());
+      assertTrue(
+          errorMessage(
+              "kept by annotation on " + annotatedNode.getNodeDescription(),
+              "was not kept by an annotation"),
+          isKeptByAnnotationOn(annotatedNode));
+      return this;
+    }
+
+    public QueryNode assertKeptByReferenceInAnnotationOn(
+        QueryNode annotationNode, QueryNode annotatedNode) {
+      assertTrue(
+          "Invalid call to assertKeptByAnnotation with: " + annotationNode.getNodeDescription(),
+          annotationNode.isPresent());
+      assertTrue(
+          "Invalid call to assertKeptByAnnotation with: " + annotatedNode.getNodeDescription(),
+          annotatedNode.isPresent());
+      assertTrue(
+          errorMessage(
+              "kept by annotation "
+                  + annotationNode.getNodeDescription()
+                  + " on "
+                  + annotatedNode.getNodeDescription(),
+              "was not kept by an annotation"),
+          isKeptByReferenceInAnnotationOn(annotationNode, annotatedNode));
+      return this;
+    }
+
     public QueryNode assertKeptByLibraryMethod(QueryNode node) {
       assertTrue(
-          "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
+          "Invalid call to assertKeptByLibraryMethod with: " + node.getNodeDescription(),
+          node.isPresent());
       assertTrue(
           errorMessage(
               "kept by library method on " + node.getNodeDescription(),
@@ -376,14 +419,27 @@
     }
 
     @Override
+    public boolean isKeptByAnnotationOn(QueryNode annotatedNode) {
+      fail("Invalid call to isKeptByAnnotationOn on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean isKeptByReferenceInAnnotationOn(
+        QueryNode annotationNode, QueryNode annotatedNode) {
+      fail("Invalid call to isKeptByReferenceInAnnotationOn on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isKeptByLibraryMethod(QueryNode node) {
-      fail("Invalid call to isKeptByLibrary on " + getNodeDescription());
+      fail("Invalid call to isKeptByLibraryMethod on " + getNodeDescription());
       throw new Unreachable();
     }
 
     @Override
     public boolean isSatisfiedBy(QueryNode... nodes) {
-      fail("Invalid call to isTriggeredBy on " + getNodeDescription());
+      fail("Invalid call to isSatisfiedBy on " + getNodeDescription());
       throw new Unreachable();
     }
   }
@@ -391,19 +447,19 @@
   // Class representing a point in the kept-graph structure.
   // The purpose of this class is to tersely specify what relationships are expected between nodes,
   // thus most methods will throw assertion errors if the predicate is false.
-  private static class QueryNodeImpl extends QueryNode {
+  private static class QueryNodeImpl<T extends GraphNode> extends QueryNode {
 
     private final GraphInspector inspector;
-    private final GraphNode graphNode;
+    private final T graphNode;
 
-    public QueryNodeImpl(GraphInspector inspector, GraphNode graphNode) {
+    public QueryNodeImpl(GraphInspector inspector, T graphNode) {
       this.inspector = inspector;
       this.graphNode = graphNode;
     }
 
     @Override
     public boolean equals(Object obj) {
-      return obj instanceof QueryNodeImpl && graphNode.equals(((QueryNodeImpl) obj).graphNode);
+      return obj instanceof QueryNodeImpl && graphNode.equals(((QueryNodeImpl<?>) obj).graphNode);
     }
 
     @Override
@@ -484,7 +540,7 @@
       if (!(node instanceof QueryNodeImpl)) {
         return false;
       }
-      QueryNodeImpl impl = (QueryNodeImpl) node;
+      QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
       return filterSources((source, infos) -> impl.graphNode == source).findFirst().isPresent();
     }
 
@@ -493,7 +549,7 @@
       if (!(node instanceof QueryNodeImpl)) {
         return false;
       }
-      QueryNodeImpl impl = (QueryNodeImpl) node;
+      QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
       return filterSources(
               (source, infos) ->
                   impl.graphNode == source && EdgeKindPredicate.compatibilityRule.test(infos))
@@ -506,22 +562,63 @@
       if (!isCompatKeptBy(node)) {
         return false;
       }
-      QueryNodeImpl impl = (QueryNodeImpl) node;
+      QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
       return filterSources((source, infos) -> impl.graphNode != source).count() == 0;
     }
 
     @Override
+    public boolean isKeptByAnnotationOn(QueryNode annotatedNode) {
+      // This should be an annotation node or a class node which it an annotation.
+      assert graphNode instanceof AnnotationGraphNode || graphNode instanceof ClassGraphNode;
+
+      // The annotated node (class, field or method) should be present.
+      assert annotatedNode.isPresent();
+      QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+      assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+          || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+          || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+      return hasSource(
+          (source, infos) ->
+              source.equals(annotatedNodeImpl.graphNode)
+                  && EdgeKindPredicate.isAnnotatedOn.test(infos));
+    }
+
+    @Override
+    public boolean isKeptByReferenceInAnnotationOn(
+        QueryNode annotationNode, QueryNode annotatedNode) {
+      // The annotation node should be present.
+      assert annotationNode.isPresent();
+      QueryNodeImpl<ClassGraphNode> annotationNodeImpl =
+          (QueryNodeImpl<ClassGraphNode>) annotationNode;
+
+      // The annotated node (class, field or method) should be present.
+      assert annotatedNode.isPresent();
+      QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+      assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+          || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+          || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+      AnnotationGraphNode expectedSource =
+          new AnnotationGraphNode(annotatedNodeImpl.graphNode, annotationNodeImpl.graphNode);
+
+      return hasSource(
+          (source, infos) ->
+              source.equals(expectedSource)
+                  && EdgeKindPredicate.isReferencedInAnnotation.test(infos));
+    }
+
+    @Override
     public boolean isKeptByLibraryMethod(QueryNode node) {
       assert graphNode instanceof MethodGraphNode;
-      if (!(node instanceof QueryNodeImpl)) {
+      if (!node.isPresent()) {
         return false;
       }
-      QueryNodeImpl impl = (QueryNodeImpl) node;
-      return filterSources(
-              (source, infos) ->
-                  impl.graphNode == source && EdgeKindPredicate.isLibraryMethod.test(infos))
-          .findFirst()
-          .isPresent();
+      assert node instanceof QueryNodeImpl;
+      QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
+      return hasSource(
+          (source, infos) ->
+              impl.graphNode == source && EdgeKindPredicate.isLibraryMethod.test(infos));
     }
 
     @Override
@@ -544,7 +641,7 @@
                   + node.getNodeDescription());
           return;
         }
-        QueryNodeImpl impl = (QueryNodeImpl) node;
+        QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
         if (!filterSources((source, infos) -> impl.graphNode == source).findFirst().isPresent()) {
           onError.accept(
               "Expected to find dependency from precondtion to dependent rule, but could not. "
@@ -564,7 +661,7 @@
       if (nodes.length != preconditions.size()) {
         for (GraphNode precondition : preconditions) {
           if (Arrays.stream(nodes)
-              .noneMatch(node -> ((QueryNodeImpl) node).graphNode == precondition)) {
+              .noneMatch(node -> ((QueryNodeImpl<?>) node).graphNode == precondition)) {
             onError.accept("Unexpected item in precondtions: " + precondition.toString());
             return;
           }
@@ -614,6 +711,10 @@
           .filter(e -> test.test(e.getKey(), e.getValue()))
           .map(Entry::getKey);
     }
+
+    private boolean hasSource(BiPredicate<GraphNode, Set<GraphEdgeInfo>> test) {
+      return filterSources(test).findAny().isPresent();
+    }
   }
 
   private final CollectingGraphConsumer consumer;
@@ -753,6 +854,52 @@
     return "rule@" + origin + ":" + new TextPosition(0, line, column);
   }
 
+  public QueryNode annotation(Class<? extends Annotation> clazz, QueryNode annotatedNode) {
+    return annotation(Reference.classFromClass(clazz), annotatedNode);
+  }
+
+  public QueryNode annotation(ClassReference annotationClassReference, QueryNode annotatedNode) {
+    // The annotation node (class, field or method) should be present.
+    assert annotatedNode.isPresent();
+    QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+    assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+        || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+        || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+    Map<ClassReference, AnnotationGraphNode> annotationsOnAnnotatedItem;
+    if (annotatedNodeImpl.graphNode instanceof ClassGraphNode) {
+      annotationsOnAnnotatedItem =
+          classAnnotations.get(((ClassGraphNode) annotatedNodeImpl.graphNode).getReference());
+    } else if (annotatedNodeImpl.graphNode instanceof FieldGraphNode) {
+      annotationsOnAnnotatedItem =
+          fieldAnnotations.get(((FieldGraphNode) annotatedNodeImpl.graphNode).getReference());
+    } else {
+      assert annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+      annotationsOnAnnotatedItem =
+          methodAnnotations.get(((MethodGraphNode) annotatedNodeImpl.graphNode).getReference());
+    }
+
+    if (annotationsOnAnnotatedItem == null) {
+      return new AbsentQueryNode(
+          "Node " + annotatedNode.getNodeDescription() + " has no annotations");
+    }
+
+    AnnotationGraphNode annotationGraphNode =
+        annotationsOnAnnotatedItem.get(annotationClassReference);
+    if (annotationGraphNode == null) {
+      return new AbsentQueryNode(
+          "Node "
+              + annotatedNode.getNodeDescription()
+              + " has no annotation of type "
+              + annotationClassReference.getTypeName());
+    }
+    return new QueryNodeImpl<>(this, annotationGraphNode);
+  }
+
+  public QueryNode clazz(Class<?> clazz) {
+    return clazz(Reference.classFromClass(clazz));
+  }
+
   public QueryNode clazz(ClassReference clazz) {
     return getQueryNode(classes.get(clazz), clazz.toString());
   }