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 {