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
+ }
+}