Add a few more reflective resource callbacks for Class
Bug: b/400878112
Change-Id: I66bba46a9e87448e626acf011ed73b10af622d8b
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 67229c3..ef36cb6 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
@@ -18,4 +18,18 @@
void onClassNewInstance(Stack stack, Class<?> clazz);
void onClassGetDeclaredMethod(Stack stack, Class<?> clazz, String method, Class<?>... parameters);
+
+ void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName);
+
+ void onClassGetDeclaredMethods(Stack stack, Class<?> clazz);
+
+ void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType);
+
+ @KeepForApi
+ enum NameLookupType {
+ NAME,
+ SIMPLE_NAME,
+ CANONICAL_NAME,
+ TYPE_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 df14c36..5aa5b93 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.NameLookupType;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import java.util.Arrays;
@@ -72,10 +73,34 @@
getInstance().onClassGetDeclaredMethod(Stack.createStack(), clazz, name, parameters);
}
+ public static void onClassGetDeclaredMethods(Class<?> clazz) {
+ getInstance().onClassGetDeclaredMethods(Stack.createStack(), clazz);
+ }
+
public static void onClassForName(String className) {
getInstance().onClassForName(Stack.createStack(), className);
}
+ public static void onClassGetDeclaredField(Class<?> clazz, String fieldName) {
+ getInstance().onClassGetDeclaredField(Stack.createStack(), clazz, fieldName);
+ }
+
+ public static void onClassGetName(Class<?> clazz) {
+ getInstance().onClassGetName(Stack.createStack(), clazz, NameLookupType.NAME);
+ }
+
+ public static void onClassGetSimpleName(Class<?> clazz) {
+ getInstance().onClassGetName(Stack.createStack(), clazz, NameLookupType.SIMPLE_NAME);
+ }
+
+ public static void onClassGetCanonicalName(Class<?> clazz) {
+ getInstance().onClassGetName(Stack.createStack(), clazz, NameLookupType.CANONICAL_NAME);
+ }
+
+ public static void onClassGetTypeName(Class<?> clazz) {
+ getInstance().onClassGetName(Stack.createStack(), clazz, NameLookupType.TYPE_NAME);
+ }
+
@KeepForApi
public static class ReflectiveOperationLogger implements ReflectiveOperationReceiver {
@Override
@@ -90,6 +115,22 @@
}
@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);
}
diff --git a/src/main/java/com/android/tools/r8/R8AssistantCommand.java b/src/main/java/com/android/tools/r8/R8AssistantCommand.java
index f89a8e2..fb5bfac 100644
--- a/src/main/java/com/android/tools/r8/R8AssistantCommand.java
+++ b/src/main/java/com/android/tools/r8/R8AssistantCommand.java
@@ -145,19 +145,12 @@
@Override
R8AssistantCommand makeCommand() {
- ClassInjectionHelper injectionHelper = new ClassInjectionHelper(getReporter());
- String reason = "Reflective instrumentation";
- addClassProgramData(
- injectionHelper.getClassBytes(ReflectiveOracle.class),
- new SynthesizedOrigin(reason, ReflectiveOracle.class));
- addClassProgramData(
- injectionHelper.getClassBytes(Stack.class), new SynthesizedOrigin(reason, Stack.class));
- addClassProgramData(
- injectionHelper.getClassBytes(ReflectiveOperationReceiver.class),
- new SynthesizedOrigin(reason, ReflectiveOperationReceiver.class));
- addClassProgramData(
- injectionHelper.getClassBytes(ReflectiveOperationLogger.class),
- new SynthesizedOrigin(reason, ReflectiveOperationLogger.class));
+ injectClasses(
+ ReflectiveOperationLogger.class,
+ ReflectiveOperationReceiver.NameLookupType.class,
+ ReflectiveOperationReceiver.class,
+ ReflectiveOracle.class,
+ Stack.class);
return new R8AssistantCommand(
getAppBuilder().build(),
getMode(),
@@ -166,5 +159,14 @@
getReporter(),
reflectiveReceiverDescriptor);
}
+
+ private void injectClasses(Class<?>... classes) {
+ ClassInjectionHelper injectionHelper = new ClassInjectionHelper(getReporter());
+ String reason = "Reflective instrumentation";
+ for (Class<?> clazz : classes) {
+ addClassProgramData(
+ injectionHelper.getClassBytes(clazz), new SynthesizedOrigin(reason, clazz));
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/assistant/ReflectiveInstrumentation.java b/src/main/java/com/android/tools/r8/assistant/ReflectiveInstrumentation.java
index 62edbb1..5629f45 100644
--- a/src/main/java/com/android/tools/r8/assistant/ReflectiveInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/assistant/ReflectiveInstrumentation.java
@@ -116,21 +116,42 @@
dexItemFactory.classMethods.getDeclaredMethod,
getMethodReferenceWithClassMethodNameAndParameters("onClassGetDeclaredMethod"),
dexItemFactory.classMethods.forName,
- getMethodReferenceWithStringParameter("onClassForName"));
+ getMethodReferenceWithStringParameter("onClassForName"),
+ dexItemFactory.classMethods.getDeclaredField,
+ getMethodReferenceWithClassAndStringParameter("onClassGetDeclaredField"),
+ dexItemFactory.createMethod(
+ dexItemFactory.classType,
+ dexItemFactory.createProto(
+ dexItemFactory.createArrayType(1, dexItemFactory.methodType)),
+ "getDeclaredMethods"),
+ getMethodReferenceWithClassParameter("onClassGetDeclaredMethods"),
+ dexItemFactory.classMethods.getName,
+ getMethodReferenceWithClassParameter("onClassGetName"),
+ dexItemFactory.classMethods.getCanonicalName,
+ getMethodReferenceWithClassParameter("onClassGetCanonicalName"),
+ dexItemFactory.classMethods.getSimpleName,
+ getMethodReferenceWithClassParameter("onClassGetSimpleName"),
+ dexItemFactory.classMethods.getTypeName,
+ getMethodReferenceWithClassParameter("onClassGetTypeName"));
}
private DexMethod getMethodReferenceWithClassParameter(String name) {
- return getMethodReferenceWithSingleParameter(name, dexItemFactory.classType);
+ return getMethodReferenceWithParameterTypes(name, dexItemFactory.classType);
+ }
+
+ private DexMethod getMethodReferenceWithClassAndStringParameter(String name) {
+ return getMethodReferenceWithParameterTypes(
+ name, dexItemFactory.classType, dexItemFactory.stringType);
}
private DexMethod getMethodReferenceWithStringParameter(String name) {
- return getMethodReferenceWithSingleParameter(name, dexItemFactory.stringType);
+ return getMethodReferenceWithParameterTypes(name, dexItemFactory.stringType);
}
- private DexMethod getMethodReferenceWithSingleParameter(String name, DexType type) {
+ private DexMethod getMethodReferenceWithParameterTypes(String name, DexType... dexTypes) {
return dexItemFactory.createMethod(
reflectiveReferences.reflectiveOracleType,
- dexItemFactory.createProto(dexItemFactory.voidType, type),
+ dexItemFactory.createProto(dexItemFactory.voidType, dexTypes),
name);
}
diff --git a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java
new file mode 100644
index 0000000..3ef728c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTest.java
@@ -0,0 +1,94 @@
+// 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;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.assistant.JavaLangClassTestClass.Foo;
+import com.android.tools.r8.assistant.runtime.ReflectiveOracle.ReflectiveOperationLogger;
+import com.android.tools.r8.assistant.runtime.ReflectiveOracle.Stack;
+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 JavaLangClassTest extends TestBase {
+
+ @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(JavaLangClassTestClass.class, Foo.class)
+ .addInstrumentationClasses(Instrumentation.class)
+ .setCustomReflectiveOperationReceiver(Instrumentation.class)
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), JavaLangClassTestClass.class)
+ .assertSuccessWithOutputLines("5", "1", "2", "3", "3", "4", "5", "6", "7", "8");
+ }
+
+ public static class Instrumentation extends ReflectiveOperationLogger {
+
+ 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 onClassGetDeclaredMethod(
+ Stack stack, Class<?> clazz, String method, Class<?>... parameters) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 2);
+ }
+
+ @Override
+ public void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName) {
+ printNumIfTrue(
+ clazz.getName().endsWith("Foo") && (fieldName.equals("a") || fieldName.equals("b")), 3);
+ }
+
+ @Override
+ public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 4);
+ }
+
+ @Override
+ public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) {
+ if (lookupType == NameLookupType.NAME) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 5);
+ }
+ if (lookupType == NameLookupType.CANONICAL_NAME) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 6);
+ }
+ if (lookupType == NameLookupType.SIMPLE_NAME) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 7);
+ }
+ if (lookupType == NameLookupType.TYPE_NAME) {
+ printNumIfTrue(clazz.getName().endsWith("Foo"), 8);
+ }
+ }
+
+ @Override
+ public void onClassNewInstance(Stack stack, Class<?> clazz) {
+ super.onClassNewInstance(stack, clazz);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java
new file mode 100644
index 0000000..9ed11b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/assistant/JavaLangClassTestClass.java
@@ -0,0 +1,44 @@
+// 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;
+
+import java.lang.reflect.Method;
+
+// Top level file since the getXName methods relies on nest members being available for lookup.
+public class JavaLangClassTestClass {
+
+ public static void main(String[] args) {
+ try {
+ Class<?> clazz = Class.forName(Foo.class.getName());
+ clazz.getDeclaredMethod("bar");
+ clazz.getDeclaredField("a");
+ clazz.getDeclaredField("b");
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ String s = clazz.getName();
+ s += clazz.getCanonicalName();
+ s += clazz.getSimpleName();
+ try {
+ s += clazz.getTypeName();
+ } catch (NoSuchMethodError e) {
+ // getTypeName is only available on 26+
+ if (!e.getMessage().contains("getTypeName")) {
+ throw new RuntimeException(e);
+ }
+ }
+ } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public abstract static class Foo {
+ public static int a;
+ public int b;
+
+ public static void bar() {}
+
+ public void foo() {}
+
+ public abstract void fooBar();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java b/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
index 230b7d6..cec0774 100644
--- a/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
@@ -66,11 +66,12 @@
.setMinApi(parameters)
.compile()
.inspectOriginalDex(inspector -> inspectStaticCallsInReflectOn(1, inspector))
- .inspect(inspector -> inspectStaticCallsInReflectOn(4, inspector))
+ .inspect(inspector -> inspectStaticCallsInReflectOn(5, inspector))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines(
"Reflectively created new instance of " + Bar.class.getName(),
"Reflectively got declared method callMe on " + Bar.class.getName(),
+ "Reflectively got name on " + Bar.class.getName() + "(NAME)",
"Reflectively called Class.forName on " + Bar.class.getName());
}
@@ -83,7 +84,7 @@
.setMinApi(parameters)
.compile()
.inspectOriginalDex(inspector -> inspectStaticCallsInReflectOn(1, inspector))
- .inspect(inspector -> inspectStaticCallsInReflectOn(4, inspector))
+ .inspect(inspector -> inspectStaticCallsInReflectOn(5, inspector))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines(
"Custom receiver " + Bar.class.getName(),
@@ -101,7 +102,7 @@
.setMinApi(parameters)
.compile()
.inspectOriginalDex(inspector -> inspectStaticCallsInReflectOn(1, inspector))
- .inspect(inspector -> inspectStaticCallsInReflectOn(4, inspector))
+ .inspect(inspector -> inspectStaticCallsInReflectOn(5, inspector))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("correct", "correct", "correct");
}
@@ -168,6 +169,16 @@
ensureCorrectStack(stack);
}
+ // TODO(b/400878112): remove + below
+ @Override
+ public void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName) {}
+
+ @Override
+ public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) {}
+
+ @Override
+ public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) {}
+
@Override
public boolean requiresStackInformation() {
return true;
@@ -187,6 +198,11 @@
public static class InstrumentationClass implements ReflectiveOperationReceiver {
@Override
+ public boolean requiresStackInformation() {
+ return true;
+ }
+
+ @Override
public void onClassForName(Stack stack, String className) {
System.out.println("Custom receiver classForName " + className);
}
@@ -201,6 +217,16 @@
Stack stack, Class<?> clazz, String method, Class<?>... parameters) {
System.out.println("Custom receiver method " + method);
}
+
+ // TODO(b/400878112): remove + below
+ @Override
+ public void onClassGetDeclaredField(Stack stack, Class<?> clazz, String fieldName) {}
+
+ @Override
+ public void onClassGetDeclaredMethods(Stack stack, Class<?> clazz) {}
+
+ @Override
+ public void onClassGetName(Stack stack, Class<?> clazz, NameLookupType lookupType) {}
}
static class TestClass {