-2

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
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
stackoverflow
  • 18,348
  • 50
  • 129
  • 196
  • 1
    What error do you get from each of the things you've tried? – Solomon Ucko May 06 '23 at 23:26
  • Question has been updated – stackoverflow May 06 '23 at 23:35
  • Got it, thanks. I assume that's the error for the first attempt; how about the second one, beginning with `new ByteBuddy()`? – Solomon Ucko May 07 '23 at 01:03
  • `java.lang.IllegalArgumentException: None of [] allows for delegation from int io.xxx.model.Dog.getAge()` – stackoverflow May 07 '23 at 01:11
  • 1
    As soon as you do `.redefine(PitBull.class)` it will load `PitBull`. You want to avoid that - but I have no idea how to do that with bytebuddy. (With a normal agent, I'd just install a transformer that does the changes when the class is eventually loaded. Or does nothing if the class is never loaded...) – Johannes Kuhn May 07 '23 at 01:39

1 Answers1

1

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.

0x150
  • 589
  • 2
  • 11