-
-
Notifications
You must be signed in to change notification settings - Fork 828
Closed
Description
ClassFormatError happens when advice is applied to a method that
- has a leading stack frame
- creates a new value with the first instruction
- leaves the newly create value uninitialized and performs a jump
Seems like bytebuddy inserts an extranop
before original method code and the uninitialized value in stack frame points to thatnop
instead of the followingnew
instruction.
import static net.bytebuddy.matcher.ElementMatchers.named;
import java.lang.invoke.MethodHandles;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
public class Main implements Opcodes {
public static void main(String... args) throws Exception {
new AgentBuilder.Default().type(named("Test"))
.transform(new AgentBuilder.Transformer.ForAdvice().advice(named("run"), RunnableAdvice.class.getName()))
.asTerminalTransformation()
.installOn(ByteBuddyAgent.install());
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = cw;
cv.visit(
V9,
ACC_PUBLIC | ACC_SUPER,
"Test",
null,
"java/lang/Object",
new String[] { "java/lang/Runnable" });
{
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
{
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
mv.visitCode();
Label newInsn = new Label();
//XXX ClassFormatError does not happen without this line
mv.visitFrame(F_SAME, 0, null, 0, null);
mv.visitTypeInsn(NEW, "java/util/ArrayList");
Label jump = new Label();
mv.visitJumpInsn(GOTO, jump);
mv.visitFrame(F_FULL, 0, null, 1, new Object[] { "java/lang/Throwable" });
mv.visitInsn(NOP);
mv.visitInsn(NOP);
mv.visitInsn(NOP);
mv.visitInsn(ATHROW);
mv.visitLabel(jump);
mv.visitFrame(F_FULL, 0, null, 1, new Object[] { newInsn });
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
mv.visitVarInsn(ASTORE, 1);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
cv.visitEnd();
byte[] bytes = cw.toByteArray();
Class<?> c = MethodHandles.lookup().defineClass(bytes);
Runnable r = (Runnable) c.getDeclaredConstructor().newInstance();
r.run();
}
public static class RunnableAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void enter() {
System.err.println("enter");
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void exit() {
System.err.println("exit");
}
}
}