I'm trying to write a unit tests for a workaround to an issue about missing stackmap frames, but for that purpose I will need to generate a class that will fail to validate on Java 8 if it's missing stackmap frames.
Below you can see my test case (dependencies: ASM, Guava, JUnit). It removes the stackmap frames from the GuineaPig class in hopes of causing its bytecode to fail to validate. The part that I'm having problems with is filling in the TODO in GuineaPig with minimal code that requires stackmap frames, so that the test would pass.
import com.google.common.io.*;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.objectweb.asm.*;
import java.io.*;
import static org.objectweb.asm.Opcodes.ASM5;
public class Java6MissingStackMapFrameFixerTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
public static class GuineaPig {
public GuineaPig() {
// TODO: make me require stackmap frames
}
}
@Test
public void example_class_cannot_be_loaded_because_of_missing_stackmap_frame() throws Exception {
byte[] originalBytecode = getBytecode(GuineaPig.class);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// remove the stackmap frames in order to cause a VerifyError
// super.visitFrame(type, nLocal, local, nStack, stack);
}
};
}
};
new ClassReader(originalBytecode).accept(cv, 0);
byte[] transformedBytecode = cw.toByteArray();
// Files.asByteSink(new File("test.class")).write(transformedBytecode);
thrown.expect(VerifyError.class);
thrown.expectMessage("Expecting a stackmap frame");
Class<?> clazz = new TestingClassLoader().defineClass(transformedBytecode);
clazz.newInstance();
}
private static byte[] getBytecode(Class<?> clazz) throws IOException {
String classFile = clazz.getName().replace(".", "/") + ".class";
try (InputStream b = clazz.getClassLoader().getResourceAsStream(classFile)) {
return ByteStreams.toByteArray(b);
}
}
private static class TestingClassLoader extends ClassLoader {
public Class<?> defineClass(byte[] bytecode) {
ClassReader cr = new ClassReader(bytecode);
String className = cr.getClassName().replace("/", ".");
return this.defineClass(className, bytecode, 0, bytecode.length);
}
}
}