[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) {