How to redefine a class with Byte-buddy to add an additional method

376 Views Asked by At

If I have the following Class named Dog

public class Dog {
  
  private String name = "Spike";
  private int age = 3;
  
  public Dog() {}
  
  public String getName() {
    return name;
  }
}

How would I declare ByteBuddy for use in a javaagent to redefine the Dog class to provide the method getAge which returns an int?

The desired (effective) class should look like this below and be able to be invoked by doing dog.getAge()

public class Dog {
  
  private String name = "Spike";
  private int age = 3;
  
  public Dog() {}
  
  public String getName() {
    return name;
  }

  public int getAge() {
    return age;      
  }
}

Attempt 1: I've tried changing the syntax to what I understand to be correct (PitBullInterceptor.class found below)

new AgentBuilder.Default()
.redefine(Dog.class)
.defineMethod("getAge", int.class, Method.PUBLIC)
.intercept(MethodDelegation.to(PitBullInterceptor.class))
.installOn(instrumentation);

I have the following issue in my attempts

The method redefine(Class) is undefined for the type AgentBuilder.Default

Which is basically a syntax error. I'm not sure which DynamicTypes or MethodDelegations/ElementMaters to use to configure the Agentbuilder to perform to this type of redefine/rebase as Rafael Winterhalter describes it

Attempt 2: I've tried using it with ByteBuddyAgent.install

  public static void main(String[] args) throws Exception {
    
    ByteBuddyAgent.install();
    
    new ByteBuddy()
    .redefine(Dog.class)               
    .defineMethod("getAge", int.class, Method.PUBLIC)
    .intercept(MethodDelegation.to(PitBullInterceptor.class))
    .make()
    .load(Dog.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
    .getLoaded()
    .newInstance();
  }
  
  public static class PitBullInterceptor {
    
    private static int age = 1;
    
    public static int getAge() {
      return age;
    }
  }

Produces the error:

java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

Attempt 3: Lastly, I've tried doing so with the agent built

  public static void premain(String arguments, Instrumentation instrumentation) {
  
    new AgentBuilder.Default()
    .disableClassFormatChanges().with(RetransformationStrategy.RETRANSFORM)
    .with(new ByteBuddy()
    .redefine(PitBull.class)
    .method(ElementMatchers.named("getAge"))
    .intercept(MethodDelegation.to(PitBullInterceptor.class))
    .make()
    .load(Dog.class.getClassLoader()))
    .installOn(instrumentation);
  }
  
  public static class PitBull {
    
    private String name = "Kujo";
    
    public String getName() {
      return name;
    }
  }
  
  public static class PitBullInterceptor {
    
    private static int age = 3;
    
    public static int getAge() {
      return age;
    }
  }

Produces:

java -javaagent:agent.jar -jar app.jar
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
Caused by: java.lang.IllegalStateException: Class already loaded: class io.xxx.agent.boss.Agent$PitBull
        at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.load(ByteArrayClassLoader.java:363)
        at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$WrappingDispatcher.load(ClassLoadingStrategy.java:367)
        at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:148)
        at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101)
        at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6317)
        at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6305)
        at io.xxx.agent.boss.Agent.premain(Agent.java:21)
        ... 6 more
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at t:\workspace\open\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 422
FATAL ERROR in native method: processing of -javaagent failed, processJavaStart failed
1

There are 1 best solutions below

4
0x150 On

Due to limitations of the JVM, you can't add, remove or modify members of existing classes. You have to either

  1. Add the method before the class is loaded, or
  2. Modify the class on disk before the JVM even starts

Alternatively, you could use third party JVMs that do support that kind of modification (see: JetBrains runtime with the Enhanced class redefinition flag), but assuming this needs to run universally, it'd be a bit of a burden.