[KeepAnno] Test for UsesReflection annotated fields.

Bug: b/248408342
Change-Id: If92f41c145455e52b73675e07b9cd3974871a1f8
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 4efa5e8..0defc0b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern.Builder;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -176,12 +177,14 @@
     }
 
     private KeepItemPattern createItemContext() {
-      // TODO(b/248408342): Default type is "any", support setting actual field type.
+      KeepFieldTypePattern typePattern =
+          KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldDescriptor));
       return KeepItemPattern.builder()
           .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
           .setMemberPattern(
               KeepFieldPattern.builder()
                   .setNamePattern(KeepFieldNamePattern.exact(fieldName))
+                  .setTypePattern(typePattern)
                   .build())
           .build();
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
index beaaa55..3965699 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
@@ -9,6 +9,10 @@
     return SomeType.ANY_TYPE_INSTANCE;
   }
 
+  public static KeepFieldTypePattern fromType(KeepTypePattern typePattern) {
+    return typePattern.isAny() ? any() : new SomeType(typePattern);
+  }
+
   public boolean isAny() {
     return isType() && asType().isAny();
   }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
new file mode 100644
index 0000000..b6cc560
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2022, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionFieldAnnotationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepUsesReflectionFieldAnnotationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    List<String> rules = getExtractedKeepRules();
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getInputClassesWithoutAnnotations())
+        .addKeepRules(rules)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class, B.class);
+  }
+
+  public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<byte[]> transformed = new ArrayList<>(classes.size());
+    for (Class<?> clazz : classes) {
+      transformed.add(transformer(clazz).removeAllAnnotations().transform());
+    }
+    return transformed;
+  }
+
+  public List<String> getExtractedKeepRules() throws Exception {
+    List<Class<?>> classes = getInputClasses();
+    List<String> rules = new ArrayList<>();
+    for (Class<?> clazz : classes) {
+      rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+    }
+    return rules;
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+    assertThat(inspector.clazz(B.class).init(), isPresent());
+    assertThat(inspector.clazz(B.class).init("int"), isAbsent());
+  }
+
+  static class A {
+
+    @UsesReflection({
+      @KeepTarget(classConstant = B.class),
+      @KeepTarget(
+          classConstant = B.class,
+          methodName = "<init>",
+          methodParameters = {}),
+    })
+    public final String classNameForB =
+        System.nanoTime() == 0
+            ? null
+            : "com.android.tools.r8.keepanno.KeepUsesReflectionFieldAnnotationTest$B";
+
+    public B foo() throws Exception {
+      return (B) Class.forName(classNameForB).getDeclaredConstructor().newInstance();
+    }
+  }
+
+  static class B {
+    B() {
+      // Used.
+    }
+
+    B(int unused) {
+      // Unused.
+    }
+
+    public void bar() {
+      System.out.println("Hello, world");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index bcd5360..1974bcd 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1494,7 +1494,7 @@
           @Override
           public FieldVisitor visitField(
               int access, String name, String descriptor, String signature, Object value) {
-            FieldVisitor fv = visitField(access, name, descriptor, signature, value);
+            FieldVisitor fv = super.visitField(access, name, descriptor, signature, value);
             return new FieldVisitor(ASM_VERSION, fv) {
               @Override
               public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {