Search code examples
javajava-bytecode-asm

ASM does not preserve order in the constant pool table


It seems that ASM ClassWriter does not preserve the order of entries in the constant pool. For example, consider the following code snippet:

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

where A.class is a classfile compiled from the following source file:

// A.java
public class A {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

The SHA 256 hash in the above snippet should be the same because I have not done any transformations to the bytecode model. How can I make ASM ClassWriter preserve order of constant pool table?

HashComputer is a function in my project. It computes SHA256 hash of the byte array.


Solution

  • This is by design. The constant pool items are decoded on-the-fly when the visit… methods are invoked and the special visitor ClassWriter (and the visitors returned by it, e.g. from visitMethod) will build a new constant pool with the values they received as parameters.

    This does not only imply that the order may change when replicating a class, also unused entries may disappear and redundant entries get replaced by a single entry.

    But there is a special constructor for the case of instrumenting a class with only little changes:

    ClassWriter(ClassReader,int)

    Constructs a new ClassWriter object and enables optimizations for "mostly add" bytecode transformations. These optimizations are the following:

    • The constant pool and bootstrap methods from the original class are copied as is in the new class, which saves time. New constant pool entries and new bootstrap methods will be added at the end if necessary, but unused constant pool entries or bootstrap methods won't be removed.
    • Methods that are not transformed are copied as is in the new class, directly from the original class bytecode (i.e. without emitting visit events for all the method instructions), which saves a lot of time. Untransformed methods are detected by the fact that the ClassReader receives MethodVisitor objects that come from a ClassWriter (and not from any other ClassVisitor instance).

    emphasis regarding constant pool copying has been added by me

    So, to get the same constant pool as the original file you can use new ClassWriter(reader, 0), passing the reader to the constructor.

    This, however, does not guaranty that the resulting array is identical to the original array. For example, the order of the class attributes may change. There is no API to enforce the original attribute order. It’s not the purpose of the ASM library to reproduce a class file bit by bit.