Search code examples
byte-buddy

Intercepting Object.class toString() method with Byte Buddy


I am using Byte Buddy to intercept some JDK methods, System.class and Thread.class is well, but is not work on java.lang.Object. I am running my test on JDK8 and it doesn't throw any errors.

final public class AgentBootstrap {
  public static void premain(String agentArgs, Instrumentation inst) throws Exception {
    try {
      new AgentBuilder.Default()
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
        .with(AgentBuilder.TypeStrategy.Default.REBASE)
        .enableNativeMethodPrefix("$$mynative_")
        .ignore(ElementMatchers.none())
        .with(
          new AgentBuilder.Listener.Filtering(
            new StringMatcher("java.lang.Object", StringMatcher.Mode.EQUALS_FULLY),
            AgentBuilder.Listener.StreamWriting.toSystemOut()))
        .type(ElementMatchers.is(Object.class))
        .transform(new Transformer() {
          @Override
          public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
            return builder.method(ElementMatchers.named("toString")).intercept(FixedValue.value("HELLO BYTE BUDDY!"));
          }
        })
        .installOn(inst);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

And I try to use Javassist, transform java.lang.Object's method is successful. Could anyone konw why does it not work on Object.class?


Solution

  • You want to use

    • disableClassFormatChanges() in order to avoid structural class file changes which would violate retransformation rules,
    • the advice API in order to insert code into existing methods without adding new ones.
    import net.bytebuddy.agent.ByteBuddyAgent;
    import net.bytebuddy.agent.builder.AgentBuilder;
    import net.bytebuddy.asm.Advice;
    
    import java.lang.instrument.Instrumentation;
    
    import static net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy.RETRANSFORMATION;
    import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
    import static net.bytebuddy.matcher.ElementMatchers.*;
    
    class Scratch {
    
      public static class ToStringAdvice {
        @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
        public static boolean before() {
          // Skip original method execution (false is the default value for boolean)
          return false;
        }
    
        @Advice.OnMethodExit
        public static void after(@Advice.Return(readOnly = false, typing = DYNAMIC) Object returnValue) {
          // Set fixed return value
          returnValue = "HELLO BYTE BUDDY!";
        }
      }
    
      public static void premain(String agentArgs, Instrumentation inst) {
        new AgentBuilder.Default()
          .disableClassFormatChanges()
          .with(RETRANSFORMATION)
          .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
          .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
          .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
          .ignore(none())
          .type(is(Object.class))
          .transform((builder, typeDescription, classLoader, module) ->
            builder.visit(
              Advice
                .to(ToStringAdvice.class)
                .on(named("toString"))
            )
          )
          .installOn(inst);
      }
    
      public static void main(String[] args) throws Exception {
        Instrumentation instrumentation = ByteBuddyAgent.install();
        premain("", instrumentation);
        instrumentation.retransformClasses(Object.class);
        System.out.println(new Object());
      }
    
    }
    

    This works, ...

    [Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@3bfdc050 on sun.instrument.InstrumentationImpl@1bce4f0a
    [Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)]
    [Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
    [Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)]
    [Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!
    [Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
    HELLO BYTE BUDDY!
    

    ... but I think you should think twice before manipulating JDK core classes. Look at the above log. Do you notice how in log line

    [Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!
    

    two objects are being printed which obviously use the method you have just advised? So be careful!