Reputation: 1295
So, I'm trying to generate some relatively simple code with Byte Buddy but I keep running into exceptions.
Basically, I'm trying to have Byte Buddy (version 1.9.0) generate the equivalent of the following Java class (this is just a simplified example; the problem happens generally when trying to pass a newly created object to a method):
public class CalendarSetter
{
public void setCalendarTime(Calendar calendar)
{
calendar.setTime(new Date());
}
}
The Byte Buddy code that I came up with is as follows (using Xtend syntax, but it's pretty close to Java):
val Builder<?> builder = new ByteBuddy()
.subclass(Object).name("CalendarSetter").merge(Visibility.PUBLIC)
.defineMethod("setCalendarTime", void, Visibility.PUBLIC)
.withParameter(Calendar)
.intercept(MethodCall.invoke(new ForLoadedMethod(Calendar.getDeclaredMethod("setTime", Date)))
.onArgument(0)
.withMethodCall(MethodCall.construct(Date.getConstructor)))
builder.make.load(class.classLoader).loaded
Unfortunately, this only produces the following exception:
java.lang.IllegalStateException: Cannot assign public java.util.Date() to java.util.Date arg0
at net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodCall.resolve(MethodCall.java:1470)
at net.bytebuddy.implementation.MethodCall.toStackManipulation(MethodCall.java:2397)
at net.bytebuddy.implementation.MethodCall$Appender.apply(MethodCall.java:2434)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:698)
...
Some further spelunking in the debugger suggests that this is due to the constructor for Date
(like all constructors) having a return type of void
, which, in turn, is not assignment-compatible with java.util.Date
. Byte Buddy's VoidAwareAssigner
produces an illegal StackManipulation
because the source type is void
whereas the target type is non-void
and the Typing
is not dynamic.
I would expect that the VoidAwareAssigner
should indeed be aware of constructors being essentially void
methods, but I'm probably missing something else here.
What is the proper way of passing a newly created object as a method argument in Byte Buddy?
UPDATE: I was able to avoid the exception by adding .withAssigner(custom, Typing.DYNAMIC)
with a custom "trivial" Assigner
that always returns a StackManipulation.Trivial
. This work-around produces seemingly correct (and working) bytecode:
public class CalendarSetter {
public void setCalendarTime(java.util.Calendar);
Code:
0: aload_1
1: new #8 // class java/util/Date
4: dup
5: invokespecial #12 // Method java/util/Date."<init>":()V
8: invokevirtual #18 // Method java/util/Calendar.setTime:(Ljava/util/Date;)V
11: return
...
However I still have a feeling that this is not really the correct approach and might be oversimplifying some scenarios that I'm not aware of...
Upvotes: 1
Views: 569
Reputation: 43972
You found a bug that I just fixed on the master branch. It will be part of the 1.10.12 release.
Byte Buddy incorrectly resolved the return type of the constructor to void.
Upvotes: 2