Stack map generation problem

Chen Liang chen.l.liang at oracle.com
Thu Dec 12 19:12:36 UTC 2024


Hi David,
I took a look at your example. Seems the only transformation done is intercepting the call at BCI 10. To confirm this is a problem with StackMapGenerator, can you create a ClassFile object with the Option StackMapsOption.GENERATE_STACK_MAPS, which forcibly discards the original stack map table to generate a new stack map table?
Also, you said that "I don't have a good way to test this with upstream directly at the moment." I think you can just get this Infrastructure class file, and process this file with JDK 24 and see.

Is this the problematic code that broke? https://github.com/smallrye/smallrye-mutiny/blob/fa30c10e3e6fb1ca372b923d4ebe20b07433633c/implementation/src/main/java/io/smallrye/mutiny/infrastructure/Infrastructure.java#L41-L54
I will grab the Infrastructure class in io.smallrye.reactive:mutiny:2.7.0 and check this resource class file in a recent JDK 24 build.  Please inform me if this is not the right binary, such as if you reproduced with a fork, an earlier version, or an ad-hoc patch.

Regards,
Chen Liang
________________________________
From: classfile-api-dev <classfile-api-dev-retn at openjdk.org> on behalf of David Lloyd <david.lloyd at redhat.com>
Sent: Thursday, December 12, 2024 11:11 AM
To: classfile-api-dev at openjdk.org <classfile-api-dev at openjdk.org>
Subject: Stack map generation problem

Initial disclaimer: I'm using my backport [1] of this API, so it's possible that I've introduced a bug that is not found upstream, or somehow missed a backport patch. I don't have a good way to test this with upstream directly at the moment.

I'm seeing a case where I'm transforming a class, and only changing calls to two overloads of `ServiceLoader.load()` to an equivalent on another class, for a test. But, the rewritten class triggers a `VerifyError` because one of the stack maps got a superfluous `int` added to it. This looks similar to a previously reported bug.

I've narrowed it down to when I change the owner type of the method being called. If I keep the original owner, then things pass through just fine - this might be because of some optimization where it doesn't detect a change, or maybe it is because I end up growing the constant pool by one?

