TypeNotPresentException with COMPUTE_FRAMES when generating class
When using COMPUTE_FRAMES
I stumbled upon a case where frame computation will cause a TypeNotPresentException
due to it trying to load the class that is currently being generated. This is on version 9.0 but happens in earlier versions, too (originally found in 7.2).
Here's the stacktrace:
Exception in thread "main" java.lang.TypeNotPresentException: Type Test$Modified not present
at org.objectweb.asm.ClassWriter.getCommonSuperClass(ClassWriter.java:1025)
at org.objectweb.asm.SymbolTable.addMergedType(SymbolTable.java:1202)
at org.objectweb.asm.Frame.merge(Frame.java:1299)
at org.objectweb.asm.Frame.merge(Frame.java:1244)
at org.objectweb.asm.MethodWriter.computeAllFrames(MethodWriter.java:1610)
at org.objectweb.asm.MethodWriter.visitMaxs(MethodWriter.java:1546)
at org.objectweb.asm.MethodVisitor.visitMaxs(MethodVisitor.java:773)
at org.objectweb.asm.ClassReader.readCode(ClassReader.java:2629)
at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1481)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:711)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:394)
Here is a repro case:
interface TestInterface { }
class Test implements TestInterface {
TestInterface a;
Test(TestInterface x) {
a = x != null ? x : this; // the cause
}
}
public static void main(String[] args) throws Throwable {
ClassReader reader = new ClassReader(Test.class.getName());
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
reader.accept(new ClassRemapper(writer, new Remapper() {
public String map(final String internalName) {
return internalName.equals(Type.getInternalName(Test.class))
? internalName + "$Modified"
: super.map(internalName);
}
}), 0);
}
The part that causes the exception is the assignment in the constructor. For the frame computation, an attempt is made to find the superclass of TestInterface
and Test$Modified
, the latter being the class being generated, so Class.forName
fails.
For now my workaround is to do a = x != null ? x : (TestInterface)(Object)this;
instead.
For the record, my actual use-case is that I have a "template" class of which I create a copy with some generated code injected into an existing method. Imagine TestInterface
having a method, Test
implementing it and in that method implementation I'd inject some generated code.
Anyway. I'm not sure this is expected behavior, so I thought I'd report it. Don't know if this can be robustly handled, really. If the workaround isn't workaround and how that's supposed to be done, great. If there are other approaches that ideally work on older versions, too, please let me know. Thanks!