Search code examples
javabytecodejava-bytecode-asm

Why ASM could not figure out the right maxs for my generating class?


I'm using ASM 3.1 to generate a dummy class. It only has a simple constructor and no other methods:

public class TestAsm {

public static void main(String... args) throws Throwable {
    ClassWriter sw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
    sw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, "test/SubCls", null, "test/SuperCls", null);
    sw.visitField(Opcodes.ACC_PUBLIC, "i", "I", null, null);

    MethodVisitor mv = sw.visitMethod(0, "<init>", "()V", null, null);
    // mv.visitMaxs(2, 1);
    mv.visitCode();
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "test/SuperCls", "<init>", "()V");
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTFIELD, "test/SubCls", "i", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();

    sw.visitEnd();
    byte[] cls = sw.toByteArray();
    FileOutputStream fos = new FileOutputStream("bin/test/SubCls.class");
    fos.write(cls);
    fos.close();

    SuperCls o = (SuperCls) Class.forName("test.SubCls").newInstance();
    System.out.println(o.i);

    System.out.println(o.getClass().getDeclaredField("i").getInt(o));
    }
}

As you can see, I specified the ClassWriter.COMPUTE_FRAMES flag, which would compute the stack size and local size for all my methods, according to the documentation. But when I run this code, I get an error:

Exception in thread "main" java.lang.VerifyError: (class: test/SubCls, method: <init> signature: ()V) Stack size too large
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at test.TestAsm.main(TestAsm.java:33)

I examined the generated class file using 'javap -c' and found that the stack size and local size are incorrect:

$ javap -c SubCls -verbose
public class test.SubCls extends test.SuperCls
minor version: 0
major version: 50
Constant pool:
const #1 = Asciz    test/SubCls;
const #2 = class    #1; //  test/SubCls
const #3 = Asciz    test/SuperCls;
const #4 = class    #3; //  test/SuperCls
const #5 = Asciz    i;
const #6 = Asciz    I;
const #7 = Asciz    <init>;
const #8 = Asciz    ()V;
const #9 = NameAndType  #7:#8;//  "<init>":()V
const #10 = Method  #4.#9;  //  test/SuperCls."<init>":()V
const #11 = NameAndType #5:#6;//  i:I
const #12 = Field   #2.#11; //  test/SubCls.i:I
const #13 = Asciz   Code;

{
public int i;


test.SubCls();
  Code:
   Stack=0, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #10; //Method test/SuperCls."<init>":()V
   4:   aload_0
   5:   iconst_2
   6:   putfield    #12; //Field i:I
   9:   return

}

When I invoked visitMaxs(2, 1) manually, this error would go away.
But I've no idea why ASM could not compute the right maxs for me. Could anyone give me a hint?


Solution

  • Call mv.visitMaxs(0, 0);; I did read that somewhere, and found it so weird that I remembered.