R8 assistant class flag support
Change-Id: Ib73e839312997ce3d478cd35f30c2609a45454e2
diff --git a/d8_r8/test_modules/tests_java_21/build.gradle.kts b/d8_r8/test_modules/tests_java_21/build.gradle.kts
index 75a1a4e..a44697d 100644
--- a/d8_r8/test_modules/tests_java_21/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_21/build.gradle.kts
@@ -23,12 +23,14 @@
val testbaseJavaCompileTask = projectTask("testbase", "compileJava")
val testbaseDepsJarTask = projectTask("testbase", "depsJar")
val mainCompileTask = projectTask("main", "compileJava")
+val assistantCompileTask = projectTask("assistant", "compileJava")
dependencies {
implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile()))
implementation(testbaseJavaCompileTask.outputs.files)
implementation(mainCompileTask.outputs.files)
implementation(projectTask("main", "processResources").outputs.files)
+ implementation(assistantCompileTask.outputs.files)
}
tasks {
@@ -48,6 +50,11 @@
layout.buildDirectory.dir("classes/java/test").get().toString())
systemProperty("TESTBASE_DATA_LOCATION",
testbaseJavaCompileTask.outputs.files.getAsPath().split(File.pathSeparator)[0])
+ systemProperty(
+ "BUILD_PROP_R8_RUNTIME_PATH",
+ mainCompileTask.outputs.files.getAsPath().split(File.pathSeparator)[0] +
+ File.pathSeparator + getRoot().resolveAll("src", "main", "resources") +
+ File.pathSeparator + assistantCompileTask.outputs.files.getAsPath().split(File.pathSeparator)[0])
}
val testJar by registering(Jar::class) {
diff --git a/d8_r8/test_modules/tests_java_21/settings.gradle.kts b/d8_r8/test_modules/tests_java_21/settings.gradle.kts
index 5961a4a..bac16df 100644
--- a/d8_r8/test_modules/tests_java_21/settings.gradle.kts
+++ b/d8_r8/test_modules/tests_java_21/settings.gradle.kts
@@ -25,5 +25,6 @@
val root = rootProject.projectDir.parentFile.parentFile
includeBuild(root.resolve("shared"))
+includeBuild(root.resolve("assistant"))
includeBuild(root.resolve("main"))
includeBuild(root.resolve("test_modules").resolve("testbase"))
\ No newline at end of file
diff --git a/src/assistant/java/com/android/tools/r8/assistant/runtime/EmptyReflectiveOperationReceiver.java b/src/assistant/java/com/android/tools/r8/assistant/runtime/EmptyReflectiveOperationReceiver.java
index b5c8c45..3d4353c 100644
--- a/src/assistant/java/com/android/tools/r8/assistant/runtime/EmptyReflectiveOperationReceiver.java
+++ b/src/assistant/java/com/android/tools/r8/assistant/runtime/EmptyReflectiveOperationReceiver.java
@@ -30,4 +30,7 @@
@Override
public void onClassGetSuperclass(Stack stack, Class<?> clazz) {}
+
+ @Override
+ public void onClassFlag(Stack stack, Class<?> clazz, ClassFlag classFlag) {}
}
diff --git a/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationLogger.java b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationLogger.java
new file mode 100644
index 0000000..a4a49eb
--- /dev/null
+++ b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationLogger.java
@@ -0,0 +1,58 @@
+// 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.assistant.runtime;
+
+import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public class ReflectiveOperationLogger implements ReflectiveOperationReceiver {
+
+ @Override
+ public void onClassNewInstance(Stack stack, Class<?> clazz) {
+ System.out.println("Reflectively created new instance of " + clazz.getName());
+ }
+
+ @Override
+ public void onClassGetDeclaredMethod(
+ Stack stack, Class<?> clazz, String method, Class<?>... parameters) {
+ System.out.println("Reflectively got declared method " + method + " on " + clazz.getName());
+ }
+
+ @Override
+ public void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName) {
+ System.out.println("Reflectively got declared field " + fieldName + " on " + clazz.getName());
+ }
+
+ @Override
+ public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) {
+ System.out.println("Reflectively got declared methods on " + clazz.getName());
+ }
+
+ @Override
+ public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) {
+ System.out.println(
+ "Reflectively got name on " + clazz.getName() + "(" + lookupType.toString() + ")");
+ }
+
+ @Override
+ public void onClassForName(Stack stack, String className) {
+ System.out.println("Reflectively called Class.forName on " + className);
+ }
+
+ @Override
+ public void onClassGetSuperclass(Stack stack, Class<?> clazz) {
+ System.out.println("Reflectively called Class.getSuperclass on " + clazz.getName());
+ }
+
+ @Override
+ public void onClassFlag(Stack stack, Class<?> clazz, ClassFlag classFlag) {
+ System.out.println("Reflectively got class flag " + classFlag);
+ }
+
+ public boolean requiresStackInformation() {
+ return true;
+ }
+}
diff --git a/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationReceiver.java b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationReceiver.java
index aa80d62..9c87b87 100644
--- a/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationReceiver.java
+++ b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOperationReceiver.java
@@ -27,6 +27,24 @@
void onClassGetSuperclass(Stack stack, Class<?> clazz);
+ void onClassFlag(Stack stack, Class<?> clazz, ClassFlag classFlag);
+
+ @KeepForApi
+ enum ClassFlag {
+ ANNOTATION,
+ ANONYMOUS_CLASS,
+ ARRAY,
+ ENUM,
+ HIDDEN,
+ INTERFACE,
+ LOCAL_CLASS,
+ MEMBER_CLASS,
+ PRIMITIVE,
+ RECORD,
+ SEALED,
+ SYNTHETIC
+ }
+
@KeepForApi
enum NameLookupType {
NAME,
diff --git a/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOracle.java b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOracle.java
index 33eb47f..e31f5b8 100644
--- a/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOracle.java
+++ b/src/assistant/java/com/android/tools/r8/assistant/runtime/ReflectiveOracle.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.assistant.runtime;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver.ClassFlag;
import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver.NameLookupType;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import java.util.Arrays;
@@ -105,48 +106,51 @@
getInstance().onClassGetSuperclass(Stack.createStack(), clazz);
}
- @KeepForApi
- public static class ReflectiveOperationLogger implements ReflectiveOperationReceiver {
- @Override
- public void onClassNewInstance(Stack stack, Class<?> clazz) {
- System.out.println("Reflectively created new instance of " + clazz.getName());
- }
+ public static void onClassIsAnnotation(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.ANNOTATION);
+ }
- @Override
- public void onClassGetDeclaredMethod(
- Stack stack, Class<?> clazz, String method, Class<?>... parameters) {
- System.out.println("Reflectively got declared method " + method + " on " + clazz.getName());
- }
+ public static void onClassIsAnonymousClass(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.ANONYMOUS_CLASS);
+ }
- @Override
- public void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName) {
- System.out.println("Reflectively got declared field " + fieldName + " on " + clazz.getName());
- }
+ public static void onClassIsArray(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.ARRAY);
+ }
- @Override
- public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) {
- System.out.println("Reflectively got declared methods on " + clazz.getName());
- }
+ public static void onClassIsEnum(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.ENUM);
+ }
- @Override
- public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) {
- System.out.println(
- "Reflectively got name on " + clazz.getName() + "(" + lookupType.toString() + ")");
- }
+ public static void onClassIsHidden(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.HIDDEN);
+ }
- @Override
- public void onClassForName(Stack stack, String className) {
- System.out.println("Reflectively called Class.forName on " + className);
- }
+ public static void onClassIsInterface(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.INTERFACE);
+ }
- @Override
- public void onClassGetSuperclass(Stack stack, Class<?> clazz) {
- System.out.println("Reflectively called Class.getSuperclass on " + clazz.getName());
- }
+ public static void onClassIsLocalClass(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.LOCAL_CLASS);
+ }
- @Override
- public boolean requiresStackInformation() {
- return true;
- }
+ public static void onClassIsMemberClass(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.MEMBER_CLASS);
+ }
+
+ public static void onClassIsPrimitive(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.PRIMITIVE);
+ }
+
+ public static void onClassIsRecord(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.RECORD);
+ }
+
+ public static void onClassIsSealed(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.SEALED);
+ }
+
+ public static void onClassIsSynthetic(Class<?> clazz) {
+ getInstance().onClassFlag(Stack.createStack(), clazz, ClassFlag.SYNTHETIC);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8AssistantCommand.java b/src/main/java/com/android/tools/r8/R8AssistantCommand.java
index b319253..55efbd7 100644
--- a/src/main/java/com/android/tools/r8/R8AssistantCommand.java
+++ b/src/main/java/com/android/tools/r8/R8AssistantCommand.java
@@ -5,9 +5,9 @@
import com.android.tools.r8.assistant.ClassInjectionHelper;
import com.android.tools.r8.assistant.runtime.EmptyReflectiveOperationReceiver;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationLogger;
import com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver;
import com.android.tools.r8.assistant.runtime.ReflectiveOracle;
-import com.android.tools.r8.assistant.runtime.ReflectiveOracle.ReflectiveOperationLogger;
import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Backend;
@@ -150,6 +150,7 @@
EmptyReflectiveOperationReceiver.class,
ReflectiveOperationLogger.class,
ReflectiveOperationReceiver.NameLookupType.class,
+ ReflectiveOperationReceiver.ClassFlag.class,
ReflectiveOperationReceiver.class,
ReflectiveOracle.class,
Stack.class);
diff --git a/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java b/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
index 8702d0a..335c1db 100644
--- a/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
+++ b/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
@@ -25,30 +26,79 @@
}
Map<DexMethod, DexMethod> getInstrumentedMethodsAndTargets() {
- return ImmutableMap.of(
+ ImmutableMap.Builder<DexMethod, DexMethod> builder = ImmutableMap.builder();
+
+ builder.put(
factory.classMethods.newInstance,
- getMethodReferenceWithClassParameter("onClassNewInstance"),
+ getMethodReferenceWithClassParameter("onClassNewInstance"));
+ builder.put(
factory.classMethods.getDeclaredMethod,
- getMethodReferenceWithClassMethodNameAndParameters("onClassGetDeclaredMethod"),
- factory.classMethods.forName,
- getMethodReferenceWithStringParameter("onClassForName"),
+ getMethodReferenceWithClassMethodNameAndParameters("onClassGetDeclaredMethod"));
+ builder.put(
+ factory.classMethods.forName, getMethodReferenceWithStringParameter("onClassForName"));
+ builder.put(
factory.classMethods.getDeclaredField,
- getMethodReferenceWithClassAndStringParameter("onClassGetDeclaredField"),
+ getMethodReferenceWithClassAndStringParameter("onClassGetDeclaredField"));
+ builder.put(
factory.createMethod(
factory.classType,
factory.createProto(factory.createArrayType(1, factory.methodType)),
"getDeclaredMethods"),
- getMethodReferenceWithClassParameter("onClassGetDeclaredMethods"),
- factory.classMethods.getName,
- getMethodReferenceWithClassParameter("onClassGetName"),
+ getMethodReferenceWithClassParameter("onClassGetDeclaredMethods"));
+ builder.put(
+ factory.classMethods.getName, getMethodReferenceWithClassParameter("onClassGetName"));
+ builder.put(
factory.classMethods.getCanonicalName,
- getMethodReferenceWithClassParameter("onClassGetCanonicalName"),
+ getMethodReferenceWithClassParameter("onClassGetCanonicalName"));
+ builder.put(
factory.classMethods.getSimpleName,
- getMethodReferenceWithClassParameter("onClassGetSimpleName"),
+ getMethodReferenceWithClassParameter("onClassGetSimpleName"));
+ builder.put(
factory.classMethods.getTypeName,
- getMethodReferenceWithClassParameter("onClassGetTypeName"),
+ getMethodReferenceWithClassParameter("onClassGetTypeName"));
+ builder.put(
factory.classMethods.getSuperclass,
getMethodReferenceWithClassParameter("onClassGetSuperclass"));
+
+ DexProto toBoolean = factory.createProto(factory.booleanType);
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isAnnotation"),
+ getMethodReferenceWithClassParameter("onClassIsAnnotation"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isAnonymousClass"),
+ getMethodReferenceWithClassParameter("onClassIsAnonymousClass"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isArray"),
+ getMethodReferenceWithClassParameter("onClassIsArray"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isEnum"),
+ getMethodReferenceWithClassParameter("onClassIsEnum"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isHidden"),
+ getMethodReferenceWithClassParameter("onClassIsHidden"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isInterface"),
+ getMethodReferenceWithClassParameter("onClassIsInterface"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isLocalClass"),
+ getMethodReferenceWithClassParameter("onClassIsLocalClass"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isMemberClass"),
+ getMethodReferenceWithClassParameter("onClassIsMemberClass"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isPrimitive"),
+ getMethodReferenceWithClassParameter("onClassIsPrimitive"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isRecord"),
+ getMethodReferenceWithClassParameter("onClassIsRecord"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isSealed"),
+ getMethodReferenceWithClassParameter("onClassIsSealed"));
+ builder.put(
+ factory.createMethod(factory.classType, toBoolean, "isSynthetic"),
+ getMethodReferenceWithClassParameter("onClassIsSynthetic"));
+
+ return builder.build();
}
private DexMethod getMethodReferenceWithClassParameter(String name) {
diff --git a/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractor.java b/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractor.java
index 1419d3c..090c748 100644
--- a/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractor.java
+++ b/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractor.java
@@ -59,9 +59,7 @@
DexType holderType = theMethod.getHolderType();
DexClass def = appInfo.definitionFor(holderType);
if (def != null && def.isLibraryClass()) {
- if (holderType.toSourceString().startsWith("java.lang.reflect")
- || holderType.isIdenticalTo(factory.unsafeType)
- || holderType.isIdenticalTo(factory.classType)) {
+ if (isReflectiveType(holderType, factory)) {
methods.computeIfAbsent(holderType, t -> new TreeSet<>()).add(theMethod);
}
}
@@ -73,6 +71,20 @@
return methods;
}
+ private static boolean isReflectiveType(DexType type, DexItemFactory factory) {
+ if (type.isIdenticalTo(factory.unsafeType) || type.isIdenticalTo(factory.classType)) {
+ return true;
+ }
+ String typeString = type.toSourceString();
+ if (!typeString.startsWith("java.lang.reflect")) {
+ return false;
+ }
+ if (type.isIdenticalTo(factory.createType("Ljava/lang/reflect/Modifier;"))) {
+ return false;
+ }
+ return !(typeString.endsWith("Exception") || typeString.endsWith("Error"));
+ }
+
public static String printMethods(Map<DexType, Collection<DexMethod>> methods) {
StringBuilder sb = new StringBuilder();
List<DexType> types = new ArrayList<>(methods.keySet());
diff --git a/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractorTest.java b/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractorTest.java
index 82f58ef..26c7761 100644
--- a/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractorTest.java
+++ b/src/test/java/com/android/tools/r8/assistant/ReflectiveCallExtractorTest.java
@@ -38,17 +38,17 @@
@Test
public void testGson() throws Exception {
- test(ToolHelper.GSON, 6, 69);
+ test(ToolHelper.GSON, 13, 56);
}
@Test
public void testGuava() throws Exception {
- test(ToolHelper.GUAVA_JRE, 6, 99);
+ test(ToolHelper.GUAVA_JRE, 12, 82);
}
@Test
public void testJacoco() throws Exception {
- test(ToolHelper.JACOCO_AGENT, 3, 17);
+ test(ToolHelper.JACOCO_AGENT, 6, 13);
}
private void test(Path jar, int success, int failure) throws Exception {
diff --git a/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21Test.java b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21Test.java
new file mode 100644
index 0000000..52e6945
--- /dev/null
+++ b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangClass21Test.java
@@ -0,0 +1,122 @@
+// 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.jdk21.assistant;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.assistant.runtime.EmptyReflectiveOperationReceiver;
+import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack;
+import com.android.tools.r8.jdk21.assistant.JavaLangTestClass21.Foo;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JavaLangClass21Test extends TestBase {
+
+ private static final String EXPECTED_RESULT_LOW_API =
+ StringUtils.lines(
+ "1",
+ "ANNOTATION",
+ "ANONYMOUS_CLASS",
+ "ARRAY",
+ "ENUM",
+ "HIDDEN",
+ "Missing isHidden",
+ "INTERFACE",
+ "LOCAL_CLASS",
+ "MEMBER_CLASS",
+ "PRIMITIVE",
+ "RECORD",
+ "Missing isRecord",
+ "SEALED",
+ "Missing isSealed",
+ "SYNTHETIC");
+
+ private static final String EXPECTED_RESULT_U =
+ StringUtils.lines(
+ "1",
+ "ANNOTATION",
+ "ANONYMOUS_CLASS",
+ "ARRAY",
+ "ENUM",
+ "HIDDEN",
+ "Missing isHidden",
+ "INTERFACE",
+ "LOCAL_CLASS",
+ "MEMBER_CLASS",
+ "PRIMITIVE",
+ "RECORD",
+ "SEALED",
+ "SYNTHETIC");
+
+ private static final String EXPECTED_RESULT_HIGH_API =
+ StringUtils.lines(
+ "1",
+ "ANNOTATION",
+ "ANONYMOUS_CLASS",
+ "ARRAY",
+ "ENUM",
+ "HIDDEN",
+ "INTERFACE",
+ "LOCAL_CLASS",
+ "MEMBER_CLASS",
+ "PRIMITIVE",
+ "RECORD",
+ "SEALED",
+ "SYNTHETIC");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNativeMultidexDexRuntimes().withMaximumApiLevel().build();
+ }
+
+ @Test
+ public void testInstrumentationWithCustomOracle() throws Exception {
+ testForAssistant()
+ .addProgramClasses(JavaLangTestClass21.class, Foo.class)
+ .addStrippedOuter(getClass())
+ .addInstrumentationClasses(Instrumentation.class)
+ .setCustomReflectiveOperationReceiver(Instrumentation.class)
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), JavaLangTestClass21.class)
+ .assertSuccessWithOutput(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.U)
+ ? EXPECTED_RESULT_LOW_API
+ : (parameters.getApiLevel().isEqualTo(AndroidApiLevel.U)
+ ? EXPECTED_RESULT_U
+ : EXPECTED_RESULT_HIGH_API));
+ }
+
+ public static class Instrumentation extends EmptyReflectiveOperationReceiver {
+
+ private void printNumIfTrue(boolean correct, int num) {
+ if (correct) {
+ System.out.println(num);
+ } else {
+ System.out.println("fail");
+ }
+ }
+
+ @Override
+ public void onClassForName(Stack stack, String className) {
+ printNumIfTrue(className.endsWith("Foo"), 1);
+ }
+
+ @Override
+ public void onClassFlag(Stack stack, Class<?> clazz, ClassFlag classFlag) {
+ System.out.println(classFlag);
+ }
+ }
+}
diff --git a/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangTestClass21.java b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangTestClass21.java
new file mode 100644
index 0000000..89ef8c2
--- /dev/null
+++ b/src/test/java21/com/android/tools/r8/jdk21/assistant/JavaLangTestClass21.java
@@ -0,0 +1,41 @@
+// 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.jdk21.assistant;
+
+public class JavaLangTestClass21 {
+
+ public static void main(String[] args) {
+ try {
+ Class<?> clazz = Class.forName(Foo.class.getName());
+ boolean ann = clazz.isAnnotation();
+ boolean anc = clazz.isAnonymousClass();
+ boolean arr = clazz.isArray();
+ boolean enu = clazz.isEnum();
+ try {
+ boolean hid = clazz.isHidden();
+ } catch (Throwable t) {
+ System.out.println("Missing isHidden");
+ }
+ boolean itf = clazz.isInterface();
+ boolean lcl = clazz.isLocalClass();
+ boolean mem = clazz.isMemberClass();
+ boolean pri = clazz.isPrimitive();
+ try {
+ boolean rec = clazz.isRecord();
+ } catch (Throwable t) {
+ System.out.println("Missing isRecord");
+ }
+ try {
+ boolean sea = clazz.isSealed();
+ } catch (Throwable t) {
+ System.out.println("Missing isSealed");
+ }
+ boolean syn = clazz.isSynthetic();
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static class Foo {}
+}