Add support for type annotations when compiling to CF with R8

Bug: b/271543766
Change-Id: I55d20d6b4b2b8b1562a49a0b6e9f0283c232aa50
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index e24a22c..f72877a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -62,7 +62,7 @@
   private static final int UNKNOWN_API_LEVEL = -1;
   private static final int NOT_SET_API_LEVEL = -2;
 
-  private static void specify(StructuralSpecification<DexAnnotation, ?> spec) {
+  protected static void specify(StructuralSpecification<DexAnnotation, ?> spec) {
     spec.withItem(a -> a.annotation).withInt(a -> a.visibility);
   }
 
@@ -71,6 +71,14 @@
     this.annotation = annotation;
   }
 
+  public boolean isTypeAnnotation() {
+    return false;
+  }
+
+  public DexTypeAnnotation asTypeAnnotation() {
+    return null;
+  }
+
   @Override
   public DexAnnotation self() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index bcddd66..1cd2f4b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -81,6 +81,8 @@
   public static DexType findDuplicateEntryType(List<DexAnnotation> annotations) {
     Set<DexType> seenTypes = Sets.newIdentityHashSet();
     for (DexAnnotation annotation : annotations) {
+      // This is only reachable from DEX where type annotations are not supported.
+      assert !annotation.isTypeAnnotation();
       if (!seenTypes.add(annotation.annotation.type)) {
         return annotation.annotation.type;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java
new file mode 100644
index 0000000..99dadb8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2023, 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.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import org.objectweb.asm.TypePath;
+
+public class DexTypeAnnotation extends DexAnnotation {
+
+  private final int typeRef;
+  private final TypePath typePath;
+
+  public DexTypeAnnotation(
+      int visibility, DexEncodedAnnotation annotation, int typeRef, TypePath typePath) {
+    super(visibility, annotation);
+    this.typeRef = typeRef;
+    this.typePath = typePath;
+  }
+
+  @Override
+  public boolean isTypeAnnotation() {
+    return true;
+  }
+
+  @Override
+  public DexTypeAnnotation asTypeAnnotation() {
+    return this;
+  }
+
+  @Override
+  public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
+    throw new Unreachable("Should not collect type annotation in DEX");
+  }
+
+  @Override
+  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    throw new Unreachable("Should not collect type annotation in DEX");
+  }
+
+  public int getTypeRef() {
+    return typeRef;
+  }
+
+  public TypePath getTypePath() {
+    return typePath;
+  }
+
+  @Override
+  public StructuralMapping<DexAnnotation> getStructuralMapping() {
+    return spec ->
+        spec.withInt(t -> typeRef)
+            .withIntArray(
+                annotation -> {
+                  int totalCount = typePath.getLength() * 2;
+                  int[] serializedArr = new int[totalCount];
+                  for (int i = 0; i < totalCount; i++) {
+                    int startIndex = i * 2;
+                    serializedArr[startIndex] = typePath.getStep(i);
+                    serializedArr[startIndex + 1] = typePath.getStepArgument(i);
+                  }
+                  return serializedArr;
+                })
+            .withSpec(DexAnnotation::specify);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 76ca9ac..368ffea 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -56,6 +56,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.zip.CRC32;
 import org.objectweb.asm.AnnotationVisitor;
@@ -175,22 +176,46 @@
     return MethodAccessFlags.fromCfAccessFlags(cleanAccessFlags(access), isConstructor);
   }
 
-  private static AnnotationVisitor createAnnotationVisitor(String desc, boolean visible,
+  private static AnnotationVisitor createAnnotationVisitor(
+      String desc,
+      boolean visible,
       List<DexAnnotation> annotations,
-      JarApplicationReader application) {
-    assert annotations != null;
+      JarApplicationReader application,
+      BiFunction<Integer, DexEncodedAnnotation, DexAnnotation> newAnnotationConstructor) {
+    assert newAnnotationConstructor != null;
     if (visible || retainCompileTimeAnnotation(desc, application)) {
-      int visiblity = visible ? DexAnnotation.VISIBILITY_RUNTIME : DexAnnotation.VISIBILITY_BUILD;
+      int visibility = visible ? DexAnnotation.VISIBILITY_RUNTIME : DexAnnotation.VISIBILITY_BUILD;
       return new CreateAnnotationVisitor(
           application,
           (names, values) ->
               annotations.add(
-                  new DexAnnotation(
-                      visiblity, createEncodedAnnotation(desc, names, values, application))));
+                  newAnnotationConstructor.apply(
+                      visibility, createEncodedAnnotation(desc, names, values, application))));
     }
     return null;
   }
 
+  private static AnnotationVisitor createTypeAnnotationVisitor(
+      String desc,
+      boolean visible,
+      List<DexAnnotation> annotations,
+      JarApplicationReader application,
+      int typeRef,
+      TypePath typePath) {
+    assert annotations != null;
+    // Java 8 type annotations are not supported by Dex. Ignore them.
+    if (!application.options.isGeneratingClassFiles()) {
+      return null;
+    }
+    return createAnnotationVisitor(
+        desc,
+        visible,
+        annotations,
+        application,
+        (visibility, annotation) ->
+            new DexTypeAnnotation(visibility, annotation, typeRef, typePath));
+  }
+
   private static boolean retainCompileTimeAnnotation(
       String desc, JarApplicationReader application) {
     return DexAnnotation.retainCompileTimeAnnotation(
@@ -446,14 +471,15 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-      return createAnnotationVisitor(desc, visible, getAnnotations(), application);
+      return createAnnotationVisitor(
+          desc, visible, getAnnotations(), application, DexAnnotation::new);
     }
 
     @Override
     public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
         boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
-      return null;
+      return createTypeAnnotationVisitor(
+          desc, visible, getAnnotations(), application, typeRef, typePath);
     }
 
     @Override
@@ -674,14 +700,15 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-      return createAnnotationVisitor(desc, visible, getAnnotations(), parent.application);
+      return createAnnotationVisitor(
+          desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
 
     @Override
     public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
         boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
-      return null;
+      return createTypeAnnotationVisitor(
+          desc, visible, getAnnotations(), parent.application, typeRef, typePath);
     }
 
     @Override
@@ -815,7 +842,8 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-      return createAnnotationVisitor(desc, visible, getAnnotations(), parent.application);
+      return createAnnotationVisitor(
+          desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
 
     @Override
@@ -827,13 +855,6 @@
     }
 
     @Override
-    public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
-        boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
-      return null;
-    }
-
-    @Override
     public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
       if (annotableParameterCount != -1) {
         // TODO(113565942): We assume that the runtime visible and runtime invisible parameter
@@ -856,28 +877,42 @@
         }
       }
       assert mv == null;
