堆叠在 Java 个虚拟机中

Stack in Java Virtual Machine

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

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

和class生成代码:

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)

默认构造函数的框架似乎将操作数堆栈大小设置为 0。但为什么会这样?我认为 ClassWriter.COPUTE_FRAMES 标志用于自动设置操作数 stack/local 变量数组大小。据我了解,异常是由 aload_0 指令将 this 加载到 Object.<init>.

的操作数堆栈引起的

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

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

但是 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 和一个操作数堆栈条目,即您在 invokespecial.

之前压入的 this 引用

一个完整的变体做一些可见的事情,手动计算最大值看起来像:

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