Reputation: 9521
I'm trying to generate a class dynamically using ASM
. Here is what I tried:
public class ByteArrayClassLoader extends ClassLoader{
public Class<?> defineClass(byte[] classData){
return defineClass(null, classData, 0, classData.length);
}
}
And the class generating code:
public class Tetst {
public static void main(String[] args) throws Throwable {
IntToLongFunction i2l = (IntToLongFunction) getKlass().newInstance();
System.out.println(i2l.applyAsLong(10));
}
public static Class<?> getKlass(){
String className = "HelloClass";
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classWriter.visit(
V1_8, ACC_PUBLIC, className, null, getInternalName(Object.class), new String[] { getInternalName(IntToLongFunction.class) }
);
MethodVisitor defaultCtor = classWriter.visitMethod(
ACC_PUBLIC, "<init>", "()V",null, null
);
defaultCtor.visitFrame(F_NEW, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS }, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS });
defaultCtor.visitVarInsn(ALOAD, 0);
defaultCtor.visitMethodInsn(INVOKESPECIAL, getInternalName(Object.class), "<init>", "()V", false);
defaultCtor.visitInsn(RETURN);
defaultCtor.visitEnd();
byte[] classData = classWriter.toByteArray();
return new ByteArrayClassLoader().defineClass(classData);
}
}
Now when invoking the default constructor as getKlass().newInstance();
I got the following exception:
Exception in thread "main" java.lang.VerifyError: Operand stack overflow
Exception Details:
Location:
HelloClass.<init>()V @0: aload_0
Reason:
Exceeded max stack size.
Current Frame:
bci: @0
flags: { flagThisUninit }
locals: { uninitializedThis }
stack: { }
Bytecode:
0x0000000: 2ab7 000a b1
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.newInstance(Class.java:412)
at Tetst.main(Tetst.java:12)
It seems the Default constructor's frame has the operand stack size set to 0. But why is that happening? I thought ClassWriter.COPUTE_FRAMES
flag which is for setting operand stack/local vars array sizes automatically. As far as I understand that the exception is caused by aload_0
instruction to load this
onto the operand stack for Object.<init>
.
Anyway I tried to set it explicitly as defaultCtor.visitFrame(F_NEW, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS }, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS });
but got the same error. How to fix the default contructor?
Upvotes: 3
Views: 375
Reputation: 298143
First of all, you have a misunderstanding of the purpose of stack map frames. These frames have to be inserted at branch merge points to declare the stack frame state right at this point. Since your constructor doesn’t contain branches, there is no need to insert any frame at all. Further, your declaration of two local variables and two operand stack entries doesn’t match the actual situation at any point in the constructor.
But ASM’s COMPUTE_FRAMES
also implies (re-)calculating the max values for local variables and the operand stack. The problem of your code is that you still have to invoke the associated visitMaxs
method, even if the arguments are not used, to indicate to ASM that you’re done with the code and the values need to be calculated. I use -1
as arguments here, to make it pretty clear that the arguments are not the actual values, but ASM is expected to recalculate them:
String className = "HelloClass";
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classWriter.visit(V1_8, ACC_PUBLIC, className, null,
getInternalName(Object.class), new String[]{getInternalName(IntToLongFunction.class)});
MethodVisitor defaultCtor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V",null,null);
defaultCtor.visitVarInsn(ALOAD, 0);
defaultCtor.visitMethodInsn(INVOKESPECIAL,
getInternalName(Object.class), "<init>", "()V", false);
defaultCtor.visitInsn(RETURN);
defaultCtor.visitMaxs(-1, -1);
defaultCtor.visitEnd();
return new ByteArrayClassLoader().defineClass(classWriter.toByteArray());
Of course, now you’ll get an java.lang.AbstractMethodError
as you’re not implementing the applyAsLong
method (yet).
It’s worth considering to provide the values yourself instead of letting ASM calculate them; after all, you should have an idea of the actual stack layout when writing the code. In your constructor, you only have one local variable, this
, and one operand stack entry, the this
reference you’ve pushed before invokespecial
.
A complete variant doing something visible, with manually calculated max values would look like:
public static void main(String[] args) throws Throwable {
IntToLongFunction i2l = (IntToLongFunction) getKlass().newInstance();
System.out.println(i2l.applyAsLong(10));
}
public static Class<?> getKlass(){
String className = "HelloClass";
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_8, ACC_PUBLIC, className, null, getInternalName(Object.class),
new String[] { getInternalName(IntToLongFunction.class) } );
MethodVisitor defaultCtor=classWriter.visitMethod(ACC_PUBLIC,"<init>","()V",null,null);
defaultCtor.visitVarInsn(ALOAD, 0);
defaultCtor.visitMethodInsn(INVOKESPECIAL,
getInternalName(Object.class), "<init>", "()V", false);
defaultCtor.visitInsn(RETURN);
defaultCtor.visitMaxs(1, 1);
defaultCtor.visitEnd();
MethodVisitor applyAsLong = classWriter.visitMethod(
ACC_PUBLIC, "applyAsLong", "(I)J",null,null);
applyAsLong.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
applyAsLong.visitLdcInsn("hello generated code"); // stack [PrintStream,String]
applyAsLong.visitMethodInsn(INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
applyAsLong.visitVarInsn(ILOAD, 1); // stack [int]
applyAsLong.visitInsn(I2L); // stack [long,*]
applyAsLong.visitInsn(LRETURN);
applyAsLong.visitMaxs(2, 2);// max stack see above, vars: [this,arg1:int]
applyAsLong.visitEnd();
return new ByteArrayClassLoader().defineClass(classWriter.toByteArray());
}
Upvotes: 5