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