Open AlexeyTsvetkov opened 1 year ago
Hi @AlexeyTsvetkov !
The issue lies in the class/unboxing/enum
optimization (https://www.guardsquare.com/manual/configuration/optimizations).
Firstly, as a work around you can disable this optimization by adding -optimizations !class/unboxing/enum
to your ProGuard configuration.
Unfortunately, in your sample this leads to another exception that will need further investigation.
The problem: the enum optimization replaces simple enums with integers and the problem occurs in the values()
method of the enum class.
The original method looks like this:
public static kotlinx.coroutines.channels.BufferOverflow[] values();
descriptor: ()[Lkotlinx/coroutines/channels/BufferOverflow;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #22 // Field $VALUES:[Lkotlinx/coroutines/channels/BufferOverflow;
3: invokevirtual #28 // Method "[Ljava/lang/Object;".clone:()Ljava/lang/Object;
6: checkcast #29 // class "[Lkotlinx/coroutines/channels/BufferOverflow;"
9: areturn
And the optimized method looks like this:
public static int[] values$1bedace4();
descriptor: ()[I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #9 // Field $VALUES$cb20445:[I
3: invokevirtual #13 // Method "[Ljava/lang/Object;".clone:()Ljava/lang/Object;
6: checkcast #5 // class "[I"
9: areturn
The values array had been transformed from an enum array [Lkotlinx/coroutines/channels/BufferOverflow;
to an integer array [I
, which is fine.
Except the next instruction is a method call to [java/lang/Object;->clone()
which is no longer correct because, as the error says, integer array is not assignable to Object array ("'[I' (current frame, stack[0]) is not assignable to '[Ljava/lang/Object;'").
This case would normally be handled in SimpleEnumDescriptorSimplifier
here. In the simplifyDescriptor
method which is called via visitClassConstant
, the class constant is checked if it is a simple enum and if so the descriptor is replaced by an integer array descriptor:
return isSimpleEnum(referencedClass) ?
descriptor.substring(0, ClassUtil.internalArrayTypeDimensionCount(descriptor)) + TypeConstants.INT :
descriptor;
But in the original snippet, the class referenced is [Ljava/lang/Object;
rather than the enum class itself so it would not have been updated.
This seems to be a difference in the code generated by the Kotlin compiler vs Java compilers.
If you compile the following with a Java compiler (OpenJDK Runtime Environment Temurin-17.0.8+7 (build 17.0.8+7)
): public enum EnumTest { A, B, C }
. The values method looks like this:
public static EnumTest[] values();
descriptor: ()[LEnumTest;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #13 // Field $VALUES:[LEnumTest;
3: invokevirtual #17 // Method "[LEnumTest;".clone:()Ljava/lang/Object;
6: checkcast #18 // class "[LEnumTest;"
9: areturn
Whereas, if you compile the following with a Kotlin compiler (kotlinc-jvm 1.8.0), enum class KotlinEnumTest { A, B, C }
, the clone()
method call references [Ljava/lang/Object;
:
public static KotlinEnumTest[] values();
descriptor: ()[LKotlinEnumTest;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #22 // Field $VALUES:[LKotlinEnumTest;
3: invokevirtual #28 // Method "[Ljava/lang/Object;".clone:()Ljava/lang/Object;
6: checkcast #29 // class "[LKotlinEnumTest;"
9: areturn
It looks like we'll have to take this into account to be able to correctly apply this optimization to enums generated by kotlinc
.
Similar/same issue with possible "fix". https://github.com/JetBrains/compose-multiplatform/issues/3947
ProGuard 7.3.2 Kotlin 1.9.0 Compose Multiplatform 1.5.0-dev1114 JDK Corretto 17.0.5
Reproducer: https://github.com/AlexeyTsvetkov/compose-proguard-optimization-issue To reproduce:
./gradlew runDistributable
. The app runs normally without ProGuard../gradlew runReleaseDistributable
. The error above is thrown with ProGuard.-dontoptimize
is added tocompose-desktop.pro
, the app runs normally with ProGuard.The configuration file passed to ProGuard can be found here (after
runReleaseDistributable
is run):The ProGuard output jars can be found here (assuming
./gradlew runReleaseDistributable
has run):The non-ProGuard output jars can be found here (assuming
./gradlew runDistributable
has run):