-      return createAnnotationVisitor(desc, visible,
-          parameterAnnotationsLists.get(parameter), parent.application);
+      return createAnnotationVisitor(
+          desc,
+          visible,
+          parameterAnnotationsLists.get(parameter),
+          parent.application,
+          DexAnnotation::new);
+    }
+
+    @Override
+    public AnnotationVisitor visitTypeAnnotation(
+        int typeRef, TypePath typePath, String desc, boolean visible) {
+      return createTypeAnnotationVisitor(
+          desc, visible, getAnnotations(), parent.application, typeRef, typePath);
     }
 
     @Override
     public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc,
         boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
+      // We do not support code type annotations since that would require us to maintain these
+      // through IR where we may as well invalidate any assumptions made on the code.
       return null;
     }
 
     @Override
     public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath,
         Label[] start, Label[] end, int[] index, String desc, boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
+      // We do not support code type annotations since that would require us to maintain these
+      // through IR where we may as well invalidate any assumptions made on the code.
       return null;
     }
 
     @Override
     public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc,
         boolean visible) {
-      // Java 8 type annotations are not supported by Dex, thus ignore them.
+      // We do not support code type annotations since that would require us to maintain these
+      // through IR where we may as well invalidate any assumptions made on the code.
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index cc88ca9..f10a6cf 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeAnnotation;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -72,6 +73,7 @@
 import org.objectweb.asm.MethodTooLargeException;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
 import org.objectweb.asm.tree.ClassNode;
 import org.objectweb.asm.tree.MethodNode;
 import org.objectweb.asm.util.CheckClassAdapter;
@@ -268,7 +270,8 @@
     assert SyntheticNaming.verifyNotInternalSynthetic(name);
     writer.visit(version.raw(), access, name, signature, superName, interfaces);
     appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
-    writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
+    writeAnnotations(
+        writer::visitAnnotation, writer::visitTypeAnnotation, clazz.annotations().annotations);
     ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
 
     if (clazz.getEnclosingMethodAttribute() != null) {
@@ -454,7 +457,8 @@
     String signature = field.getGenericSignature().toRenamedString(getNamingLens(), isTypeMissing);
     Object value = getStaticValue(field);
     FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
-    writeAnnotations(visitor::visitAnnotation, field.annotations().annotations);
+    writeAnnotations(
+        visitor::visitAnnotation, visitor::visitTypeAnnotation, field.annotations().annotations);
     visitor.visitEnd();
   }
 
@@ -488,7 +492,10 @@
       }
     }
     writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
