Java虚拟机中的堆栈

时间:2018-03-18 16:37:01

标签: java constructor stack java-bytecode-asm

我正在尝试使用ASM动态生成一个类。这是我试过的:

public class ByteArrayClassLoader extends ClassLoader{
    public Class<?> defineClass(byte[] classData){
        return defineClass(null, classData, 0, classData.length);
    }
}

生成代码的类:

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);
    }
}

现在,当调用默认构造函数getKlass().newInstance();时,我得到以下异常:

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)

看起来Default构造函数的框架的操作数堆栈大小设置为0.但是为什么会发生这种情况呢?我认为ClassWriter.COPUTE_FRAMES标志用于自动设置操作数堆栈/本地变量数组大小。据我所知,异常是由aload_0指令将this加载到Object.<init>的操作数堆栈引起的。

无论如何,我试图将其明确设置为defaultCtor.visitFrame(F_NEW, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS }, 2, new Object[]{ UNINITIALIZED_THIS, UNINITIALIZED_THIS });,但得到了同样的错误。如何修复默认的构造函数?

1 个答案:

答案 0 :(得分:3)

首先,您对堆栈映射帧的目的存在误解。必须在分支合并点处插入这些帧以在此时声明堆栈帧状态。由于构造函数不包含分支,因此根本不需要插入任何框架。此外,您声明的两个局部变量和两个操作数堆栈条目与构造函数中任何一点的实际情况都不匹配。

但ASM的COMPUTE_FRAMES也暗示(重新)计算局部变量和操作数堆栈的最大值。代码的问题在于,即使未使用参数,您仍然必须调用关联的visitMaxs方法,以向ASM表明您已完成代码并且需要计算值。我在这里使用-1作为参数,以明确说明参数不是实际值,但ASM应该重新计算它们:

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());

当然,现在您将获得java.lang.AbstractMethodError,因为您尚未实施applyAsLong方法。

值得考虑自己提供价值而不是让ASM计算它们;毕竟,在编写代码时,您应该了解实际的堆栈布局。在构造函数中,您只有一个局部变量this和一个操作数堆栈条目,即this之前推送的invokespecial引用。

使用手动计算的最大值执行可见事物的完整变体如下所示:

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());
}