ASM 不保留常量池表中的顺序

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

看起来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 哈希值。

java java-bytecode-asm
1个回答
0
投票

这是设计使然。当调用访问…方法时,常量池项目会被即时解码,并且特殊访问者

ClassWriter
(以及由它返回的访问者,例如来自
visitMethod
)将构建一个新的常量池他们收到的作为参数的值。

这不仅意味着复制类时顺序可能会改变,而且未使用的条目可能会消失并且冗余条目会被单个条目替换。

但是有一个特殊的构造函数,用于只需要很少的更改即可检测类的情况:

ClassWriter(ClassReader,int)

构造一个新的

ClassWriter
对象并启用“主要添加”字节码转换的优化。这些优化如下:

  • 原始类中的常量池和引导方法按原样复制到新类中,这节省了时间。如有必要,新的常量池条目和新的引导方法将在末尾添加,但未使用的常量池条目或引导方法不会被删除
  • 未转换的方法将按原样复制到新类中,直接从原始类字节码复制(即不为所有方法指令发出访问事件),这节省了“很多”时间。未转换的方法是通过以下事实来检测的:ClassReader
    接收来自 
    MethodVisitor
    (而不是来自任何其他 
    ClassWriter
    实例)的 
    ClassVisitor
    对象。
关于常量池复制的强调是我添加的

因此,要获得与原始文件相同的常量池,您可以使用

new ClassWriter(reader, 0)

,将读取器传递给构造函数。

但这并不能保证结果数组与原始数组相同。例如,类属性的顺序可能会改变。没有 API 来强制执行原始属性顺序。 ASM 库的目的不是一点一点地重现一个类文件。

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