How to use an anonymous class instance in another

2019-07-23 19:13发布

问题:

I have difficulty in using a generated bytecode class which is loaded by Unsafe.defineAnonymousClass(). I am wondering how to use an object of anonymous class to initiliaze another class (or anonymous class).

Take an example class Callee below for example, its constructor accepts Callee2 as parameter.

Class Callee{
    Callee2 _call2;
    public Callee(Callee2 callee2){
        ...
    }
}

During runtime, I generated new classes for both Callee2 and Callee, and both new classes are loaded by Unsafe.defineAnonymousClass(). For new Callee, the constructor is also changed to be:

 public test.code.jit.asm.simple.Callee(test.code.jit.asm.simple.Callee2.1506553666);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: aload_0       
         1: invokespecial #65                 // Method java/lang/Object."<init>":()V
           ....
         8: return       

while the generated class name of Callee2 is:

      class test.code.jit.asm.simple.Callee2/1506553666

I created an instance of `Callee2/1506553666' and want to create instance of new Callee with it, but fail:

        newCls.getConstructor(args).newInstance(objs); 

where args = [class test.code.jit.asm.simple.Callee2/1506553666] and objs= [test.code.jit.asm.simple.Callee2/1506553666@39b0a038]

The args[0] is meanless as this class is loaded by anonymous loader (Anonymous classes are not referenced by any class loaders). So it really puzzles me how to pass objects in objs array to a method invocation..

The exception occurs on the invocation of getConstructor (args) with message:

java.lang.NoClassDefFoundError: test/code/jit/asm/simple/Callee2/1506553666
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2483)
    at java.lang.Class.getConstructor0(Class.java:2793)
    at java.lang.Class.getConstructor(Class.java:1708)
    at code.jit.asm.util.ReflectionUtil.adapt2GeneratedObject(ReflectionUtil.java:36)
    at code.jit.asm.services.BytecodeGenerator.generator(BytecodeGenerator.java:164)

The exception is clearly for me since the anonymous class is transient to any classloader. But in my case, I do need initialize the new Callee (also anonymous class) by new Callee2 instance (The bytecodes in Callee's constructor will reads Callee2's field members), so is there any workaround to pass new callee2 instance for the new callee's constructor?

回答1:

Have a look at the signature and documentation comment, which is not available in the standard API documentation as it’s not part of the official API:

Define a class but do not make it known to the class loader or system dictionary. For each CP entry, the corresponding CP patch must either be null or have the a format that matches its tag:

  • Integer, Long, Float, Double: the corresponding wrapper object type from java.lang
  • Utf8: a string (must have suitable syntax if used as signature or name) Class: any java.lang.Class object
  • String: any object (not just a java.lang.String)
  • InterfaceMethodRef: (NYI) a method handle to invoke on that call site's arguments

… (params:)

cpPatches where non-null entries exist, they replace corresponding CP entries in data

public native Class<?> defineAnonymousClass(
                       Class<?> hostClass, byte[] data, Object[] cpPatches);

In other words, you may provide an array of the same size as the constant pool of the class you’re going to define. Keep nulls where you don’t want to modify it. Right at the places where your constant pool has a CONSTANT_Class_info representing an anonymous class, you simply pass the associated Class object in the array. So there’s no lookup for the class then, you don’t even have to provide the correct class name in the class bytes.

There are some obvious limitations:

  • Problem will arise if you have circular dependencies as you need an already existing Class object to patch the pool of another class. Well, for class uses that are known to be resolved lazily, it might work
  • You can only patch CONSTANT_Class_info to a Class which is sufficient for, e.g. accessing members of that class or creating new instances of it. But it doesn’t help when an anonymous class is part of a signature, i.e. you want to declare a field of that type or use a method having it as parameter or return type. But you may access such methods using the option of patching a CONSTANT_InterfaceMethodref_info entry via a MethodHandle (ahem, once implemented as I guess “NYI” means “not yet implemented”)…