Support enum unboxing for kotlinc-dev

The kotlinc generated code for enum values() methods changed the
signature of the invoked clone() method.

Bug: b/323512667
Change-Id: I3327676442591c7450b2076d1e31347e0d0bca16
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 928fa07..3470908 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1354,7 +1354,6 @@
     return Reason.ELIGIBLE;
   }
 
-  @SuppressWarnings("ReferenceEquality")
   // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
   private Reason analyzeInvokeUser(
       InvokeMethod invoke,
@@ -1362,13 +1361,29 @@
       ProgramMethod context,
       DexProgramClass enumClass,
       Value enumValue) {
-    if (invoke.getInvokedMethod().holder.isArrayType()) {
+    DexType holder = invoke.getInvokedMethod().getHolderType();
+    if (holder.isArrayType()) {
       // The only valid methods is clone for values() to be correct.
-      if (invoke.getInvokedMethod().name == factory.cloneMethodName) {
+      if (invoke.getInvokedMethod().name.isIdenticalTo(factory.cloneMethodName)) {
         return Reason.ELIGIBLE;
       }
       return Reason.INVALID_INVOKE_ON_ARRAY;
     }
+    // Also allow clone on Object, as when kotlinc 2.0 generates the `values()` method it uses
+    // signature `Ljava/lang/Object;.clone:()Ljava/lang/Object;` and not
+    // `[Ljava/lang/Object;.clone:()Ljava/lang/Object;` as kotlinc 1.9 and before to create a clone
+    // of the $VALUES array. javac use the signature `[LEnumClass;.clone:()Ljava/lang/Object;`.
+    if (holder.isIdenticalTo(factory.objectType)
+        && invoke.getInvokedMethod().getName().isIdenticalTo(factory.cloneMethodName)
+        && invoke.getFirstArgument().getType().isArrayType()
+        && invoke
+            .getFirstArgument()
+            .getType()
+            .asArrayType()
+            .getBaseType()
+            .isClassType(enumClass.getType())) {
+      return Reason.ELIGIBLE;
+    }
 
     DexClassAndMethod resolvedMethod =
         appView
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceEnumUnboxingTest.java
new file mode 100644
index 0000000..84a4121
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceEnumUnboxingTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2024, 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.enumunboxing;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 CloneOnEnumInstanceEnumUnboxingTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "Got CloneNotSupportedException");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println(MyEnum.A);
+      try {
+        MyEnum.A.m();
+      } catch (CloneNotSupportedException e) {
+        System.out.println("Got CloneNotSupportedException");
+      }
+    }
+  }
+
+  enum MyEnum {
+    A;
+
+    void m() throws CloneNotSupportedException {
+      clone();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceInSubClassEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceInSubClassEnumUnboxingTest.java
new file mode 100644
index 0000000..d0fbdea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumInstanceInSubClassEnumUnboxingTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2024, 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.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 CloneOnEnumInstanceInSubClassEnumUnboxingTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+        .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "Hello B");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A);
+      System.out.println(MyEnum.B);
+    }
+  }
+
+  enum MyEnum {
+    A,
+    B {
+      @NeverInline
+      @Override
+      public String toString() {
+        try {
+          clone();
+          return "Not expected!";
+        } catch (CloneNotSupportedException e) {
+          return "Hello " + super.toString();
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumValuesEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumValuesEnumUnboxingTest.java
new file mode 100644
index 0000000..b3ef687
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CloneOnEnumValuesEnumUnboxingTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2024, 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.enumunboxing;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.ArrayList;
+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 CloneOnEnumValuesEnumUnboxingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(
+            inspector -> {
+              inspector.assertUnboxed(MyEnum1.class);
+              inspector.assertNotUnboxed(MyEnum2.class);
+              inspector.assertNotUnboxed(MyEnum3.class);
+            })
+        .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .addDontObfuscate()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "2", "B", "2", "A", "A", "B", "B");
+  }
+
+  static class Main {
+    @NeverInline
+    public static void m1(MyEnum1[] values) {
+      for (MyEnum1 e : values.clone()) {
+        System.out.println(e.name());
+        System.out.println(values.clone().length);
+      }
+    }
+
+    @NeverInline
+    public static void m2(MyEnum2[] values) {
+      for (MyEnum2 e : values) {
+        System.out.println(e.name());
+        System.out.println(values.clone()[e.ordinal()]);
+      }
+    }
+
+    @NeverInline
+    public static void m3(MyEnum3[] values) {
+      new ArrayList<>().add(values);
+    }
+
+    public static void main(String[] args) {
+      m1(MyEnum1.values());
+      m2(MyEnum2.values());
+      m3(MyEnum3.values());
+    }
+  }
+
+  enum MyEnum1 {
+    A,
+    B
+  }
+
+  enum MyEnum2 {
+    A,
+    B
+  }
+
+  enum MyEnum3 {
+    A,
+    B
+  }
+}