Here's my transformation code (it's a little redundant in places but I was trying to rule out possible problems):
            ClassFile cf = ClassFile.of();
            ClassModel old = cf.parse(bytes);

            // then later...

            newBytes = cf.transformClass(old, old.thisClass(), ClassTransform.transformingMethodBodies(
                (cb, ce) -> {
                    if (ce instanceof InvokeInstruction ii
                        && ii.opcode() == Opcode.INVOKESTATIC
                        && ii.owner().name().equalsString("java/util/ServiceLoader")
                        && ii.name<http://ii.name>().equalsString("load")
                    ) {
                        switch (ii.type().stringValue()) {
                            case "(Ljava/lang/Class;)Ljava/util/ServiceLoader;",
                                 "(Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;" ->
                                cb.invokestatic(ModuleServiceLoader.class.describeConstable().orElseThrow(), ii.name<http://ii.name>().stringValue(), ii.typeSymbol(), ii.isInterface());
                            default ->
                                cb.invokestatic(ii.owner().asSymbol(), ii.name<http://ii.name>().stringValue(), ii.typeSymbol(), ii.isInterface());
                        }
                    } else {
                        cb.with(ce);
                    }
                }
            ));

Here's the original with a correct stack map:

  Stackmap Frame:
    bci: @61
    flags: { }
    locals: { 'java/util/ServiceLoader', 'java/util/Iterator' }
    stack: { integer }
  Bytecode:
    0000000: 1259 b800 5ab3 003b 125b b802 314b 2ab6
    0000010: 0040 4c2b b900 4101 0099 0021 2bb9 0042
    0000020: 0100 c000 5b4d 2cb9 005c 0100 125d b800
    0000030: 30c0 005e b800 09a7 0006 b800 5fb8 0060
    0000040: b800 61b2 0047 b300 2db2 0050 b300 4c11
    0000050: 0080 b300 0510 20b3 0006 1101 00b3 0007
    0000060: b1
              - {start: 28, line number: 45}
              - {start: 38, line number: 46}
              - {start: 55, line number: 47}
              - {start: 58, line number: 48}
              - {start: 61, line number: 51}
              - {start: 64, line number: 53}
              - {start: 67, line number: 63}
              - {start: 73, line number: 65}
              - {start: 79, line number: 67}
              - {start: 85, line number: 69}
              - {start: 90, line number: 70}
            local variables:
              - {start: 38, end: 55, slot: 2, name: next, type: Lio/smallrye/mutiny/infrastructure/ExecutorConfiguration;}
              - {start: 14, end: 67, slot: 0, name: executorLoader, type: Ljava/util/ServiceLoader;}
              - {start: 19, end: 67, slot: 1, name: iterator, type: Ljava/util/Iterator;}
            local variable types:
              - {start: 14, end: 67, slot: 0, name: executorLoader, signature: Ljava/util/ServiceLoader<Lio/smallrye/mutiny/infrastructure/ExecutorConfiguration;>;}
              - {start: 19, end: 67, slot: 1, name: iterator, signature: Ljava/util/Iterator<Lio/smallrye/mutiny/infrastructure/ExecutorConfiguration;>;}
            stack map frames:
                58: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
                61: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
            //stack map frame @0: {locals: [], stack: []}
            0: {opcode: LDC, constant value: mutiny.disableCallBackDecorators}
            2: {opcode: INVOKESTATIC, owner: java/lang/Boolean, method name: getBoolean, method type: (Ljava/lang/String;)Z}
            5: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: DISABLE_CALLBACK_DECORATORS, field type: Z}
            8: {opcode: LDC, constant value: 'ClassDesc[ExecutorConfiguration]'}
            10: {opcode: INVOKESTATIC, owner: java/util/ServiceLoader, method name: load, method type: (Ljava/lang/Class;)Ljava/util/ServiceLoader;}
            13: {opcode: ASTORE_0, slot: 0}
            14: {opcode: ALOAD_0, slot: 0, type: Ljava/util/ServiceLoader;, variable name: executorLoader}
            15: {opcode: INVOKEVIRTUAL, owner: java/util/ServiceLoader, method name: iterator, method type: ()Ljava/util/Iterator;}
            18: {opcode: ASTORE_1, slot: 1}
            19: {opcode: ALOAD_1, slot: 1, type: Ljava/util/Iterator;, variable name: iterator}
            20: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name: hasNext, method type: ()Z}
            25: {opcode: IFEQ, target: 58}
            28: {opcode: ALOAD_1, slot: 1, type: Ljava/util/Iterator;, variable name: iterator}
            29: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name: next, method type: ()Ljava/lang/Object;}
            34: {opcode: CHECKCAST, type: io/smallrye/mutiny/infrastructure/ExecutorConfiguration}
            37: {opcode: ASTORE_2, slot: 2}
            38: {opcode: ALOAD_2, slot: 2, type: Lio/smallrye/mutiny/infrastructure/ExecutorConfiguration;, variable name: next}
            39: {opcode: INVOKEINTERFACE, owner: io/smallrye/mutiny/infrastructure/ExecutorConfiguration, method name: getDefaultWorkerExecutor, method type: ()Ljava/util/concurrent/Executor;}
            44: {opcode: LDC, constant value: executor}
            46: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/helpers/ParameterValidation, method name: nonNull, method type: (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;}
            49: {opcode: CHECKCAST, type: java/util/concurrent/Executor}
            52: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: setDefaultExecutor, method type: (Ljava/util/concurrent/Executor;)V}
            55: {opcode: GOTO, target: 61}
            //stack map frame @58: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
            58: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: setDefaultExecutor, method type: ()V}
            //stack map frame @61: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
            61: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: reload, method type: ()V}
            64: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: resetCanCallerThreadBeBlockedSupplier, method type: ()V}
            67: {opcode: GETSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure$PrintAndDumpThrowableConsumer, field name: INSTANCE, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$PrintAndDumpThrowableConsumer;}
            70: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: droppedExceptionHandler, field type: Ljava/util/function/Consumer;}
            73: {opcode: GETSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure$PrintOperatorEventOperatorLogger, field name: INSTANCE, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$PrintOperatorEventOperatorLogger;}
            76: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: operatorLogger, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$OperatorLogger;}
            79: {opcode: SIPUSH, constant value: 128}
            82: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: multiOverflowDefaultBufferSize, field type: I}
            85: {opcode: BIPUSH, constant value: 32}
            87: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: bufferSizeXs, field type: I}
            90: {opcode: SIPUSH, constant value: 256}
            93: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: bufferSizeS, field type: I}
            96: {opcode: RETURN}

