bytes for the source class ClassReader cr = new ClassReader(is); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new
MyClassAdapter(new CheckClassAdapter(cw)); cr.accept(cv, 0); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw); assertTrue(sw.toString(), sw.toString().length()==0); Above code runs transformed bytecode trough the
CheckClassAdapter
. It won't be exactly the same verification as JVM does, but it run data flow analysis for the code of each method and checks that expectations are met for each method instruction.
If method bytecode has errors, assertion text will show the erroneous instruction number and dump of the failed method with information about locals and stack slot for each instruction. For example (format is - insnNumber locals : stack):
org.deuce.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found . at org.deuce.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289) at org.deuce.objectweb.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135) ... remove()V 00000 LinkedBlockingQueue$Itr . . . . . . . . : ICONST_0 00001 LinkedBlockingQueue$Itr . . . . . . . . : I ISTORE 2 00001 LinkedBlockingQueue$Itr . I . . . . . . : ... 00071 LinkedBlockingQueue$Itr . I . . . . . . : ILOAD 1 00072 ? INVOKESPECIAL java/lang/Integer. (I)V ...
In the above output you can see that variable 1 loaded by
ILOAD 1
instruction at position
00071
is not initialized. You can also see that at the beginning of the method (code inserted by the transformation) variable 2 is initialized.
Note that when used like that, CheckClassAdapter.verify()
can trigger additional class loading, because it is using SimpleVerifier
.
@author Eric Bruneton