Java JVM字节码,如何找到局部变量的类型?

Java JVM字节码,如何找到局部变量的类型?,java,bytecode,Java,Bytecode,我正在制作Jetbrains的FernFlower,并对其进行了一些小的改进 FernFlower真正让我恼火的一件事是,它根据bpush/spush等中的值来确定局部变量的类型,而Jode和Procyon不知何故找到了一种方法来找到局部变量的原始值 这是原始的源代码 public static void main(String[] args) throws Exception { int hello = 100; char a2 = 100; short y1o = 1

我正在制作Jetbrains的FernFlower,并对其进行了一些小的改进

FernFlower真正让我恼火的一件事是,它根据bpush/spush等中的值来确定局部变量的类型,而Jode和Procyon不知何故找到了一种方法来找到局部变量的原始值

这是原始的源代码

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);
}
使用FernFlower反编译时,它会输出以下内容:

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);
}
但当使用Jode/Procyon反编译时,它会输出原始的局部变量类型:

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

我想知道这是怎么可能的,因为我认为编译时没有存储局部变量类型信息?如何向FernFlower添加相同的功能?

.class
出于调试目的,文件可以选择包含“LocalVariableTable”属性。如果调用命令
javap-l.class
,您可以看到数据是否存在。

因此,在查看和调试之后,我发现由于某种原因,FernFlower决定完全忽略LocalVariableTable中的一些数据

下面是用于解码LocalVariableTable的ferns原始代码:

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();
    }
}
public void initContent(ConstantPool池)引发IOException{
DataInputFullStream数据=流();
int len=data.readUnsignedShort();
如果(len>0){
mapVarNames=新HashMap(len);
对于(int i=0;i
如果需要类型信息,则需要添加以下内容:

@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();
    }
}
@覆盖
公共void initContent(ConstantPool池)引发IOException{
DataInputFullStream数据=流();
int len=data.readUnsignedShort();
如果(len>0){
mapVarNames=新HashMap(len);
mapVarTypes=新HashMap(len);
对于(int i=0;i
它现在输出与Jode相同的代码,并具有适当的变量类型:)


我想知道FernFlower为什么选择忽略此信息。

是否可能是反编译器在决定使用哪种本地类型(例如,
byte
int
)时随意决定的?在许多情况下,您应该能够从使用这些插槽的指令中推断出来。亲自查看一下字节码,特别是调用的
String.valueOf()
的各种重载。在本例中,似乎还有一些调试信息,否则名称也不可用。如果没有调试信息,编译代码中就不会有LocalVariableTable,因此我看不出如何推断原始声明的类型。我自己能解决这个问题。请看下面我的答案。谢谢你的帮助@我告诉过你,这太可悲了。这段代码完全忽略了变量的作用域?除了最简单的代码示例之外,这不太可能对任何东西都有效……您能再扩展一下您的响应吗?我不熟悉字节码。
start
end
描述了字节码中变量有效的范围。在此范围之外,相同的
varIndex
编号可能用于不同的变量,该变量可能具有不同的名称和类型。由于使用
varIndex
作为映射键,因此将只记录每个索引最后遇到的变量。想象一下类似
的代码(int ix=0;ixAh您完全正确,多亏了这一点。我在大型程序中遇到了一个问题,因为varIndex被重用,所以它不知道变量的类型。我假设我需要为每个LocalVariable生成一个UID,以确保它不会与其他变量重叠?这里是Fernflower的原始作者。LocalVari的内容ableTable没有得到VM的验证,因此模糊处理程序可以自由地在那里存储他们喜欢的任何乱七八糟的信息。依赖这些信息是危险的。唯一真正可靠的来源是代码本身,这就是为什么Fernflower试图直接从代码中推导出变量类型。当然,总有改进的余地:)