raner
raner

Reputation: 1295

Byte Buddy code for passing a newly created object to a method

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

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

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

Related Questions