Stack map generation problem
David Lloyd
david.lloyd at redhat.com
Thu Dec 12 19:08:56 UTC 2024
Probably related: a few other classes which are being transformed with the
new method call are failing due to stack size calculation problems, like
this:
java.lang.IllegalArgumentException: Stack size mismatch at bytecode offset
204 of method discoverSources()
- max stack: 65535
max locals: 65535
attributes: []
//stack map frame @0: {locals:
[io/smallrye/config/SmallRyeConfigBuilder], stack: []}
0: {opcode: NEW, type: java/util/ArrayList}
3: {opcode: DUP}
4: {opcode: INVOKESPECIAL, owner: java/util/ArrayList, method name:
<init>, method type: ()V}
7: {opcode: ASTORE_1, slot: 1}
8: {opcode: LDC, constant value: 'ClassDesc[ConfigSource]'}
10: {opcode: ALOAD_0, slot: 0}
11: {opcode: GETFIELD, owner: io/smallrye/config/SmallRyeConfigBuilder,
field name: classLoader, field type: Ljava/lang/ClassLoader;}
14: {opcode: INVOKESTATIC, owner:
io/github/dmlloyd/modules/ModuleServiceLoader, method name: load, method
type: (Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;}
17: {opcode: ASTORE_2, slot: 2}
18: {opcode: ALOAD_2, slot: 2}
19: {opcode: INVOKEVIRTUAL, owner: java/util/ServiceLoader, method
name: iterator, method type: ()Ljava/util/Iterator;}
22: {opcode: ASTORE_3, slot: 3}
23: {opcode: ALOAD_3, slot: 3}
24: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
hasNext, method type: ()Z}
29: {opcode: IFEQ, target: 55}
32: {opcode: ALOAD_3, slot: 3}
33: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
next, method type: ()Ljava/lang/Object;}
38: {opcode: CHECKCAST, type:
org/eclipse/microprofile/config/spi/ConfigSource}
41: {opcode: ASTORE, slot: 4}
43: {opcode: ALOAD_1, slot: 1}
44: {opcode: ALOAD, slot: 4}
46: {opcode: INVOKEINTERFACE, owner: java/util/List, method name: add,
method type: (Ljava/lang/Object;)Z}
51: {opcode: POP}
52: {opcode: GOTO, target: 23}
55: {opcode: LDC, constant value: 'ClassDesc[ConfigSourceProvider]'}
57: {opcode: ALOAD_0, slot: 0}
58: {opcode: GETFIELD, owner: io/smallrye/config/SmallRyeConfigBuilder,
field name: classLoader, field type: Ljava/lang/ClassLoader;}
61: {opcode: INVOKESTATIC, owner:
io/github/dmlloyd/modules/ModuleServiceLoader, method name: load, method
type: (Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;}
64: {opcode: ASTORE_3, slot: 3}
65: {opcode: ALOAD_3, slot: 3}
66: {opcode: INVOKEVIRTUAL, owner: java/util/ServiceLoader, method
name: iterator, method type: ()Ljava/util/Iterator;}
69: {opcode: ASTORE, slot: 4}
71: {opcode: ALOAD, slot: 4}
73: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
hasNext, method type: ()Z}
78: {opcode: IFEQ, target: 148}
81: {opcode: ALOAD, slot: 4}
83: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
next, method type: ()Ljava/lang/Object;}
88: {opcode: CHECKCAST, type:
org/eclipse/microprofile/config/spi/ConfigSourceProvider}
91: {opcode: ASTORE, slot: 5}
93: {opcode: ALOAD, slot: 5}
95: {opcode: ALOAD_0, slot: 0}
96: {opcode: GETFIELD, owner: io/smallrye/config/SmallRyeConfigBuilder,
field name: classLoader, field type: Ljava/lang/ClassLoader;}
99: {opcode: INVOKEINTERFACE, owner:
org/eclipse/microprofile/config/spi/ConfigSourceProvider, method name:
getConfigSources, method type:
(Ljava/lang/ClassLoader;)Ljava/lang/Iterable;}
104: {opcode: INVOKEINTERFACE, owner: java/lang/Iterable, method name:
iterator, method type: ()Ljava/util/Iterator;}
109: {opcode: ASTORE, slot: 6}
111: {opcode: ALOAD, slot: 6}
113: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
hasNext, method type: ()Z}
118: {opcode: IFEQ, target: 145}
121: {opcode: ALOAD, slot: 6}
123: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
next, method type: ()Ljava/lang/Object;}
128: {opcode: CHECKCAST, type:
org/eclipse/microprofile/config/spi/ConfigSource}
131: {opcode: ASTORE, slot: 7}
133: {opcode: ALOAD_1, slot: 1}
134: {opcode: ALOAD, slot: 7}
136: {opcode: INVOKEINTERFACE, owner: java/util/List, method name: add,
method type: (Ljava/lang/Object;)Z}
141: {opcode: POP}
142: {opcode: GOTO, target: 111}
145: {opcode: GOTO, target: 71}
148: {opcode: LDC, constant value: 'ClassDesc[ConfigSourceFactory]'}
150: {opcode: ALOAD_0, slot: 0}
151: {opcode: GETFIELD, owner:
io/smallrye/config/SmallRyeConfigBuilder, field name: classLoader, field
type: Ljava/lang/ClassLoader;}
154: {opcode: INVOKESTATIC, owner:
io/github/dmlloyd/modules/ModuleServiceLoader, method name: load, method
type: (Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;}
157: {opcode: ASTORE, slot: 4}
159: {opcode: ALOAD, slot: 4}
161: {opcode: INVOKEVIRTUAL, owner: java/util/ServiceLoader, method
name: iterator, method type: ()Ljava/util/Iterator;}
164: {opcode: ASTORE, slot: 5}
166: {opcode: ALOAD, slot: 5}
168: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
hasNext, method type: ()Z}
173: {opcode: IFEQ, target: 207}
176: {opcode: ALOAD, slot: 5}
178: {opcode: INVOKEINTERFACE, owner: java/util/Iterator, method name:
next, method type: ()Ljava/lang/Object;}
183: {opcode: CHECKCAST, type: io/smallrye/config/ConfigSourceFactory}
186: {opcode: ASTORE, slot: 6}
188: {opcode: ALOAD_1, slot: 1}
189: {opcode: NEW, type: io/smallrye/config/ConfigurableConfigSource}
192: {opcode: DUP}
193: {opcode: ALOAD, slot: 6}
195: {opcode: INVOKESPECIAL, owner:
io/smallrye/config/ConfigurableConfigSource, method name: <init>, method
type: (Lio/smallrye/config/ConfigSourceFactory;)V}
198: {opcode: INVOKEINTERFACE, owner: java/util/List, method name: add,
method type: (Ljava/lang/Object;)Z}
203: {opcode: POP}
204: {opcode: GOTO, target: 166}
207: {opcode: ALOAD_1, slot: 1}
208: {opcode: ARETURN}
On Thu, Dec 12, 2024 at 11:11 AM David Lloyd <david.lloyd at redhat.com> wrote:
> 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
>
--
- DML • he/him
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20241212/16464028/attachment-0001.htm>
More information about the classfile-api-dev
mailing list