I'm playing with the support that ByteBuddy has for constant MethodHandle
s.
I am trying to (effectively) look up a MethodHandle
on one class, and then use it from a ByteBuddy-generated subclass.
(I am aware that I could do this using a static MethodHandle
field and a type initializer, but I wanted to try it using this constant support.)
I have a FieldDescription.Token
representing a field in the first class, and a TypeDescription
representing the first class. From these I can get a FieldDescription.InDefinedShape
like this: new FieldDescription.Latent(typeDescription, fieldDescriptionToken)
. From that I can get a JavaConstant.MethodHandle
like this: JavaConstant.MethodHandle.ofSetter(fieldDescriptionLatent)
. This works fine.
Then I do this:
// Call invokeExact() on the MethodHandle I looked up, but
// call it on that MethodHandle as a constant pool entry.
builder
.intercept(MethodCall.invoke(INVOKE_EXACT)
.on(new JavaConstantValue(javaConstantMethodHandle),
MethodHandle.class)
// etc.
By doing this, I am using the on
overload that takes a StackManipulation
, which in this case is the JavaConstantValue
which wraps a JavaConstant
, which is a superclass of JavaConstant.MethodHandle
. As you can see, I am attempting to call invokeExact()
on this MethodHandle
where the MethodHandle
is hopefully stored as a constant.
My first question is: is this the right recipe?
Next, the (stupid) field I am going to set in the superclass using this "field setter" MethodHandle
is named fortyTwo
and has a type of Integer
. You can probably guess what I want to set its value to. This is all fine, and obviously doesn't (yet) have anything to do with ByteBuddy, but it will.
Everything compiles fine.
When I run my code, before I ever get a chance to do anything, i.e. during class load, I get a ClassFormatError
for the generated subclass that ByteBuddy generates. The error complains that this subclass has attempted to define a field (!) with an invalid signature:
java.lang.ClassFormatError: Field "fortyTwo" in class com/foo/bar/GeneratedSubclassOf$com$foo$bar$Baz$26753A95 has illegal signature "V"
I am not defining such a field. The superclass does, of course (see above), and its type, as previously noted, is java.lang.Integer
. The access level of the field does not matter.
I have looked at the TypeDescription
contained by the DynamicType.Unloaded
(before it is loaded, obviously) and there are no fieldTokens
, i.e. ByteBuddy is truly not attempting to actually define a field in the subclass. It appears that something in the recipe I'm using makes it look to the…uh, verifier? I guess? don't really know? that the MethodHandle
represented by the constant is trying to manipulate the field on the subclass, and of course no such field exists (I'm going to guess that "V
" is the bytecode signature for void
and is probably the default).
So my last question is: Why does the use of a field-setting MethodHandle
constant make it look like ByteBuddy is attempting to define a field in the subclass in this case? It's as if the owning type of the field is dropped when the constant is defined or stored or something.
I presume this all has to do with how ByteBuddy supports constant MethodHandle
s. Could it be that ByteBuddy is not properly representing this kind of constant MethodHandle
in the constant pool? Or is this some sort of inherent problem with (maybe just field-setting) MethodHandle
s themselves?
I do notice that there is a reference (indirectly) to void
in the ByteBuddy code in question. This makes a certain amount of sense to me: if you're going to synthesize a method handle that sets a field, then its return type will be void
. I wonder though (naïvely) if this is in fact the proper type to pass here, or if this may be near where the problem lies.
On the other hand, it appears that in order to resolve a method handle constant that (in this case) is a "field setter", or a method handle of kinds 1 through 4, inclusive, resolution must proceed according to the JVM's rules of field resolution. Those rules seem (to this naïve reader) to indicate that the descriptor used should be the field's descriptor. I think that, instead, ByteBuddy is using the descriptor of the synthesized method handle which in this case would be V
(or void
). My naïve reading makes me think that at least in the case of "field setters" the return value of getDescriptor()
should instead be the type of the sole parameter present in the return value of getParameterTypes()
.
My apologies if I have misdiagnosed this; I am still learning.
For posterity: this turns out to be a bug in ByteBuddy for which I've submitted a PR.