-    writeAnnotations(visitor::visitAnnotation, definition.annotations().annotations);
+    writeAnnotations(
+        visitor::visitAnnotation,
+        visitor::visitTypeAnnotation,
+        definition.annotations().annotations);
     writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
     if (!definition.shouldNotHaveCode()) {
       writeCode(method, classFileVersion, namingLens, rewriter, visitor);
@@ -525,11 +532,13 @@
         parameterAnnotations.getAnnotableParameterCount(), true);
     visitor.visitAnnotableParameterCount(
         parameterAnnotations.getAnnotableParameterCount(), false);
-
     for (int i = 0; i < parameterAnnotations.size(); i++) {
       int iFinal = i;
       writeAnnotations(
           (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+          (typeRef, typePath, desc, visible) -> {
+            throw new Unreachable("Type annotations are not placed on parameters");
+          },
           parameterAnnotations.get(i).annotations);
     }
   }
@@ -538,7 +547,14 @@
     AnnotationVisitor visit(String desc, boolean visible);
   }
 
-  private void writeAnnotations(AnnotationConsumer visitor, DexAnnotation[] annotations) {
+  private interface TypeAnnotationConsumer {
+    AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
+  }
+
+  private void writeAnnotations(
+      AnnotationConsumer visitor,
+      TypeAnnotationConsumer typeAnnotationVisitor,
+      DexAnnotation[] annotations) {
     for (DexAnnotation dexAnnotation : annotations) {
       if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
         // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
@@ -546,10 +562,14 @@
         // signature, throws.
         continue;
       }
+      String desc = getNamingLens().lookupDescriptor(dexAnnotation.annotation.type).toString();
+      boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
+      DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
       AnnotationVisitor v =
-          visitor.visit(
-              getNamingLens().lookupDescriptor(dexAnnotation.annotation.type).toString(),
-              dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME);
+          dexTypeAnnotation == null
+              ? visitor.visit(desc, visible)
+              : typeAnnotationVisitor.visit(
+                  dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
       if (v != null) {
         writeAnnotation(v, dexAnnotation.annotation);
         v.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java b/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java
index b5dbbe9..307e052 100644
--- a/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java
@@ -4,18 +4,31 @@
 
 package com.android.tools.r8.annotations;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.annotations.testclasses.MainWithTypeAndGeneric;
 import com.android.tools.r8.annotations.testclasses.NotNullTestClass;
 import com.android.tools.r8.annotations.testclasses.NotNullTestRuntime;
 import com.android.tools.r8.annotations.testclasses.SuperInterface;
 import com.android.tools.r8.annotations.testclasses.TestClassWithTypeAndGenericAnnotations;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundAnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.AnnotatedType;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,34 +38,6 @@
 @RunWith(Parameterized.class)
 public class TypeUseAnnotationWithGenericsTest extends TestBase {
 
-  private final String EXPECTED_JVM =
-      StringUtils.joinLines(
-          "printAnnotation - Class: " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - Class: NULL",
-          "printAnnotation - Extends(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - Implements(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - Field: NULL",
-          "printAnnotation - Field: NULL",
-          "printAnnotation - Field(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - Method: NULL",
-          "printAnnotation - Method: NULL",
-          "printAnnotation - MethodReturnType(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - MethodParameter at 0(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - MethodParameter at 1(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - MethodException at 0(0): " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - MethodException at 1(0): " + typeName(NotNullTestRuntime.class),
-          "Hello World!");
-
-  private final String EXPECTED_R8 =
-      StringUtils.joinLines(
-          "printAnnotation - Class: " + typeName(NotNullTestRuntime.class),
-          "printAnnotation - Class: NULL",
-          "printAnnotation - Field: NULL",
-          "printAnnotation - Field: NULL",
-          "printAnnotation - Method: NULL",
-          "printAnnotation - Method: NULL",
-          "Hello World!");
-
   @Parameter() public TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -71,7 +56,26 @@
             TestClassWithTypeAndGenericAnnotations.class,
             SuperInterface.class)
         .run(parameters.getRuntime(), MainWithTypeAndGeneric.class)
-        .assertSuccessWithOutputLines(EXPECTED_JVM);
+        .assertSuccessWithOutputLines(getExpected(typeName(NotNullTestRuntime.class)));
+  }
+
+  private String getExpected(String notNullTestRuntimeTypeName) {
+    return StringUtils.joinLines(
+        "printAnnotation - Class: " + notNullTestRuntimeTypeName,
+        "printAnnotation - Class: NULL",
+        "printAnnotation - Extends(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - Implements(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - Field: NULL",
+        "printAnnotation - Field: NULL",
+        "printAnnotation - Field(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - Method: NULL",
+        "printAnnotation - Method: NULL",
+        "printAnnotation - MethodReturnType(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - MethodParameter at 0(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - MethodParameter at 1(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - MethodException at 0(0): " + notNullTestRuntimeTypeName,
+        "printAnnotation - MethodException at 1(0): " + notNullTestRuntimeTypeName,
+        "Hello World!");
   }
 
   @Test
@@ -91,6 +95,22 @@
 
   @Test
   public void testR8() throws Exception {
+    setupR8Test(
+        builder ->
+            builder.addKeepClassRules(
+                NotNullTestClass.class, NotNullTestRuntime.class, SuperInterface.class));
+  }
+
+  @Test
+  public void testR8WithRenaming() throws Exception {
+    setupR8Test(
+        builder ->
+            builder.addKeepClassRulesWithAllowObfuscation(
+                NotNullTestClass.class, NotNullTestRuntime.class, SuperInterface.class));
+  }
+
+  private void setupR8Test(ThrowableConsumer<R8FullTestBuilder> modifier) throws Exception {
+    Box<String> finalNotNullTestRuntimeName = new Box<>();
     testForR8(parameters.getBackend())
         .addProgramClasses(
             MainWithTypeAndGeneric.class,
@@ -99,22 +119,121 @@
             TestClassWithTypeAndGenericAnnotations.class,
             SuperInterface.class)
         .setMinApi(parameters)
-        .addKeepClassRules(NotNullTestClass.class, NotNullTestRuntime.class, SuperInterface.class)
+        .apply(modifier)
         .addKeepRuntimeVisibleAnnotations()
         .addKeepRuntimeInvisibleAnnotations()
         .addKeepRuntimeVisibleTypeAnnotations()
         .addKeepRuntimeInvisibleTypeAnnotations()
         .addKeepAttributeSignature()
+        .addKeepAttributeExceptions()
         .addKeepMainRule(MainWithTypeAndGeneric.class)
         .addKeepClassAndMembersRules(TestClassWithTypeAndGenericAnnotations.class)
         .applyIf(parameters.isDexRuntime(), b -> b.addDontWarn(AnnotatedType.class))
         .compile()
-        .inspect(
+        .inspectWithOptions(
             inspector -> {
-              // TODO(b/271543766): Add inspection of annotated types, even runtime invisible.
-            })
+              ClassSubject notNullTestClass = inspector.clazz(NotNullTestClass.class);
+              assertThat(notNullTestClass, isPresent());
+              ClassSubject notNullTestRuntime = inspector.clazz(NotNullTestRuntime.class);
+              assertThat(notNullTestRuntime, isPresent());
+              finalNotNullTestRuntimeName.set(notNullTestRuntime.getFinalName());
+              ClassSubject clazz = inspector.clazz(TestClassWithTypeAndGenericAnnotations.class);
+              assertThat(clazz, isPresent());
+              if (parameters.isDexRuntime()) {
+                inspectAnnotations(
+                    clazz.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    2,
+                    0,
+                    1,
+                    1);
+              } else {
+                inspectAnnotations(
+                    clazz.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    10,
+                    8,
+                    5,
+                    5);
+              }
+              FieldSubject field = clazz.uniqueFieldWithOriginalName("field");
+              assertThat(field, isPresent());
+              if (parameters.isDexRuntime()) {
+                inspectAnnotations(
+                    field.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    0,
+                    0,
+                    0,
+                    0);
+              } else {
+                inspectAnnotations(
+                    field.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    4,
+                    4,
+                    2,
+                    2);
+              }
+              MethodSubject method = clazz.uniqueMethodWithOriginalName("method");
+              assertThat(method, isPresent());
+              // We create a dex annotation for the checked exception.
+              if (parameters.isDexRuntime()) {
+                inspectAnnotations(
+                    method.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    1,
+                    0,
+                    0,
+                    0);
+              } else {
+                inspectAnnotations(
+                    method.annotations(),
+                    notNullTestRuntime.getFinalReference(),
+                    notNullTestClass.getFinalReference(),
+                    17,
+                    16,
+                    8,
+                    8);
+              }
+            },
+            options -> options.programConsumer = ClassFileConsumer.emptyConsumer())
         .run(parameters.getRuntime(), MainWithTypeAndGeneric.class)
         .assertFailureWithErrorThatThrowsIf(parameters.isDexRuntime(), NoSuchMethodError.class)
-        .assertSuccessWithOutputLinesIf(parameters.isCfRuntime(), EXPECTED_R8);
+        .assertSuccessWithOutputLinesIf(
+            parameters.isCfRuntime(), getExpected(finalNotNullTestRuntimeName.get()));
+  }
+
+  private void inspectAnnotations(
+      List<FoundAnnotationSubject> annotations,
+      ClassReference notNullRuntime,
+      ClassReference notNullClass,
+      int expectedCount,
+      int expectedTypeAnnotationCount,
+      int expectedNotNullTestRuntimeCount,
+      int expectedNotNullTestClassCount) {
+    assertEquals(expectedCount, annotations.size());
+    assertEquals(
+        expectedTypeAnnotationCount,
+        annotations.stream().filter(FoundAnnotationSubject::isTypeAnnotation).count());
+    assertEquals(
+        expectedNotNullTestRuntimeCount,
+        annotations.stream()
+            .filter(
+                annotation ->
+                    annotation.getAnnotation().type.asClassReference().equals(notNullRuntime))
+            .count());
+    assertEquals(
+        expectedNotNullTestClassCount,
+        annotations.stream()
+            .filter(
+                annotation ->
+                    annotation.getAnnotation().type.asClassReference().equals(notNullClass))
+            .count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
index 610b054..2ad1eb6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexTypeAnnotation;
 
 public class AbsentAnnotationSubject extends AnnotationSubject {
 
@@ -28,4 +29,19 @@
   public DexEncodedAnnotation getAnnotation() {
     throw new UnsupportedOperationException();
   }
+
+  @Override
+  public int isVisible() {
+    throw new Unreachable("Subject is absent");
+  }
+
+  @Override
+  public boolean isTypeAnnotation() {
+    throw new Unreachable("Subject is absent");
+  }
+
+  @Override
+  public DexTypeAnnotation asDexTypeAnnotation() {
+    throw new Unreachable("Subject is absent");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
index 4bffcca..ddd737b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
@@ -5,8 +5,15 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexTypeAnnotation;
 
 public abstract class AnnotationSubject extends Subject {
 
   public abstract DexEncodedAnnotation getAnnotation();
+
+  public abstract int isVisible();
+
+  public abstract boolean isTypeAnnotation();
+
+  public abstract DexTypeAnnotation asDexTypeAnnotation();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
index e73c25a..4d67e5d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexTypeAnnotation;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
 
@@ -51,4 +52,19 @@
   public DexEncodedAnnotation getAnnotation() {
     return annotation.annotation;
   }
+
+  @Override
+  public int isVisible() {
+    return annotation.getVisibility();
+  }
+
+  @Override
+  public boolean isTypeAnnotation() {
+    return annotation.isTypeAnnotation();
+  }
+
+  @Override
+  public DexTypeAnnotation asDexTypeAnnotation() {
+    return annotation.asTypeAnnotation();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index d16221e..abc83be 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -115,7 +114,7 @@
 
   @Override
   public List<FoundAnnotationSubject> annotations() {
-    throw new Unimplemented();
+    return FoundAnnotationSubject.listFromDex(dexField.annotations(), codeInspector);
   }
 
   @Override
@@ -133,9 +132,7 @@
 
   @Override
   public String getJvmFieldSignatureAsString() {
-    return dexField.getReference().name.toString()
-        + ":"
-        + dexField.getReference().type.toDescriptorString();
+    return dexField.getReference().name + ":" + dexField.getReference().type.toDescriptorString();
   }
 
   @Override