Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JVM Bytecode, how can I find the type of local variables?

Tags:

java

bytecode

I'm working on a fork of FernFlower from Jetbrains and I've been adding minor improvements to it.

One thing that really annoys me about FernFlower is that it bases the type of the local variable based on its value in bpush/spush etc. While Jode and Procyon somehow find a way to find the original value of a local variable.

Here is the original source code.

public static void main(String[] args) throws Exception {
    int hello = 100;
    char a2 = 100;
    short y1o = 100;
    int hei = 100;

    System.out.println(a2+" "+y1o+", "+hei+", "+hello);
}

When decompiled with FernFlower, it outputs this:

public static void main(String[] args) throws Exception {
    byte hello = 100;
    char a2 = 100;
    byte y1o = 100;
    byte hei = 100;
    System.out.println(a2 + " " + y1o + ", " + hei + ", " + hello);
}

But when decompiled with Jode/Procyon it outputs the original local variable types:

  public static void main(String[] args)
    throws Exception
  {
    int hello = 100;
    char a2 = 'd';
    short y1o = 100;
    byte hei = 100;

    System.out.println(a2 + " " + y1o + ", " + hei + ", " + hello);
  }

I was wondering how is this possible because I thought no local variable type information is stored at compile time? How can I add the same functionality to FernFlower?

like image 447
Jonathan Beaudoin Avatar asked Jan 19 '16 00:01

Jonathan Beaudoin


People also ask

Where does the JVM store the values of local variables and object types?

The objects are always stored in the heap memory, as that's the way heap memory is defined in the first place: The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.

How does JVM interpret bytecode?

Bytecode is a special machine language native to the JVM. The JVM interprets and executes this code at runtime. It is the JVM that is built and customized for each platform that supports Java, rather than our programs or libraries. Modern JVMs also have a JIT compiler.

What is the format of bytecode in Java?

Bytecode is the compiled format for Java programs. Once a Java program has been converted to bytecode, it can be transferred across a network and executed by Java Virtual Machine (JVM). Bytecode files generally have a . class extension.


2 Answers

.class files optionally contain a 'LocalVariableTable' attribute for debugging purposes. If you invoke the command javap -l <Class>.class you can see the data if it is present.

like image 183
Kiskae Avatar answered Oct 04 '22 20:10

Kiskae


So after looking around and debugging I found that for some reason FernFlower decides to completely ignore some of the data in the LocalVariableTable.

Here is ferns original code for decoding the LocalVariableTable:

public void initContent(ConstantPool pool) throws IOException {
    DataInputFullStream data = stream();

    int len = data.readUnsignedShort();
    if (len > 0) {
        mapVarNames = new HashMap<Integer, String>(len);
        for (int i = 0; i < len; i++) {
            data.discard(4);
            int nameIndex = data.readUnsignedShort();
            data.discard(2);
            int varIndex = data.readUnsignedShort();
            mapVarNames.put(varIndex, pool.getPrimitiveConstant(nameIndex).getString());
        }
    } else {
        mapVarNames = Collections.emptyMap();
    }
}

If you want type information you need to add the following:

@Override
public void initContent(ConstantPool pool) throws IOException {
    DataInputFullStream data = stream();

    int len = data.readUnsignedShort();
    if (len > 0) {
        mapVarNames = new HashMap<Integer, String>(len);
        mapVarTypes = new HashMap<Integer, String>(len);
        for (int i = 0; i < len; i++) {
            int start  = data.readUnsignedShort();
            int end    = start + data.readUnsignedShort();
            int nameIndex = data.readUnsignedShort();
            int typeIndex = data.readUnsignedShort();
            int varIndex = data.readUnsignedShort();
            mapVarNames.put(varIndex, pool.getPrimitiveConstant(nameIndex).getString());
            mapVarTypes.put(varIndex, pool.getPrimitiveConstant(typeIndex).getString());
        }
    } else {
        mapVarNames = Collections.emptyMap();
        mapVarTypes = Collections.emptyMap();
    }
}

It now outputs the same code as Jode with proper variable types :)

I wonder why FernFlower chose to ignore this information.

like image 39
Jonathan Beaudoin Avatar answered Oct 04 '22 19:10

Jonathan Beaudoin