Decompiling for-each loop

csirmazbendeguz :

Decompiling the .class file of the following for-each loop produces interesting results.

Source - Main.java:

public class Main {
    public static void main(String[] args) {
        String[] names = new String[3];
        int var3 = 3;

        for (String name : names) {
            System.out.println(name);
        }
    }
}

Result - Main.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        String[] names = new String[3];
        int var3 = true;
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            System.out.println(name);
        }

    }
}

The file was decompiled with IntelliJ IDEA.

  • Why was true assigned to the unused int?
  • Why was the var3 variable redeclared?

Is this a mistake on behalf of the decompiler?

Holger :

On the bytecode level, there are no formal declarations of local variables, at least not the way known from source code. A method has a declaration of the maximum number of local variables existing at the same time or “slots” to reserve for them. A local variable comes into life when an actual value is assigned to it (by “slot” index) and exists at least to the last read of that value.

With these operations, it is impossible to recognize when a variable’s scope ends or whether two variables with disjunct scopes share a slot (compared to multiple assignments to the same variable). Well, if they have completely incompatible types, their assignments give a hint.

To help debugging, there is an optional code attribute providing hints about declared local variables and their scope, but this is not required to be complete and won’t affect the way the JVM will execute the bytecode. But here, it seems the attribute was present and has been used by the decompiler.

When I compile your example code with javac -g, I get

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
  stack=2, locals=7, args_size=1
     0: iconst_3
     1: anewarray     #2        // class java/lang/String
     4: astore_1
     5: iconst_3
     6: istore_2
     7: aload_1
     8: astore_3
     9: aload_3
    10: arraylength
    11: istore        4
    13: iconst_0
    14: istore        5
    16: iload         5
    18: iload         4
    20: if_icmpge     43
    23: aload_3
    24: iload         5
    26: aaload
    27: astore        6
    29: getstatic     #3        // Field java/lang/System.out:Ljava/io/PrintStream;
    32: aload         6
    34: invokevirtual #4        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    37: iinc          5, 1
    40: goto          16
    43: return
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
       29       8     6  name   Ljava/lang/String;
        0      44     0  args   [Ljava/lang/String;
        5      39     1 names   [Ljava/lang/String;
        7      37     2  var3   I

The declared variables args (the method parameter), names, var3, and name were assigned to the variable indices 0, 1, 2, and 6, in that order.

There are synthetic variables without a declaration,

  • at index 3 to hold a reference to the array the loop is iterating over
  • at index 4 to hold the array length
  • at index 5 to hold the int index variable that will get incremented in the loop

It seems, the decompiler has a simple strategy to deal with variables not contained in the LocalVariableTable. It generates a name consisting of the prefix "var" and the index within the stack frame. So it generated the names var3, var4 and var5 for the synthetic variables described above and didn’t care that there was a name clash between these generated names and the explicitly declared names, i.e. var3.

Now, it’s not clear why the decompiler generates an assignment of true for an int variable, but it helps to know that there are no dedicated boolean processing instructions in Java bytecode, but rather boolean values are processed the same way as int values. It requires appropriate meta information, like variable declarations, to understand when values should get interpreted as boolean values. Perhaps, the name clash described above led the decompiler to confuse the variable types afterwards, to eventually consider the value type not to be an int and fall back to treat it as boolean then. But that’s just a guess; there could be an entirely unrelated bug too.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=79492&siteId=1