Reland "Tests and fixes for missing classes reported from annotations"

This reverts commit 42636872591d239eed3d292dd176fc0370a4f80f.

Change-Id: Id6d4b58f5e8d642b9db9defc32c31fa6f6769c5b
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6cddfff..ac03c78 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -368,7 +368,8 @@
    * A map from annotation classes to annotations that need to be processed should the classes ever
    * become live.
    */
-  private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
+  private final Map<DexType, Map<DexAnnotation, ProgramDefinition>> deferredAnnotations =
+      new IdentityHashMap<>();
 
   /** Map of active if rules to speed up aapt2 generated keep rules. */
   private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
@@ -1820,10 +1821,13 @@
 
     // If this type has deferred annotations, we have to process those now, too.
     if (clazz.isAnnotation()) {
-      Set<DexAnnotation> annotations = deferredAnnotations.remove(clazz.type);
-      if (annotations != null && !annotations.isEmpty()) {
-        assert annotations.stream().allMatch(a -> a.annotation.type == clazz.type);
-        annotations.forEach(annotation -> processAnnotation(clazz, annotation));
+      Map<DexAnnotation, ProgramDefinition> annotations =
+          deferredAnnotations.remove(clazz.getType());
+      if (annotations != null) {
+        assert annotations.keySet().stream()
+            .allMatch(a -> a.getAnnotationType() == clazz.getType());
+        annotations.forEach(
+            (annotation, annotatedItem) -> processAnnotation(annotatedItem, annotation));
       }
     }
 
@@ -1932,14 +1936,16 @@
 
   private void processAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
     DexType type = annotation.getAnnotationType();
-    recordTypeReference(type, annotatedItem);
-    DexClass clazz = appView.definitionFor(type);
+    DexClass clazz = definitionFor(type, annotatedItem);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
     if (!shouldKeepAnnotation(appView, annotatedItem.getDefinition(), annotation, isLive)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
-        deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
+        Map<DexAnnotation, ProgramDefinition> deferredAnnotationsForAnnotationType =
+            deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
+        assert !deferredAnnotationsForAnnotationType.containsKey(annotation);
+        deferredAnnotationsForAnnotationType.put(annotation, annotatedItem);
       }
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java
new file mode 100644
index 0000000..0af31a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromClassAnnotationTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromClassAnnotationTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  @MissingRuntimeAnnotation
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java
new file mode 100644
index 0000000..0918d76
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.junit.Test;
+
+public class MissingClassReferencedFromClassAnnotationWithDataTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromClassAnnotationWithDataTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        addRuntimeAnnotation());
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addRuntimeAnnotation().andThen(addDontWarn(Main.class)));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addRuntimeAnnotation().andThen(addDontWarn(MissingClass.class)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addRuntimeAnnotation().andThen(addIgnoreWarnings()));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addRuntimeAnnotation() {
+    return builder ->
+        builder
+            .addProgramClasses(RuntimeAnnotation.class)
+            .addKeepClassRules(RuntimeAnnotation.class)
+            .addKeepRuntimeVisibleAnnotations();
+  }
+
+  @RuntimeAnnotation(data = MissingClass.class)
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface RuntimeAnnotation {
+    Class<?> data();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
new file mode 100644
index 0000000..7788831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromFieldAnnotationTest extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(Reference.classFromClass(Main.class), "FIELD", Reference.INT);
+
+  public MissingClassReferencedFromFieldAnnotationTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    @MissingRuntimeAnnotation static int FIELD;
+
+    public static void main(String[] args) {
+      int ignore = FIELD;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java
new file mode 100644
index 0000000..189b49e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromMethodAnnotationTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromMethodAnnotationTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    @MissingRuntimeAnnotation
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java
new file mode 100644
index 0000000..0017aa7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromParameterAnnotationTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromParameterAnnotationTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    public static void main(@MissingRuntimeAnnotation String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index dafb7b4..c08622c 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -23,6 +23,8 @@
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.function.Function;
 import org.junit.runner.RunWith;
@@ -39,6 +41,9 @@
     int field;
   }
 
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MissingRuntimeAnnotation {}
+
   interface MissingInterface {}
 
   private final TestParameters parameters;