Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VerifyError with Compose Multiplatform project #349

Open
AlexeyTsvetkov opened this issue Jul 26, 2023 · 2 comments · May be fixed by #350
Open

VerifyError with Compose Multiplatform project #349

AlexeyTsvetkov opened this issue Jul 26, 2023 · 2 comments · May be fixed by #350

Comments

@AlexeyTsvetkov
Copy link

ProGuard 7.3.2
Kotlin 1.9.0
Compose Multiplatform 1.5.0-dev1114
JDK Corretto 17.0.5

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    kotlinx/coroutines/channels/BufferOverflow.values$1bedace4()[I @3: invokevirtual
  Reason:
    Type '[I' (current frame, stack[0]) is not assignable to '[Ljava/lang/Object;'
  Current Frame:
    bci: @3
    flags: { }
    locals: { }
    stack: { '[I' }
  Bytecode:
    0000000: b200 09b6 000d c000 05b0  

Reproducer: https://github.com/AlexeyTsvetkov/compose-proguard-optimization-issue
To reproduce:

  1. Run ./gradlew runDistributable. The app runs normally without ProGuard.
  2. Run ./gradlew runReleaseDistributable. The error above is thrown with ProGuard.
  3. If -dontoptimize is added to compose-desktop.pro, the app runs normally with ProGuard.

The configuration file passed to ProGuard can be found here (after runReleaseDistributable is run):

build/compose/tmp/proguardReleaseJars/root-config.pro

The ProGuard output jars can be found here (assuming ./gradlew runReleaseDistributable has run):

build/compose/binaries/main-release/app/proguard-optimizations-issue.app/Contents/app

The non-ProGuard output jars can be found here (assuming ./gradlew runDistributable has run):

build/compose/binaries/main/app/proguard-optimizations-issue.app/Contents/app
@mrjameshamilton
Copy link
Collaborator

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.

@acarlsen
Copy link

Similar/same issue with possible "fix".
JetBrains/compose-multiplatform#3947

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants