简体   繁体   English

Java虚拟机中的堆栈

[英]Stack in Java Virtual Machine

I'm trying to generate a class dynamically using ASM . 我正在尝试使用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(); 现在,当调用默认构造函数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? 似乎Default构造函数的框架的操作数堆栈大小设置为0。但是为什么会这样呢? I thought ClassWriter.COPUTE_FRAMES flag which is for setting operand stack/local vars array sizes automatically. 我认为ClassWriter.COPUTE_FRAMES标志用于自动设置操作数堆栈/本地vars数组大小。 As far as I understand that the exception is caused by aload_0 instruction to load this onto the operand stack for Object.<init> . 据我明白,异常是由引起aload_0指令加载this到用于操作数栈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 }); 无论如何,我试图将其显式设置为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? 如何修复默认构造函数?

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. 但是ASM的COMPUTE_FRAMES还暗示(重新)计算局部变量和操作数堆栈的最大值。 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. 您的代码的问题在于,即使不使用参数,您仍然必须调用关联的visitMaxs方法,以向ASM表示您已完成代码并需要计算值。 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: 我在这里使用-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());

Of course, now you'll get an java.lang.AbstractMethodError as you're not implementing the applyAsLong method (yet). 当然,由于您尚未实现applyAsLong方法(现在),因此现在将收到java.lang.AbstractMethodError


It's worth considering to provide the values yourself instead of letting ASM calculate them; 值得考虑自己提供这些值,而不是让ASM计算它们。 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 . 在构造函数中,只有一个局部变量this和一个操作数堆栈条目,即您在invokespecial之前推送的this引用。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM