看起来ASM
ClassWriter
并没有保留常量池中条目的顺序。例如,考虑以下代码片段:
@Test
void hashShouldBeSame() throws IOException, NoSuchAlgorithmException {
Path A = CLASSFILE.resolve("A.class");
String originalHash = HashComputer.computeHash(Files.readAllBytes(A), "SHA-256");
ClassReader reader = new ClassReader(Files.readAllBytes(A));
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
reader.accept(writer, 0);
String hashFromRewrittenModel = HashComputer.computeHash(writer.toByteArray(), "SHA-256");
assertThat(originalHash).isEqualTo(hashFromRewrittenModel);
}
其中
A.class
是从以下源文件编译的类文件:
// A.java
public class A {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
上面代码片段中的 SHA 256 哈希应该是相同的,因为我没有对字节码模型进行任何转换。如何让 ASM
ClassWriter
保留常量池表的顺序?
HashComputer
是我项目中的一个函数。它计算字节数组的 SHA256 哈希值。
这是设计使然。当调用访问…方法时,常量池项目会被即时解码,并且特殊访问者
ClassWriter
(以及由它返回的访问者,例如来自visitMethod
)将构建一个新的常量池他们收到的作为参数的值。
这不仅意味着复制类时顺序可能会改变,而且未使用的条目可能会消失并且冗余条目会被单个条目替换。
但是有一个特殊的构造函数,用于只需要很少的更改即可检测类的情况:
ClassWriter(ClassReader,int)
关于常量池复制的强调是我添加的对象并启用“主要添加”字节码转换的优化。这些优化如下:ClassWriter
- 原始类中的常量池和引导方法按原样复制到新类中,这节省了时间。如有必要,新的常量池条目和新的引导方法将在末尾添加,但未使用的常量池条目或引导方法不会被删除。
- 未转换的方法将按原样复制到新类中,直接从原始类字节码复制(即不为所有方法指令发出访问事件),这节省了“很多”时间。未转换的方法是通过以下事实来检测的:
ClassReader
接收来自MethodVisitor
(而不是来自任何其他ClassWriter
实例)的ClassVisitor
对象。
因此,要获得与原始文件相同的常量池,您可以使用
new ClassWriter(reader, 0)
,将读取器传递给构造函数。
但这并不能保证结果数组与原始数组相同。例如,类属性的顺序可能会改变。没有 API 来强制执行原始属性顺序。 ASM 库的目的不是一点一点地重现一个类文件。