proguard icon indicating copy to clipboard operation
proguard copied to clipboard

Bytecode retains seemingly pointless `checkcast` instruction causing `VerifyError`

Open JakeWharton opened this issue 11 months ago • 2 comments

I'm attempting to run ProGuard 7.6.1 on my library's unit tests to assert they all still pass when the library is minified. My library depends on kotlinx.coroutines which contains code that ProGuard processes into an invalid state causing a VerifyError:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    kotlinx/coroutines/JobKt.invokeOnCompletion(Lkotlinx/coroutines/Job;ZLkotlinx/coroutines/JobNode;)Lkotlinx/coroutines/DisposableHandle; @40: invokeinterface
  Reason:
    Type 'kotlin/jvm/functions/Function1' (current frame, stack[3]) is not assignable to 'kotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1'
  Current Frame:
    bci: @40
    flags: { }
    locals: { 'kotlinx/coroutines/Job', integer, 'kotlinx/coroutines/JobNode' }
    stack: { 'kotlinx/coroutines/Job', integer, integer, 'kotlin/jvm/functions/Function1' }
  Bytecode:
    0000000: 2a1b 2c4d 3c59 4bc1 0007 9900 0d2a c000
    0000010: 071b 2cb6 000a b02a 2cb6 0009 1bbb 0005
    0000020: 592c b700 08c0 0002 b900 0b04 00b0
  Stackmap Table:
    same_frame(@23)

    at kotlinx.coroutines.JobKt__JobKt.invokeOnCompletion$default$7beb268f(Job.kt:351)
    at kotlinx.coroutines.CancellableContinuationImpl.installParentHandle(CancellableContinuationImpl.kt:23001)
    at kotlinx.coroutines.CancellableContinuationImpl.initCancellability(CancellableContinuationImpl.kt:126)
    at kotlinx.coroutines.DelayKt.delay-VtjQ1oo(Delay.kt:2178)

The offending bytecode is this:

29: new           #5                  // class kotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1
32: dup
33: aload_2
34: invokespecial #8                  // Method kotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1."<init>":(Lkotlinx/coroutines/JobNode;)V
37: checkcast     #2                  // class kotlin/jvm/functions/Function1
40: invokeinterface #11,  4           // InterfaceMethod kotlinx/coroutines/Job.invokeOnCompletion$87b5a68:(ZZLkotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1;)Lkotlinx/coroutines/DisposableHandle;

Here we can see that the offending bytecode 40 is an invoke which accepts a type of kotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1 but the bytecode prior, 37, is doing a cast to kotlin/jvm/functions/Function1 which causes the error:

Type 'kotlin/jvm/functions/Function1' (current frame, stack[3]) is not assignable to 'kotlinx/coroutines/JobKt__JobKt$invokeOnCompletion$1'

The original function in kotlinx.coroutines uses the Function1 type for its parameter, and the anonymous $1 class does implement that interface. ProGuard seems to be lifting the parameter type to the sole implementation the method is invoked with, but failing to remove the now-needless and error-inducing checkcast.

A PR with the offending build setup is here: https://github.com/JakeWharton/mosaic/pull/591

If this is too difficult to run to debug in its current state (Gradle's build classpath), I'm happy to create an isolated project that reproduces.

JakeWharton avatar Jan 07 '25 21:01 JakeWharton

Thanks for the report!

I was able to patch your PR and reproduce the issue locally. It indeed seems to be caused by the method/specialization/parametertype optimization not detecting the check cast right before that. If you need to unblock your pr, you can temporarily disable only that optimization with this rule:

-optimizations !method/specialization/parametertype

It solves the issue locally for me. Looking at what's going wrong, it seems we don't handle such cases in the MemberDescriptorSpecializer

piazzesiNiccolo-GS avatar Jan 08 '25 08:01 piazzesiNiccolo-GS

Thanks! I'll use that workaround for now.

JakeWharton avatar Jan 08 '25 15:01 JakeWharton