1

I am using Byte Buddy 1.9.0 as a code generator for an Xtext-based programming language, and I am struggling to produce bytecode for a method that instantiates an "anonymous" class. For all intents and purposes, I'm trying to have Byte Buddy create a method that does the equivalent of this:

package fxxx.test;
class DummyUser
{
  DummyOperator dummy()
  {
    return new DummyUser$1();
  }
}

My compiler has already created the nested "anonymous" class DummyUser$1, but, for the life of me, I cannot come up with the correct Byte Buddy instruction sequence to create a new object of that class and then return that object. After some searching I came across MethodCall.construct(MethodDescription), but my current code only produces the following exception:

java.lang.IllegalStateException: Cannot return void from public fxxx.test.DummyOperator fxxx.test.DummyUser_.dummy()
    at net.bytebuddy.implementation.MethodCall$TerminationHandler$1.resolve(MethodCall.java:2189)
    at net.bytebuddy.implementation.MethodCall.toStackManipulation(MethodCall.java:2405)
    at net.bytebuddy.implementation.MethodCall$Appender.apply(MethodCall.java:2434)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:698)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:683)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:590)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:5114)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1915)
    at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:197)
    at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:174)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3376)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:3565)
...

This error seems to tell me, somewhat understandably, that since the constructor for the class has an <init>()V signature and technically returns void this is equivalent to returning a void value from a method that should really return some subclass of DummyOperator. I tried to combine the MethodCall.construct with a StackManipulation that duplicates the top of the stack, but as the TypeCreation via the new bytecode is already baked into the MethodCall.construct I don't seem to have access to the newly created raw object instance. The project is not open-sourced yet, but I can provide this snippet (using Xtend syntax) to illustrate what my code is doing:

    val Builder<?> anonymousClass = ... // the anonymous class that has already been created
    val TypeDescription declaringType = describe(anonymousClass) // basically .make.typeDescription
    val MethodDescription constructor = method(declaringType, new TypeDescription.ForLoadedType(void), "<init>", #[]) // new Latent declaringType/returnType
    val MethodCall construct = MethodCall.construct(constructor)
    // ^^^ this value gets return as the new method's Implementation

What am I missing so that the method will properly return the newly created instance?

raner
  • 1,175
  • 1
  • 11
  • 21
  • 2
    Byte-Buddy is a great tool for ad-hoc Instrumentation, but when you are implementing a compiler, I recommend using the ASM library directly. – Holger Feb 10 '20 at 08:48
  • Fair point, @Holger. I might switch directly to ASM at some point. For now, I find ByteBuddy pretty useful for doing a lot of the grunt work and validation. For example, getting the raw object creation (`new`), the constructor invocation (`invokespecial`) and the returned value (`areturn`) all in a single call (`MethodCall.construct`) is pretty handy! – raner Feb 10 '20 at 22:23
  • 1
    I fully agree with Holger here, Byte Buddy is not designed for this. And if you don't want to jump into the cold water immediately, Byte Buddy deliberately exposes ASM to allow you write custom byte code when you need to. – Rafael Winterhalter Feb 11 '20 at 08:37

1 Answers1

2

So, it looks like I wasn't missing any steps after all, or necessarily doing anything wrong about the object instantiation itself. When I ran this in the debugger, I noticed that the invalid stack manipulation was caused by a type mismatch, which indicated that the return type of method was actually not compatible with the type of the anonymous class (mistake on my part; the language's type system is a little more complex than, let's say, plain Java, and the two types were incompatible in a subtle way). The lesson learned, I guess, is to double-check the type compatibility when encountering exceptions like the above.

raner
  • 1,175
  • 1
  • 11
  • 21