Daneel S. Yaitskov Daneel S. Yaitskov - 3 months ago 36
Java Question

How define class after byte code transofrmation with ASM (class file version 0.0)

I cannot load a class after byte code modification with ASM library.

Here it is identity transformer and I expect to get modified array with the same size as bytes one, but it 2 times shorter! (439 vs 278)

String path = SimpleClass.class.getName().replace(".", "/") + ".class";
ClassLoader classLoader = SimpleClass.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream(path);

byte[] bytes = IOUtils.toByteArray(is);

ClassReader reader = new ClassReader(bytes);
ClassWriter writer = new ClassWriter(reader, 0);
byte[] modified = writer.toByteArray();


There is nothing wonderful that it fails to loads.
I suspect that header is truncated, but first bytes are the same in
both arrays.

-54, -2, -70

static class ByteClassLoader extends ClassLoader {
public Class define(String name, byte[] body) {
return defineClass(name, body, 0, body.length);
}
}

ByteClassLoader myLoader = new ByteClassLoader();
Class myClass = myLoader.define("Ooo", modified);


Fails with error:

java.lang.UnsupportedClassVersionError: Ooo has been compiled by a more recent version of the Java Runtime (class file version 0.0), this version of the Java Runtime only recognizes class file versions up to 52.0

at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at PrintTest$ByteClassLoader.define(PrintTest.java:24)
at PrintTest.x(PrintTest.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:253)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)

Answer

Those bytes correspond to 0xcafeba which matches the Java class file magic number, 0xcafebabe.

The problem here is that new ClassWriter(reader, 0) does not do what you presumably believe it does; see the API documentation:

classReader - the ClassReader used to read the original class. It will be used to copy the entire constant pool from the original class and also to copy other fragments of original bytecode where applicable.

We still need to actually have the writer visit the reader via ClassReader.accept, as follows:

reader.accept(writer, 0);

As a side note, you don't need IOUtils.toByteArary as ClassReader has a constructor taking InputStream.