Skip to content

java.lang.ClassFormatError: StackMapTable format error: bad offset for Uninitialized in method Test.run()V #1113

@laurit

Description

@laurit

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 extra nop before original method code and the uninitialized value in stack frame points to that nop instead of the following new 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");
    }
  }
}

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions