[keepanno] Initial implementation of support for @UsesReflectiontoAccessMethod
Bug: b/392865072
Change-Id: Ibe8aa5b2a71a2d1aeb88e8448ca25c9905bde68a
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 6b0c939..023c844 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
@@ -21,6 +21,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflectionToAccessMethod;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflectionToConstruct;
import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
import com.android.tools.r8.keepanno.ast.KeepBindingReference;
@@ -107,12 +108,15 @@
if (AnnotationConstants.Edge.isDescriptor(descriptor)
|| AnnotationConstants.UsesReflection.isDescriptor(descriptor)
|| AnnotationConstants.UsesReflectionToConstruct.isDescriptor(descriptor)
+ || AnnotationConstants.UsesReflectionToAccessMethod.isDescriptor(descriptor)
|| AnnotationConstants.ForApi.isDescriptor(descriptor)
|| AnnotationConstants.UsedByReflection.isDescriptor(descriptor)
|| AnnotationConstants.UsedByNative.isDescriptor(descriptor)
|| AnnotationConstants.CheckRemoved.isDescriptor(descriptor)
|| AnnotationConstants.CheckOptimizedOut.isDescriptor(descriptor)
|| AnnotationConstants.UsesReflectionToConstruct.isKotlinRepeatableContainerDescriptor(
+ descriptor)
+ || AnnotationConstants.UsesReflectionToAccessMethod.isKotlinRepeatableContainerDescriptor(
descriptor)) {
return true;
}
@@ -377,6 +381,16 @@
.setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
.build());
}
+ if (AnnotationConstants.UsesReflectionToAccessMethod.isDescriptor(descriptor)) {
+ return new UsesReflectionToAccessMethodVisitor(
+ parsingContext,
+ parent::accept,
+ setContext,
+ bindingsHelper ->
+ KeepClassItemPattern.builder()
+ .setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
+ .build());
+ }
if (ForApi.isDescriptor(descriptor)) {
return new ForApiClassVisitor(parsingContext, parent::accept, setContext, className);
}
@@ -536,6 +550,23 @@
bindingsHelper ->
createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
}
+ if (AnnotationConstants.UsesReflectionToAccessMethod.isDescriptor(descriptor)) {
+ return new UsesReflectionToAccessMethodVisitor(
+ parsingContext,
+ parent::accept,
+ setContext,
+ bindingsHelper ->
+ createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
+ }
+ if (AnnotationConstants.UsesReflectionToAccessMethod.isKotlinRepeatableContainerDescriptor(
+ descriptor)) {
+ return new UsesReflectionToAccessMethodContainerVisitor(
+ parsingContext,
+ parent::accept,
+ setContext,
+ bindingsHelper ->
+ createMethodItemContext(className, methodName, methodDescriptor, bindingsHelper));
+ }
if (AnnotationConstants.ForApi.isDescriptor(descriptor)) {
return new ForApiMemberVisitor(
parsingContext,
@@ -1638,6 +1669,235 @@
}
}
+ private static class UsesReflectionToAccessMethodContainerVisitor extends AnnotationVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
+ private final Parent<KeepEdge> parent;
+ Consumer<KeepEdgeMetaInfo.Builder> addContext;
+ Function<UserBindingsHelper, KeepItemPattern> contextBuilder;
+
+ UsesReflectionToAccessMethodContainerVisitor(
+ AnnotationParsingContext parsingContext,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
+ this.parent = parent;
+ this.addContext = addContext;
+ this.contextBuilder = contextBuilder;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals("value")) {
+ return new UsesReflectionToAccessMethodContainerElementVisitor(
+ parsingContext, parent, addContext, contextBuilder);
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ super.visit(name, value);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ }
+ }
+
+ private static class UsesReflectionToAccessMethodContainerElementVisitor
+ extends AnnotationVisitorBase {
+ private final AnnotationParsingContext parsingContext;
+ private final Parent<KeepEdge> parent;
+ Consumer<KeepEdgeMetaInfo.Builder> addContext;
+ Function<UserBindingsHelper, KeepItemPattern> contextBuilder;
+
+ public UsesReflectionToAccessMethodContainerElementVisitor(
+ AnnotationParsingContext parsingContext,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
+ this.parent = parent;
+ this.addContext = addContext;
+ this.contextBuilder = contextBuilder;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
+ if (AnnotationConstants.UsesReflectionToAccessMethod.isDescriptor(descriptor)) {
+ return new UsesReflectionToAccessMethodVisitor(
+ parsingContext, parent, addContext, contextBuilder);
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ super.visit(name, value);
+ }
+ }
+
+ private static class UsesReflectionToAccessMethodVisitor extends AnnotationVisitorBase {
+
+ private final ParsingContext parsingContext;
+ private final Parent<KeepEdge> parent;
+ private final KeepEdge.Builder builder = KeepEdge.builder();
+ private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
+ private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+ private KeepMethodParametersPattern parameters = KeepMethodParametersPattern.any();
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
+
+ private KeepQualifiedClassNamePattern qualifiedName;
+ private KeepMethodNamePattern methodName;
+ private KeepMethodReturnTypePattern returnType = KeepMethodReturnTypePattern.any();
+
+ UsesReflectionToAccessMethodVisitor(
+ AnnotationParsingContext parsingContext,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ Function<UserBindingsHelper, KeepItemPattern> contextBuilder) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
+ this.parent = parent;
+ KeepItemPattern context = contextBuilder.apply(bindingsHelper);
+ KeepBindingReference contextBinding =
+ bindingsHelper.defineFreshItemBinding("CONTEXT", context);
+ preconditions.addCondition(KeepCondition.builder().setItemReference(contextBinding).build());
+ addContext.accept(metaInfoBuilder);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals(UsesReflectionToAccessMethod.classConstant) && value instanceof Type) {
+ qualifiedName =
+ KeepQualifiedClassNamePattern.exactFromDescriptor(((Type) value).getDescriptor());
+ return;
+ }
+ if (name.equals(AnnotationConstants.UsesReflectionToAccessMethod.className)
+ && value instanceof String) {
+ qualifiedName = KeepQualifiedClassNamePattern.exact((String) value);
+ return;
+ }
+ if (name.equals(UsesReflectionToAccessMethod.methodName) && value instanceof String) {
+ methodName = KeepMethodNamePattern.exact((String) value);
+ return;
+ }
+ if (name.equals(UsesReflectionToAccessMethod.returnType) && value instanceof Type) {
+ returnType =
+ KeepMethodReturnTypePattern.fromType(
+ KeepTypePattern.fromDescriptor(((Type) value).getDescriptor()));
+ return;
+ }
+ if (name.equals(UsesReflectionToAccessMethod.returnTypeName) && value instanceof String) {
+ returnType =
+ KeepMethodReturnTypePattern.fromType(
+ KeepTypePattern.fromClass(
+ KeepClassPattern.fromName(
+ KeepQualifiedClassNamePattern.exact((String) value))));
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
+ if (name.equals(UsesReflectionToAccessMethod.parameterTypes)) {
+ return new ParametersClassVisitor(
+ propertyParsingContext, parameters -> this.parameters = parameters);
+ }
+ if (name.equals(UsesReflectionToAccessMethod.parameterTypeNames)) {
+ return new ParametersClassNamesVisitor(
+ propertyParsingContext, parameters -> this.parameters = parameters);
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ String kotlinMetadataDescriptor = "Lkotlin/Metadata;";
+
+ KeepClassBindingReference classBinding =
+ bindingsHelper.defineFreshClassBinding(
+ KeepClassItemPattern.builder()
+ .setClassPattern(
+ KeepClassPattern.builder().setClassNamePattern(qualifiedName).build())
+ .build());
+ KeepMemberBindingReference memberBinding =
+ bindingsHelper.defineFreshMemberBinding(
+ KeepMemberItemPattern.builder()
+ .setClassReference(classBinding)
+ .setMemberPattern(
+ KeepMethodPattern.builder()
+ .setNamePattern(methodName)
+ .setParametersPattern(parameters)
+ .setReturnTypePattern(returnType)
+ .build())
+ .build());
+
+ KeepClassBindingReference kotlinMetadataBinding =
+ bindingsHelper.defineFreshClassBinding(
+ KeepClassItemPattern.builder()
+ .setClassPattern(KeepClassPattern.exactFromDescriptor(kotlinMetadataDescriptor))
+ .build());
+ KeepMemberBindingReference kotlinMetadataMembersBinding =
+ bindingsHelper.defineFreshMemberBinding(
+ KeepMemberItemPattern.builder()
+ .setClassReference(kotlinMetadataBinding)
+ .setMemberPattern(KeepMemberPattern.allMembers())
+ .build());
+
+ Annotation keepConstraintKotlinMetadataAnnotation =
+ KeepConstraint.annotation(
+ KeepAnnotationPattern.builder()
+ .setNamePattern(
+ KeepQualifiedClassNamePattern.exactFromDescriptor(kotlinMetadataDescriptor))
+ .addRetentionPolicy(RetentionPolicy.RUNTIME)
+ .build());
+
+ KeepConsequences.Builder consequencesBuilder =
+ KeepConsequences.builder()
+ .addTarget(
+ KeepTarget.builder()
+ .setItemReference(classBinding)
+ .setConstraints(
+ KeepConstraints.defaultAdditions(
+ KeepConstraints.builder()
+ .add(keepConstraintKotlinMetadataAnnotation)
+ .build()))
+ .build())
+ .addTarget(
+ KeepTarget.builder()
+ .setItemReference(memberBinding)
+ // Keeping the kotlin.Metadata annotation on the members is not really needed,
+ // as the annotation is only supported on classes. However, having it here
+ // makes the keep rule extraction generate more compact rules.
+ .setConstraints(
+ KeepConstraints.defaultAdditions(
+ KeepConstraints.builder()
+ .add(keepConstraintKotlinMetadataAnnotation)
+ .build()))
+ .build())
+ .addTarget(KeepTarget.builder().setItemReference(kotlinMetadataBinding).build())
+ .addTarget(
+ KeepTarget.builder().setItemReference(kotlinMetadataMembersBinding).build());
+
+ parent.accept(
+ builder
+ .setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
+ .setPreconditions(preconditions.build())
+ .setConsequences(consequencesBuilder.build())
+ .build());
+ }
+ }
+
private static class KeepBindingsVisitor extends AnnotationVisitorBase {
private final ParsingContext parsingContext;
private final UserBindingsHelper helper;
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepAnnoTestExtractedRulesBase.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepAnnoTestExtractedRulesBase.java
index c5aa87a..ee167c1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepAnnoTestExtractedRulesBase.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepAnnoTestExtractedRulesBase.java
@@ -411,6 +411,7 @@
protected void testExtractedRules(
Iterable<Class<?>> classes, Iterable<byte[]> classFileData, ExpectedRules expectedRules)
throws IOException {
+ assumeFalse(parameters.isPG());
if (parameters.isExtractRules()) {
List<String> extractedRules = new ArrayList<>();
for (Class<?> testClass : classes) {
@@ -457,7 +458,8 @@
Class<?> mainClass,
List<Class<?>> classes,
List<byte[]> classFileData,
- ExpectedRules expectedRules)
+ ExpectedRules expectedRules,
+ String expectedOutput)
throws Exception {
// TODO(b/392865072): Proguard 7.4.1 fails with "Encountered corrupt @kotlin/Metadata for class
// <binary name> (version 2.1.0)", as ti avoid missing classes warnings from ProGuard some of
@@ -477,7 +479,17 @@
}
})
.run(mainClass)
- .assertSuccessWithOutput(getExpectedOutputForJava());
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ protected void testExtractedRulesAndRunJava(
+ Class<?> mainClass,
+ List<Class<?>> classes,
+ List<byte[]> classFileData,
+ ExpectedRules expectedRules)
+ throws Exception {
+ testExtractedRulesAndRunJava(
+ mainClass, classes, classFileData, expectedRules, getExpectedOutputForJava());
}
protected void testExtractedRulesAndRunJava(List<Class<?>> classes, ExpectedRules expectedRules)
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
new file mode 100644
index 0000000..4455cb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
@@ -0,0 +1,287 @@
+// Copyright (c) 2025, 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.androidx;
+
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+
+import androidx.annotation.keep.UsesReflectionToAccessMethod;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.AnnotationBuilder;
+import com.android.tools.r8.transformers.ClassFileTransformer.AnnotationContentBuilder;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.function.Consumer;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionToAccessMethodTest extends KeepAnnoTestExtractedRulesBase {
+
+ @Parameterized.Parameters(name = "{0}, {1}")
+ public static Collection<Object[]> data() {
+ // Test with Android 14, which has `java.lang.ClassValue` to avoid having to deal with R8
+ // missing class warnings for tests using the kotlin-reflect library.
+ return buildParameters(
+ createParameters(
+ getTestParameters()
+ .withDexRuntime(Version.V14_0_0)
+ .withDefaultCfRuntime()
+ .withMaximumApiLevel()
+ .build()),
+ getKotlinTestParameters().withLatestCompiler().build());
+ }
+
+ private static Collection<Path> getKotlinSources() {
+ try {
+ return getFilesInTestFolderRelativeToClass(
+ KeepUsesReflectionToAccessMethodTest.class, "kt", "Methods.kt");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ compilationResults = getCompileMemoizerWithKeepAnnoLib(getKotlinSources());
+ }
+
+ private static ExpectedRules getExpectedRulesJava(
+ Class<?> conditionClass, String... consequentMembers) {
+ Consumer<ExpectedKeepRule.Builder> setCondition =
+ b ->
+ b.setConditionClass(conditionClass)
+ .setConditionMembers("{ void foo(java.lang.Class); }");
+ ExpectedRules.Builder builder = ExpectedRules.builder();
+ for (int i = 0; i < consequentMembers.length; i++) {
+ builder.add(
+ ExpectedKeepRule.builder()
+ .apply(setCondition)
+ .setConsequentClass(KeptClass.class)
+ .setConsequentMembers(consequentMembers[i])
+ .build());
+ }
+ addConsequentKotlinMetadata(builder, b -> b.apply(setCondition));
+ return builder.build();
+ }
+
+ private static ExpectedRules getExpectedRulesKotlin(
+ String conditionClass, String conditionMembers, String... consequentMembers) {
+ Consumer<ExpectedKeepRule.Builder> setCondition =
+ b ->
+ b.setConditionClass("com.android.tools.r8.keepanno.androidx.kt.Methods")
+ .setConditionMembers(conditionMembers);
+ ExpectedRules.Builder builder = ExpectedRules.builder();
+ for (int i = 0; i < consequentMembers.length; i++) {
+ builder.add(
+ ExpectedKeepRule.builder()
+ .apply(setCondition)
+ .setConsequentClass("com.android.tools.r8.keepanno.androidx.kt.MethodsKeptClass")
+ .setConsequentMembers(consequentMembers[i])
+ .build());
+ }
+ addConsequentKotlinMetadata(builder, b -> b.apply(setCondition));
+ return builder.build();
+ }
+
+ private static void buildUsesReflectionToAccessMethod(
+ AnnotationBuilder builder, Object clazz, String methodName, Class<?>... parameterTypes) {
+ AnnotationContentBuilder ab =
+ builder.setAnnotationClass(Reference.classFromClass(UsesReflectionToAccessMethod.class));
+ if (clazz instanceof String) {
+ ab.setField("className", clazz);
+ } else {
+ assert clazz instanceof Class<?> || clazz instanceof Type;
+ ab.setField("classConstant", clazz);
+ }
+ ab.setField("methodName", methodName);
+ // No parameterTypes or parameterTypeNames means any method.
+ if (parameterTypes != null && parameterTypes.length > 0) {
+ ab.setArray("parameterTypes", (Object[]) parameterTypes);
+ }
+ // No returnType or returnTypeName means any return type.
+ }
+
+ private static void buildUsesReflectionToAccessMethodMultiple(
+ AnnotationBuilder builder, Object clazz) {
+ builder
+ .setAnnotationClass(
+ Reference.classFromBinaryName(
+ Reference.classFromClass(UsesReflectionToAccessMethod.class).getBinaryName()
+ + "$Container"))
+ .buildArray(
+ "value",
+ builder1 ->
+ builder1
+ .setAnnotationField(
+ null,
+ builder2 ->
+ buildUsesReflectionToAccessMethod(builder2, clazz, "m", int.class))
+ .setAnnotationField(
+ null,
+ builder3 ->
+ buildUsesReflectionToAccessMethod(
+ builder3, clazz, "m", int.class, long.class))
+ .setAnnotationField(
+ null,
+ builder4 ->
+ buildUsesReflectionToAccessMethod(
+ builder4, clazz, "m", String.class, String.class, String.class)));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndAnyParameters() throws Exception {
+ testExtractedRulesAndRunJava(
+ ClassWithAnnotation.class,
+ ImmutableList.of(KeptClass.class),
+ ImmutableList.of(
+ setAnnotationOnMethod(
+ ClassWithAnnotation.class,
+ MethodPredicate.onName("foo"),
+ builder -> buildUsesReflectionToAccessMethod(builder, KeptClass.class, "m"))),
+ getExpectedRulesJava(ClassWithAnnotation.class, "{ *** m(...); }"),
+ StringUtils.lines("4"));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndIntParameter() throws Exception {
+ testExtractedRulesAndRunJava(
+ ClassWithAnnotation.class,
+ ImmutableList.of(KeptClass.class),
+ ImmutableList.of(
+ setAnnotationOnMethod(
+ ClassWithAnnotation.class,
+ MethodPredicate.onName("foo"),
+ builder ->
+ buildUsesReflectionToAccessMethod(builder, KeptClass.class, "m", int.class))),
+ getExpectedRulesJava(ClassWithAnnotation.class, "{ *** m(int); }"),
+ parameters.isReference() ? StringUtils.lines("4") : StringUtils.lines("1"));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndMultipleParameterLists() throws Exception {
+ testExtractedRulesAndRunJava(
+ ClassWithAnnotation.class,
+ ImmutableList.of(KeptClass.class),
+ ImmutableList.of(
+ setAnnotationOnMethod(
+ ClassWithAnnotation.class,
+ MethodPredicate.onName("foo"),
+ builder -> buildUsesReflectionToAccessMethodMultiple(builder, KeptClass.class))),
+ getExpectedRulesJava(
+ ClassWithAnnotation.class,
+ "{ *** m(int); }",
+ "{ *** m(int, long); }",
+ "{ *** m(java.lang.String, java.lang.String, java.lang.String); }"),
+ parameters.isReference() ? StringUtils.lines("4") : StringUtils.lines("3"));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndAnyParametersKotlin() throws Exception {
+ testExtractedRulesAndRunKotlin(
+ compilationResults,
+ (classReference, classFileBytes) ->
+ setAnnotationOnMethod(
+ classReference,
+ classFileBytes,
+ Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Methods"),
+ MethodPredicate.onName("foo"),
+ builder ->
+ buildUsesReflectionToAccessMethod(
+ builder,
+ Type.getType(
+ DescriptorUtils.javaTypeToDescriptor(
+ "com.android.tools.r8.keepanno.androidx.kt.MethodsKeptClass")),
+ "m")),
+ "com.android.tools.r8.keepanno.androidx.kt.MethodsKt",
+ getExpectedRulesKotlin(
+ "com.android.tools.r8.keepanno.androidx.kt.Methods",
+ "{ void foo(kotlin.reflect.KClass); }",
+ "{ *** m(...); }"),
+ StringUtils.lines("4"));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndIntParameterKotlin() throws Exception {
+ testExtractedRulesAndRunKotlin(
+ compilationResults,
+ (classReference, classFileBytes) ->
+ setAnnotationOnMethod(
+ classReference,
+ classFileBytes,
+ Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Methods"),
+ MethodPredicate.onName("foo"),
+ builder ->
+ buildUsesReflectionToAccessMethod(
+ builder,
+ Type.getType(
+ DescriptorUtils.javaTypeToDescriptor(
+ "com.android.tools.r8.keepanno.androidx.kt.MethodsKeptClass")),
+ "m",
+ int.class)),
+ "com.android.tools.r8.keepanno.androidx.kt.MethodsKt",
+ getExpectedRulesKotlin(
+ "com.android.tools.r8.keepanno.androidx.kt.Methods",
+ "{ void foo(kotlin.reflect.KClass); }",
+ "{ *** m(int); }"),
+ parameters.isReference() ? StringUtils.lines("4") : StringUtils.lines("1"));
+ }
+
+ @Test
+ public void testAnyReturnTypeAndMultipleParameterListsKotlin() throws Exception {
+ testExtractedRules(
+ compilationResults,
+ (classReference, classFileBytes) ->
+ setAnnotationOnMethod(
+ classReference,
+ classFileBytes,
+ Reference.classFromTypeName("com.android.tools.r8.keepanno.androidx.kt.Methods"),
+ MethodPredicate.onName("foo"),
+ builder ->
+ buildUsesReflectionToAccessMethodMultiple(
+ builder,
+ Type.getType(
+ DescriptorUtils.javaTypeToDescriptor(
+ "com.android.tools.r8.keepanno.androidx.kt.MethodsKeptClass")))),
+ getExpectedRulesKotlin(
+ "com.android.tools.r8.keepanno.androidx.kt.Methods",
+ "{ void foo(kotlin.reflect.KClass); }",
+ "{ *** m(int); }",
+ "{ *** m(int, long); }",
+ "{ *** m(java.lang.String, java.lang.String, java.lang.String); }"));
+ }
+
+ // Test class without annotation to be used by multiple tests inserting annotations using a
+ // transformer.
+ static class ClassWithAnnotation {
+
+ public void foo(Class<KeptClass> clazz) throws Exception {
+ if (clazz != null) {
+ System.out.println(clazz.getDeclaredMethods().length);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new ClassWithAnnotation().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+ }
+ }
+
+ static class KeptClass {
+ public void m() {}
+
+ public void m(int i) {}
+
+ public void m(int i, long l) {}
+
+ public void m(String s1, String s2, String s3) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Methods.kt b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Methods.kt
new file mode 100644
index 0000000..b61191e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/Methods.kt
@@ -0,0 +1,27 @@
+// Copyright (c) 2025, 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.androidx.kt
+
+import kotlin.reflect.KClass
+import kotlin.reflect.full.declaredMemberFunctions
+
+class Methods {
+ fun foo(clazz: KClass<MethodsKeptClass>?) {
+ println(clazz?.declaredMemberFunctions?.filter { it.name == "m" }?.size)
+ }
+}
+
+class MethodsKeptClass {
+ fun m() {}
+
+ fun m(i: Int) {}
+
+ fun m(i: Int, l: Long) {}
+
+ fun m(s1: String, s2: String, s3: String) {}
+}
+
+fun main() {
+ Methods().foo(if (System.nanoTime() > 0) MethodsKeptClass::class else null)
+}