Search code examples
java-7bytecodejava-bytecode-asmbytecode-manipulationstack-frame

Construct FrameNode with ASM api


I succeeded in implementing bytecode method inline optimization and the generated code seems OK for me. Yet, the verification fails with message:

java.lang.VerifyError: Expecting a stackmap frame at branch target 47
Exception Details:
  Location:
    code/sxu/asm/example/Caller.test(II)V @44: goto
  Reason:
    Expected stackmap frame at this location.

And the corresponding bytecode is:

public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=8, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: aload_0       
         4: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         7: iload_1       
         8: iload_2       
         9: istore_3      
        10: istore        4
        12: astore        5
        14: aload         5
        16: getfield      #40                 // Field code/sxu/asm/example/Callee._a:Ljava/lang/String;
        19: invokevirtual #46                 // Method java/lang/String.length:()I
        22: aload         5
        24: getfield      #49                 // Field code/sxu/asm/example/Callee._b:Ljava/lang/String;
        27: invokevirtual #46                 // Method java/lang/String.length:()I
        30: iadd          
        31: istore        6
        33: iload         6
        35: iload         4
        37: iload_3       
        38: iadd          
        39: iadd          
        40: istore        6
        42: iload         6
        44: goto          47
        47: isub          
        48: istore        7
        50: getstatic     #59                 // Field java/lang/System.out:Ljava/io/PrintStream;
        53: iload         7
        55: invokevirtual #65                 // Method java/io/PrintStream.println:(I)V
        58: getstatic     #59                 // Field java/lang/System.out:Ljava/io/PrintStream;
        61: ldc           #67                 // String 1..........
        63: invokevirtual #70                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              14      33     0  this   Lcode/sxu/asm/example/Callee;
              14      33     1     t   I
              14      33     2     p   I
              33      14     7   tmp   I
               0      67     0  this   Lcode/sxu/asm/example/Caller;
               0      67     1     a   I
               0      67     2     b   I
              50      17     7     r   I
      LineNumberTable:
        line 16: 0
        line 13: 14
        line 14: 33
        line 15: 42
        line 18: 50
        line 19: 58
        line 20: 66
}

Line 9-11: store stacked parameters. Line 14-44: inlined bytecodes of Callee, and its last ireturn is replaced by GOTO.

Two possible solutions for the verification failure in Java7:

  • Add -XX:-UseSplitVerifier for VM argument. The option works but the it is deprecated in Java8.

  • Add the stackmap table before line 44 (The one before GOTO instruction), that lists the types at positions which are targets of jumps (from Stackmap frame description).

For me, option 2 is preferable, but i have problem in construction of the frame. My code is:

    if (opcode == Opcodes.RETURN || opcode == Opcodes.IRETURN) {
        FrameNode stackMap = new FrameNode(Opcodes.NEW, -1, null, -1, null);
        stackMap.accept(mv);    //Visit The framenode before GOTO
        super.visitJumpInsn(Opcodes.GOTO, end);
    } else {
        super.visitInsn(opcode);
    }

I think both Opcodes.NEW/SMAE should work here. But it is impossible to calculate the remaining four arguments because the visitor has not visited the target codes and it does not know the nStack, nlocals..

So can anyone give suggestion in constructing the FrameNode here or example to handle this case? Thanks.

The description for FramNodeFrameNode ASM Document:

public FrameNode(int type, int nLocal, Object[] local, int nStack, Object[] stack)

Constructs a new FrameNode.

Parameters:

type - the type of this frame. Must be Opcodes.F_NEW for expanded frames, or Opcodes.F_FULL, Opcodes.F_APPEND, Opcodes.F_CHOP, Opcodes.F_SAME or Opcodes.F_APPEND, Opcodes.F_SAME1 for compressed frames.
nLocal - number of local variables of this stack map frame.
local - the types of the local variables of this stack map frame. Elements of this list can be Integer, String or LabelNode objects (for primitive, reference and uninitialized types respectively - see MethodVisitor).
nStack - number of operand stack elements of this stack map frame.
stack - the types of the operand stack elements of this stack map frame. Elements of this list can be Integer, String or LabelNode objects (for primitive, reference and uninitialized types respectively - see MethodVisitor). 

Solution

  • Provided that your bytecode is correct, you can let asm create the frame nodes for you.

    I usually do it like this:

    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
    {
        @Override
        protected String getCommonSuperClass(final String type1, final String type2)
        {
            //  the default asm merge uses Class.forName(), this prevents that.
            return "java/lang/Object";
        }
    };
    
    cn.accept(cw);
    

    Explaining: initialize your class writer with COMPUTE_FRAMES, and to avoid class loading issues overwrite getCommonSuperClass.

    Assuming that whatever route you are using to generate your bytecode (visitors, ClassNode, etc.) eventually you're using a ClassWriter to generate the class bytes.

    If you really want to do it manually, try this first and then use the asmifier to see how to write the frame code using asm.