Extend modeling of Proxy to Proxy#getProxyClass

Bug: b/445971857
Change-Id: I0684beae36a3c9062617fc6db31f12639a2e62ee
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index b880ca8..ad19a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2942,9 +2942,15 @@
 
   public class ProxyMethods {
 
+    public final DexMethod getProxyClass;
     public final DexMethod newProxyInstance;
 
     private ProxyMethods() {
+      getProxyClass =
+          createMethod(
+              proxyType,
+              createProto(classType, classLoaderType, classArrayType),
+              createString("getProxyClass"));
       newProxyInstance =
           createMethod(
               proxyType,
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
index 0941d2a..5042fc5 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
@@ -107,7 +107,7 @@
   }
 
   @Override
-  public void onJavaLangReflectProxyNewProxyInstance(
+  public void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
       Set<DexProgramClass> classes, ProgramMethod context) {
     KeepReason reason = KeepReason.reflectiveUseIn(context);
     for (DexProgramClass clazz : classes) {
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
index ea733a4..50c601f 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
@@ -59,7 +59,7 @@
   }
 
   @Override
-  public void onJavaLangReflectProxyNewProxyInstance(
+  public void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
       Set<DexProgramClass> classes, ProgramMethod context) {
     for (DexProgramClass clazz : classes) {
       keep(clazz, context);
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
index 5d49cb7..8aef51a 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
@@ -97,7 +97,8 @@
       }
     } else if (holder.isIdenticalTo(factory.proxyType)) {
       // java.lang.reflect.Proxy
-      if (invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
+      if (invokedMethod.isIdenticalTo(factory.proxyMethods.getProxyClass)
+          || invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
         enqueue(method);
       }
     } else if (holder.isIdenticalTo(factory.serviceLoaderType)) {
@@ -187,8 +188,9 @@
       }
     } else if (holder.isIdenticalTo(factory.proxyType)) {
       // java.lang.reflect.Proxy
-      if (invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
-        handleJavaLangReflectProxyNewProxyInstance(method, invoke);
+      if (invokedMethod.isIdenticalTo(factory.proxyMethods.getProxyClass)
+          || invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
+        handleJavaLangReflectProxyGetProxyClassOrNewProxyInstance(method, invoke);
         return true;
       }
     } else if (holder.isIdenticalTo(factory.serviceLoaderType)) {
@@ -423,7 +425,7 @@
    * Handles reflective uses of {@link java.lang.reflect.Proxy#newProxyInstance(ClassLoader,
    * Class[], InvocationHandler)}.
    */
-  private void handleJavaLangReflectProxyNewProxyInstance(
+  private void handleJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
       ProgramMethod method, InvokeMethod invoke) {
     if (!invoke.isInvokeStatic()) {
       assert false;
@@ -466,7 +468,7 @@
       }
     }
     if (!classes.isEmpty()) {
-      eventConsumer.onJavaLangReflectProxyNewProxyInstance(classes, method);
+      eventConsumer.onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(classes, method);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
index d6255c3..3c3163b 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
@@ -24,7 +24,8 @@
 
   void onJavaLangReflectConstructorNewInstance(ProgramMethod initializer, ProgramMethod context);
 
-  void onJavaLangReflectProxyNewProxyInstance(Set<DexProgramClass> classes, ProgramMethod context);
+  void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
+      Set<DexProgramClass> classes, ProgramMethod context);
 
   void onJavaUtilConcurrentAtomicAtomicIntegerFieldUpdaterNewUpdater(
       ProgramField field, ProgramMethod context);
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
similarity index 64%
copy from src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
copy to src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
index ecdf0ba..93e146c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.
 
@@ -10,31 +10,31 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Proxy;
 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 InterfaceWithProxyTest extends TestBase {
+public class InterfaceWithGetProxyClassTest extends TestBase {
 
   private static final String EXPECTED = StringUtils.lines("Hello world!");
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InterfaceWithProxyTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
-        .addInnerClasses(InterfaceWithProxyTest.class)
+        .addInnerClasses(InterfaceWithGetProxyClassTest.class)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
@@ -42,7 +42,7 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(InterfaceWithProxyTest.class)
+        .addInnerClasses(InterfaceWithGetProxyClassTest.class)
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -53,17 +53,15 @@
 
   static class TestClass {
 
-    public static void main(String[] args) {
-      I obj =
-          (I)
-              Proxy.newProxyInstance(
-                  TestClass.class.getClassLoader(),
-                  new Class<?>[] {I.class},
-                  (proxy, method, args1) -> {
-                    System.out.print("Hello");
-                    return null;
-                  });
-      obj.greet();
+    public static void main(String[] args) throws Exception {
+      Class<?> proxyClass = Proxy.getProxyClass(TestClass.class.getClassLoader(), I.class);
+      InvocationHandler ih =
+          (proxy, method, args1) -> {
+            System.out.print("Hello");
+            return null;
+          };
+      I i = (I) proxyClass.getDeclaredConstructor(InvocationHandler.class).newInstance(ih);
+      i.greet();
       new A().greet();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
index ecdf0ba..a512441 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
@@ -14,27 +14,26 @@
 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 InterfaceWithProxyTest extends TestBase {
+public class InterfaceWithNewProxyInstanceTest extends TestBase {
 
   private static final String EXPECTED = StringUtils.lines("Hello world!");
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InterfaceWithProxyTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
-        .addInnerClasses(InterfaceWithProxyTest.class)
+        .addInnerClasses(InterfaceWithNewProxyInstanceTest.class)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
@@ -42,7 +41,7 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(InterfaceWithProxyTest.class)
+        .addInnerClasses(InterfaceWithNewProxyInstanceTest.class)
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()