Question

I have a method which I have altered in a Java 7 (major version 51) class. Using javap, I've looked at the bytecode and the Stack Frame Map. Everything looks fine:

public int addOne(int);
flags: ACC_PUBLIC
Code:
  stack=2, locals=2, args_size=2
     0: iload_1
     1: iconst_0
     2: invokestatic  #50                 // Method isSomething:(I)Z
     5: ifeq          12
     8: iconst_0
     9: goto          13
    12: iconst_1
    13: iadd
    14: ireturn

StackMapTable: number_of_entries = 2
     frame_type = 255 /* full_frame */
    offset_delta = 12
    locals = [ class test/Target, int ]
    stack = [ int ]
     frame_type = 255 /* full_frame */
    offset_delta = 0
    locals = [ class test/Target, int ]
    stack = [ int, int ]

This verifier throws this exception:

java.lang.VerifyError: Expecting a stackmap frame at branch target 12
Exception Details:
  Location:
    test/Target.addOne(I)I @5: ifeq
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0000000: 1b03 b800 3299 0007 03a7 0004 0460 ac

What's driving me crazy is that I had the compiler generate the same code from Java source, and it looks like this:

  public int addOne(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: iconst_0
         2: invokestatic  #16                 // Method isSomething:(I)Z
         5: ifeq          12
         8: iconst_0
         9: goto          13
        12: iconst_1
        13: iadd
        14: ireturn

      StackMapTable: number_of_entries = 2
           frame_type = 76 /* same_locals_1_stack_item */
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class test/Target, int ]
          stack = [ int, int ]

Notice that the only difference in the stack frame map is that the synthetic map has all full frames -- but that shouldn't cause a difference. Does anyone know why the verfier might not like my synthetic map?

Was it helpful?

Solution

I am unable to reproduce this problem. Perhaps you are creating the stack frames in a way that javap will still read but isn't actually valid? Because my edited class has the same javap output but it verifies just fine. If you post the actual classfile I can see if I can find the problem, because I don't think there's anything further I can do with just the Javap output.

Source:

public class ArrayTest {
    public int addOne(int x){
        return x + (isSomething(0) ? 0 : 1);
    }

    public static boolean isSomething(int z) {return true;}
}

Javap output of method in original class

  public int addOne(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: iconst_0
         2: invokestatic  #2                  // Method isSomething:(I)Z
         5: ifeq          12
         8: iconst_0
         9: goto          13
        12: iconst_1
        13: iadd
        14: ireturn
      StackMapTable: number_of_entries = 2
           frame_type = 76 /* same_locals_1_stack_item */
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class ArrayTest, int ]
          stack = [ int, int ]

Javap output of method in edited class

  public int addOne(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: iconst_0
         2: invokestatic  #15                 // Method ArrayTest.isSomething:(
)Z
         5: ifeq          12
         8: iconst_0
         9: goto          13
        12: iconst_1
        13: iadd
        14: ireturn
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class ArrayTest2, int ]
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class ArrayTest2, int ]
          stack = [ int, int ]

As you can see, I have the same Javap output, but my class works just fine.

OTHER TIPS

The answer is that javassist sucks and I am deeply regretting using it.

The StackMapTable attribute is gotten from a call to CodeAttribute.getAttribute(String tag). Even though this is how you access it, there is no API to add it back, unless it is of type StackMapTable. The only API that accepts a vanilla AttributeInfo as a parameter is on the MethodInfo class.

In cases where the method did not need (or have) a stack frame already, you get a null. If you create an AttributeInfo structure for a new stack frame map, you shouldn't add the to the MethodInfo (where the addAttribute API is), but the CodeAttribute where it belongs.

This is what I was doing:

MethodInfo mi ...
AttributeInfo attr ...

mi.addAttribute(attr);

This is what I needed to do:

CodeAttribute ca ...
ca.getAttributes().add(attr);

(Of course, ca.getAttributes()returns an untyped List because we all miss 2004.)

I dug into the method that allows you to add a type StackFramMap to the CodeAttribute and figured out this work around for the lack of a generic API.

The result of using the top construct is that javap will make it appear that you have a proper StackMapTable. You do, but it's attached to the wrong object and you can't see that from the javap output.

I didn't use ASM for my project because I found its obsessive use of the Visitor Pattern to be annoying. I now admit that this was a bad decision. Since javassist hasn't had an update since 2012, I'm wondering if the project is dead. I certainly have a truckload of revisions I'd push. It is a mess.

EDIT Oh wow. Javassist internal code assumes that any StackMapTable attribute it its own internal StackMapTable type (because how else would you add the StackMapTable attribute). I guess I could create my own StackMapTable instance, except the SFM constructors are package protected for no apparent reason. It just gets worse and worse...

http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/html/javassist/bytecode/MethodInfo.html

Now you can call:

mi.rebuildStackMapIf6(pool, cf);

It will rebuild stack map, and don't use or need -XX:-UseSplitVerifier

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top