Here's the rewritten version with the wrong stack map:

  Current Frame:
    bci: @55
    flags: { }
    locals: { 'java/util/ServiceLoader', 'java/util/Iterator', 'io/smallrye/mutiny/infrastructure/ExecutorConfiguration' }
    stack: { }
  Stackmap Frame:
    bci: @61
    flags: { }
    locals: { 'java/util/ServiceLoader', 'java/util/Iterator' }
    stack: { integer }
  Bytecode:
    0000000: 1259 b800 5ab3 003b 125b b802 314b 2ab6
    0000010: 0040 4c2b b900 4101 0099 0021 2bb9 0042
    0000020: 0100 c000 5b4d 2cb9 005c 0100 125d b800
    0000030: 30c0 005e b800 09a7 0006 b800 5fb8 0060
    0000040: b800 61b2 0047 b300 2db2 0050 b300 4c11
    0000050: 0080 b300 0510 20b3 0006 1101 00b3 0007
    0000060: b1
              - {start: 14, line number: 43}
              - {start: 19, line number: 44}
              - {start: 28, line number: 45}
              - {start: 38, line number: 46}
              - {start: 55, line number: 47}
              - {start: 58, line number: 48}
              - {start: 61, line number: 51}
              - {start: 64, line number: 53}
              - {start: 67, line number: 63}
              - {start: 73, line number: 65}
              - {start: 79, line number: 67}
              - {start: 85, line number: 69}
              - {start: 90, line number: 70}
            stack map frames:
                58: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
                61: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: [int]}
            //stack map frame @0: {locals: [], stack: []}
            0: {opcode: LDC, constant value: mutiny.disableCallBackDecorators}
            2: {opcode: INVOKESTATIC, owner: java/lang/Boolean, method name: getBoolean, method type: (Ljava/lang/String;)Z}
            5: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: DISABLE_CALLBACK_DECORATORS, field type: Z}
            8: {opcode: LDC, constant value: 'ClassDesc[ExecutorConfiguration]'}
            10: {opcode: INVOKESTATIC, owner: io/github/dmlloyd/modules/ModuleServiceLoader, method name: load, method type: (Ljava/lang/Class;)Ljava/util/ServiceLoader;}
            13: {opcode: ASTORE_0, slot: 0}
            14: {opcode: ALOAD_0, slot: 0, type: Ljava/util/ServiceLoader;, variable name: executorLoader}
            15: {opcode: INVOKEVIRTUAL, owner: java/util/ServiceLoader, method name: iterator, method type: ()Ljava/util/Iterator;}
            18: {opcode: ASTORE_1, slot: 1}
            19: {opcode: ALOAD_1, slot: 1, type: Ljava/util/Iterator;, variable name: iterator}
            20: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name: hasNext, method type: ()Z}
            25: {opcode: IFEQ, target: 58}
            28: {opcode: ALOAD_1, slot: 1, type: Ljava/util/Iterator;, variable name: iterator}
            29: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name: next, method type: ()Ljava/lang/Object;}
            34: {opcode: CHECKCAST, type: io/smallrye/mutiny/infrastructure/ExecutorConfiguration}
            37: {opcode: ASTORE_2, slot: 2}
            38: {opcode: ALOAD_2, slot: 2, type: Lio/smallrye/mutiny/infrastructure/ExecutorConfiguration;, variable name: next}
            39: {opcode: INVOKEINTERFACE, owner: io/smallrye/mutiny/infrastructure/ExecutorConfiguration, method name: getDefaultWorkerExecutor, method type: ()Ljava/util/concurrent/Executor;}
            44: {opcode: LDC, constant value: executor}
            46: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/helpers/ParameterValidation, method name: nonNull, method type: (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;}
            49: {opcode: CHECKCAST, type: java/util/concurrent/Executor}
            52: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: setDefaultExecutor, method type: (Ljava/util/concurrent/Executor;)V}
            55: {opcode: GOTO, target: 61}
            //stack map frame @58: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: []}
            58: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: setDefaultExecutor, method type: ()V}
            //stack map frame @61: {locals: [java/util/ServiceLoader, java/util/Iterator], stack: [int]}
            61: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: reload, method type: ()V}
            64: {opcode: INVOKESTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, method name: resetCanCallerThreadBeBlockedSupplier, method type: ()V}
            67: {opcode: GETSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure$PrintAndDumpThrowableConsumer, field name: INSTANCE, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$PrintAndD
umpThrowableConsumer;}
            70: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: droppedExceptionHandler, field type: Ljava/util/function/Consumer;}
            73: {opcode: GETSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure$PrintOperatorEventOperatorLogger, field name: INSTANCE, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$PrintOperatorEventOperatorLogger;}
            76: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: operatorLogger, field type: Lio/smallrye/mutiny/infrastructure/Infrastructure$OperatorLogger;}
            79: {opcode: SIPUSH, constant value: 128}
            82: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: multiOverflowDefaultBufferSize, field type: I}
            85: {opcode: BIPUSH, constant value: 32}
            87: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: bufferSizeXs, field type: I}
            90: {opcode: SIPUSH, constant value: 256}
            93: {opcode: PUTSTATIC, owner: io/smallrye/mutiny/infrastructure/Infrastructure, field name: bufferSizeS, field type: I}
            96: {opcode: RETURN}

[1] https://github.com/dmlloyd/jdk-classfile-backport

--
- DML • he/him
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20241212/3b0d3b41/attachment-0001.htm>


More information about the classfile-api-dev mailing list