反编译for-each循环

时间:2019-04-13 16:01:57

标签: java bytecode

反编译以下for-each循环的.class文件会产生有趣的结果。

源-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);
        }
    }
}

结果-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);
        }

    }
}

该文件已使用IntelliJ IDEA反编译。

  • 为什么将true分配给未使用的int
  • 为什么要重新声明var3变量?

这是代表反编译器的错误吗?

1 个答案:

答案 0 :(得分:3)

在字节码级别上,没有正式的局部变量声明,至少不是从源代码中知道的方式。方法声明了同时存在的最大局部变量或为它们保留的“插槽”的最大数量。当为局部变量分配了一个实际值(通过“ slot”索引)后,该局部变量就生效了,并且至少存在于该值的最后一次读取中。

通过这些操作,无法识别变量的作用域何时结束,或者具有相异作用域的两个变量是否共享一个插槽(与对同一变量的多次分配相比)。好吧,如果它们具有完全不兼容的类型,则它们的分配会给出提示。

为了帮助调试,有一个可选的code属性,它提供有关已声明的局部变量及其范围的提示,但这不是必须完整的操作,也不会影响JVM执行字节码的方式。但是在这里,似乎该属性存在并且已被反编译器使用。

当我用javac -g编译示例代码时,我得到了

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

已将声明的变量args(方法参数)namesvar3name分配给变量索引0,{{1 }},12的顺序。

有些合成变量没有声明,

  • 在索引6上保存对循环进行迭代的数组的引用
  • 在索引3处以保留数组长度
  • 位于索引4处以保存5索引变量,该变量将在循环中递增

反编译器似乎有一个简单的策略来处理int中未包含的变量。它生成一个由前缀LocalVariableTable和堆栈帧内的索引组成的名称。因此,它为上述合成变量生成了名称"var"var3var4,并且不在乎这些生成的名称与显式声明的名称之间是否存在名称冲突,即var5

现在,不清楚为什么反编译器会为var3变量生成true的赋值,但是它有助于知道Java字节码中没有专门的int处理指令,但是boolean值的处理方式与boolean值相同。它需要适当的元信息,例如变量声明,以了解何时应将值解释为int值。也许,上述名称冲突导致反编译器随后混淆了变量类型,最终认为值类型不是boolean,然后退回去将其视为int。但这只是一个猜测;也可能有一个完全不相关的错误。