javajava-bytecode-asm

try to build a decompiler of java bytecode, and don't know in what case the "stack map frame" would not happen to be the "FRAMSAME"


the java code snippet

    int x4(int a) {
    if(a>7){
        System.out.println("a>7");
    }


    if(a==0){
        System.out.println("a==0");
    }else if(a>77){
        System.out.println(" a>77");
    }else if(a>44){
        System.out.println(" a>44");
    }else{
        System.out.println("otherwise");
    }

    return 44;
}

the bytecode outline:

// access flags 0x0
x4(I)I
// parameter  a
L0
ILOAD 1
BIPUSH 7
IF_ICMPLE L1  ---- operand stack is empty here , end of a statement
L2 ---- new statement mark
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a>7"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
IFNE L3
L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a==0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
GOTO L6
L3
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 77
IF_ICMPLE L7
L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>77"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
GOTO L6
L7
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 44
IF_ICMPLE L10
L11
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>44"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L12
GOTO L6
L10
FRAME SAME ---- branching place start with empty operand stack
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "otherwise"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
FRAME SAME ---- branching place start with empty operand stack
BIPUSH 44
IRETURN
L13
// access flags 0x0
x4(I)I
// parameter  a
L0
ILOAD 1
BIPUSH 7
IF_ICMPLE L1
L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a>7"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
IFNE L3
L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a==0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
GOTO L6
L3
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 77
IF_ICMPLE L7
L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>77"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
GOTO L6
L7
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 44
IF_ICMPLE L10
L11
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>44"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L12
GOTO L6
L10
FRAME SAME ---- branching place start with empty operand stack
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "otherwise"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
FRAME SAME ---- branching place start with empty operand stack
BIPUSH 44
IRETURN
L13

it seems that whenever the operand stack is emptied by "assignment" "operation+assignment" "follow control" "method invocation" , a new label would mark a new "statement line" .

the "follow control" block is a collection of "statement" , and when a block(marked by "jump instructions") is end, it would also a "statement" ending hence the operand stack would be empty for the block's following code or alternating branches...

so it seems to me that no case the "labeled by ' jump instruction ' code" would not be the "FRAME SAME" for the compiled java source code

but i know it will not be the case . otherwise the STACK MAP FRAME would not be designed into byte-code.

please experts ,thanks very much , give me example of non-FRAME SAME branching , and explain it intuitively .

thanks very much, please help. help please.


Solution

  • If you only want to decompile, i.e. print the instruction sequence, you may simply ignore stack frames. They only provide information about the types of items on the operand stack and the local variables. If you don’t print these types, you don’t need to parse them.

    If you only use branch statements, there are indeed no operand stack entries on both ends of the branch, but the reason why you don’t encounter local variable changes simply is that you don’t have such changes in your example code.

    Consider:

    for(int i=0; i<10; i++)
        System.out.println(i);
    

    Here, the variable i has been added to the stack frame at the loop’s branch, so you are likely to encounter an F_APPEND frame there (compilers are encouraged, but not required to use the most compact form). Likewise, when adding another subsequent branch like in

    for(int i=0; i<10; i++)
      System.out.println(i);
    if(a==0)
      System.out.println();
    

    you are likely to encounter a F_CHOP frame because at the subsequent branch, i is not in scope anymore.

    Note that there are also intra-statement branches possible, allowing to have different operand stack entries, e.g.

    System.out.println(a==0? "zero": "non-zero");
    

    bears two branches. For the first, a reference to a PrintStream instance is already on the stack, for the second, references to a PrintStream and a String instance are on the stack.

    Also, exception handlers form implicit branch targets having a single operand stack entry, the caught exception. This situation can be encoded with the conveniently defined F_SAME1 frame type.


    If you plan to use the information contained in stack frames and are using the ASM library, you don’t need to parse all the different frame types. Just pass the flag ClassReader.EXPAND_FRAMES to the ClassReader upon construction and all you will ever encounter, are F_NEW frames containing the complete stack state, eliminating the need to remember previous frames.