Stack map generation problem

David Lloyd david.lloyd at redhat.com
Thu Dec 12 17:11:51 UTC 2024


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().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().stringValue(), ii.typeSymbol(), ii.isInterface());
                            default ->
                                cb.invokestatic(ii.owner().asSymbol(),
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/7c2d4d2f/attachment-0001.htm>


More information about the classfile-api-dev mailing list