对每个循环进行反编译

问题描述 投票:4回答:1

反编译以下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变量?

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

java bytecode
1个回答
3
投票

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

通过这些操作,无法识别变量的范围何时结束,或者两个具有析取范围的变量是否共享一个槽(与同一变量的多个赋值相比)。好吧,如果他们有完全不兼容的类型,他们的任务给出了一个提示。

为了帮助调试,有一个可选的代码属性提供有关声明的局部变量及其范围的提示,但这不需要完整,也不会影响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按顺序分配给变量索引0126

有没有声明的合成变量,

  • 在索引3处保存对循环迭代的数组的引用
  • 在索引4保持数组长度
  • 在索引5处保存将在循环中递增的int索引变量

似乎,反编译器有一个简单的策略来处理LocalVariableTable中没有包含的变量。它生成一个名称,该名称由前缀"var"和堆栈帧中的索引组成。所以它为上面描述的合成变量生成了名称var3var4var5,并不关心这些生成的名称和明确声明的名称之间存在名称冲突,即var3

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

© www.soinside.com 2019 - 2024. All rights reserved.