How do I install and use a constant MethodHandle in ByteBuddy?

222 views Asked by At

I'm playing with the support that ByteBuddy has for constant MethodHandles.

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 MethodHandles. 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) MethodHandles 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.

1

There are 1 answers

0
Laird Nelson On BEST ANSWER

For posterity: this turns out to be a bug in ByteBuddy for which I've submitted a PR.