Using Javassist 3.20.0.GA, I am trying to inject code in to an empty loop.
Everything I try, I keep running in to a java.lang.VerifyError error at the point I attempt to create a new instance of the modified class.
I've attempted to isolate the issue to a small program that fails to run successfully.
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.InstructionPrinter;
import javassist.bytecode.MethodInfo;
import javassist.compiler.Javac;
public class EmptyLoopTest {
private void testEmptyLoopModification() throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(EmptyLoopTest.class.getName() + "$EmptyLoopClass");
CtMethod m = cc.getDeclaredMethod("emptyLoopMethod");
printMethod("Before modifications", m);
MethodInfo methodInfo = m.getMethodInfo();
CodeAttribute ca = methodInfo.getCodeAttribute();
CodeIterator ci = ca.iterator();
Javac jv = new Javac(cc);
jv.compileStmnt("System.out.println(\"injected into loop\");");
Bytecode b = jv.getBytecode();
adjustCodeAttributeIfNeeded(b, ca);
ci.insertAt(0, b.get());
printMethod("After modifications", m);
Class c = cc.toClass();
logInfo("Attempting to create instance of modified class");
c.newInstance();
}
private void adjustCodeAttributeIfNeeded(Bytecode b, CodeAttribute ca){
int locals = b.getMaxLocals();
int stack = b.getMaxStack();
if(stack > ca.getMaxStack()) {
ca.setMaxStack(stack);
}
if(locals > ca.getMaxLocals()) {
ca.setMaxLocals(locals);
}
}
private void printMethod(String title, CtMethod m){
logInfo(title);
InstructionPrinter instructionPrinter = new InstructionPrinter(System.out);
instructionPrinter.print(m);
}
private void logInfo(String message){
System.out.println("");
System.out.println("------" + message);
}
public class EmptyLoopClass {
public void emptyLoopMethod() {
for(;;){
}
}
}
public static void main(String[] args) throws Exception {
// have errors written to sysout so all output from this program is in order
System.setErr(System.out);
new EmptyLoopTest().testEmptyLoopModification();
}
}
When I run this program, the following is written to the console..
------Before modifications
0: goto 0
------After modifications
0: getstatic #32 = Field java.lang.System.out(Ljava/io/PrintStream;)
3: ldc #34 = "injected into loop"
5: invokevirtual #40 = Method java.io.PrintStream.println((Ljava/lang/String;)V)
8: goto 8
------Attempting to create instance of modified class
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 8
Exception Details:
Location:
EmptyLoopTest$EmptyLoopClass.emptyLoopMethod()V @8: goto
Reason:
Expected stackmap frame at this location.
Bytecode:
0x0000000: b200 2012 22b6 0028 a700 00
Stackmap Table:
same_frame(@0)
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.newInstance(Class.java:412)
at EmptyLoopTest.testEmptyLoopModification(EmptyLoopTest.java:35)
at EmptyLoopTest.main(EmptyLoopTest.java:68)
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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Process finished with exit code 1
If everything was working as expected, I'd expect the goto instruction after modifications to goto 0 instead of what it's currently showing, goto 8. It's as if the StackMapTable wasn't adjusted appropriately when I invoked javassist.bytecode.CodeIterator#insertAt.
Can anyone spot what I'm doing wrong here?
Thanks,
Eric
you create class like this
c.newInstance();
so it supposed to have 0-parameters constructor - to be static inner classyou should call
methodInfo.rebuildStackMap(cp);
after code modifications. JVM uses StackMap to validate class file.Anyway you are ending up with bytecode
This code is not inside the loop, it is before loop. Actual loop is single instruction
8: goto 8
and you have to generate some additional loop code if you want to inject instructions inside it.