是否可以将Java字节码反编译回原始泛型类型参数

时间:2017-08-31 21:26:19

标签: java generics bytecode decompiler

我知道Java编译器用类型参数替换泛型类型中的所有类型参数,如果类型参数在类型擦除过程中无界限,则替换Object。生成的机器字节码将反映替换的边界或Object

有没有办法获取生成的机器字节码并将其反编译回包含泛型类型中原始类型参数的Java文件?是否存在可以实现此目的的反编译器?或者由于编译过程的性质,这个过程根本不可逆转?

3 个答案:

答案 0 :(得分:4)

您是正确的,在字节码级别,当您定义泛型类型并与其进行交互时,会丢失很多信息。类型擦除对于保持兼容性很好:如果你在编译时主要强制执行类型安全,那么你不需要在运行时做很多事情,所以你可以将泛型类型减少到它们的原始类型。等同物。

这就是关键:编译时验证。如果您想要泛型的灵活性和类型安全性,您的编译器必须了解与您交互的泛型类型。在许多情况下,您不会获得这些类的源代码,因此必须从某处获取信息。它确实:元数据。嵌入在.class文件旁边的字节码是丰富的信息:编译器需要知道的所有内容都安全地使用通用库类型。那么什么样的泛型信息得以保存?

输入变量和约束

编译器为了使用泛型类型而需要知道的最基本的事情是类型变量列表。对于任何泛型类型或泛型方法,都会保留类型变量的名称和位置。此外,还包括任何约束(上限或下限)。

通用超类型签名

有时您编写一个扩展泛型类或实现泛型接口的类。如果您编写扩展StringList的{​​{1}},则会继承许多功能。如果有人想要按照预期使用您的ArrayList<String> 并且没有源代码,那么编译器就不足以知道您扩展了StringList;它必须知道你延长了ArrayList。这适用于层次结构的传递:它必须知道ArrayList<String>扩展ArrayList<>,依此类推。所以这些信息得以保留。您的类文件a将包含任何通用超类型(类或接口)的完整通用签名。

会员签名

如果编译器不知道完整的通用类型的字段,方法参数和返回类型,则无法验证您是否正确使用了泛型类型。所以,你猜对了:信息包括在内。如果类成员的任何部分包含泛型类型,通配符或类型变量,该成员将获取其签名信息保存在元数据中。

本地变量

它可以用于调试,但关于它。有一些元数据表可用于记录变量的名称和类型,以及它们存在的字节码范围。根据编译器的不同,默认情况下可能会编写也可能不编写它们。您可以通过传递AbstractList<>强制javac发出它们,但我相信默认情况下会忽略它们

致电网站

反编译器的一个最大问题,主要是影响方法体内的泛型推理,是调用泛型方法的调用站点保留没有关于类型参数的信息。这给像Java 8 Streams这样的API带来了巨大的麻烦,在这些API中,泛型运算符被链接在一起,每个都接受匿名类型的lambda(它们的参数类型可能是逆变的,返回类型中可能是协变的)。这是一种类型推断的噩梦,但对于碰巧泛型进行交互的任何代码都是一个问题。这种代码变得非常难以反编译,因为它在泛型类型中存在

这会如何影响反编译

像Procyon和CFR这样的现代Java反编译器应该能够很好地重建泛型类型。如果局部变量元数据可用,则结果应该非常接近原始代码。如果没有,他们将不得不尝试基于数据流分析推断方法体中的泛型类型参数。本质上,反编译器必须查看哪些数据流入和流出泛型实例,并使用它所知道的数据类型来猜测类型参数。有时候效果很好;其他时候,不是那么多(参见之前关于Java 8 Streams的评论)。

在API级别,虽然是类型和成员签名,但结果应该是定点的。

<强>注意事项

严格地说,这里描述的所有元数据都是可选:它只在编译时(或反编译时)需要。如果有人通过混淆器,优化器或其他实用程序运行其编译的类,则所有这些信息都可能被删除。它不会在运行时产生影响。

<强> tldr;结论

是的,当然可以使用其类型参数完整地反编译泛型类型和方法。假设存在所需的元数据,那么获得正确的类型和成员签名就是“简单”。部分。正确地推断泛型实例和方法调用的类型参数是一个棘手的问题,但这对于发生与泛型交互的任何代码来说都是一个问题。

如前所述,Procyon和CFR都应该在恢复通用类型和方法方面做得相当不错。

答案 1 :(得分:1)

这主要取决于代码是否已被混淆。虽然泛型使用类型擦除确实是正确的,但编译器通常包含源级信息,例如泛型类型作为类文件中的元数据,原因有多种 - 反射,调试,针对封闭源库的编译等。

因此,对于表现良好的类文件,应该可以获取信息。是否有任何现成的工具,我不知道。许多反编译器都试图恢复泛型类型,但我不知道它们有多可靠。

如果代码已被混淆,那么所有元数据都将被删除,因此无法恢复原始泛型类型。

答案 2 :(得分:-2)

是的,这被称为反编译过程以转换机器代码,或者我们可以将其称为字节代码到其原始源代码,但在某种程度上! 有一些反编译器确实存在!
你需要的是获得反编译器的一些帮助,并尽可能少地努力将这个字节代码转换为它的泛型类型。 但是不可能以高精度比进行这样的逆向工程过程,因为现代编译器的设计方式是它们经过几个步骤才能将源代码转换为机器代码,这样你就可以获得反转之后是一个只是非人类可读形式的汇编代码,但在反编译器的帮助下,可以在一定程度上轻松完成同样的工作。 &#34; java反编译器项目&#34;或JD项目是我所谈论的事情 http://jd.benow.ca 希望它能让你的概念变得清晰!