Search code examples
javajavaagentsbyte-buddy

How to override values of object in runtime using byte-buddy


This is my object class:

package com.example;

public class Car {

private String name;
private String model;

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public String getModel() {
    return model;
}
public void setModel(String model) {
    this.model = model;
}

This is operation class:

package com.example;

public class Call1 {

public static String callMethod1(){
    return "Hello from callMethod1";
}

public static Car callingCall2(){
    Call2 call = new Call2();
    Car args= call.callMethod2();

    return args;
}
}

This is another class:

package com.example;

public class Call2 {

public  Car callMethod2(){

    Car car = new Car();
    car.setModel("2009");
    car.setName("mustang");
    return car;
}
}

This is my test class:

package com.example;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.OnMethodEnter;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import org.junit.Test;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import static net.bytebuddy.matcher.ElementMatchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;


public class CallingTest {

@Test
public void givenCall1_whenRedefined_thenReturnFooRedefined() throws 
Exception {

     premain(null, ByteBuddyAgent.install());
    /* Car carTest = new Car();
        carTest.setModel("2011");
        carTest.setName("Maruti");
   ByteBuddyAgent.install();
    new ByteBuddy()
        .redefine(Call1.class)
        .method(named("callingCall2"))
        .intercept(FixedValue.value(carTest))
        .make()
        .load(Car.class.getClassLoader(), 
ClassReloadingStrategy.fromInstalledAgent());*/
     Call1 f = new Call1();
    assertEquals(f.callingCall2().getModel(), "2011");
    assertEquals(f.callingCall2().getName(), "Maruti");
}

 public static void premain(String arguments, Instrumentation instrumentation) {
    new AgentBuilder.Default()
        .disableClassFormatChanges()
        .with(RedefinitionStrategy.RETRANSFORMATION)
        .type(is(Call1.class))
        .transform(new AgentBuilder.Transformer() {
/*          @Override
      public DynamicType.Builder transform(DynamicType.Builder builder,
                                              TypeDescription typeDescription,
                                              ClassLoader classloader) {
        return builder.method(named("toString"))
                      .intercept(FixedValue.value("transformed"));
      }*/

    @Override
    public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription,
            ClassLoader classLoader, JavaModule arg3) {
        Car carTest = new Car();
        carTest.setModel("2011");
        carTest.setName("Maruti");

        return builder.method(named("callingCall2")).intercept(MethodDelegation.to(MyAdvice.class));
//      return builder.visit((AsmVisitorWrapper) Advice.to(Advice.class).on(ElementMatchers.named("callingCall2")));

    }
    }).installOn(instrumentation);
  }
 class MyAdvice {
  @OnMethodEnter 
  Car foo() {
        Car carTest = new Car();
        carTest.setModel("2011");
        carTest.setName("Maruti");
        return carTest;
  }

  }

}

The requirement is to override the value of object Car in this scenario such that test class should pass. I tried using byte buddy and AgentBuilder for doing so. When I tried using redefine, it threw error which is 'java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)' When I tried using AgentBuilder, it is not affecting assertion resulting it to fail. I am newly introduced to byte-buddy so please help achieving requirement.


Solution

  • FixedValue can only be used with Strings, Class object, primitive types and their wrappers. To cover Your case You can introduce an interceptor like this:

    @Test
    public void givenCall1_whenRedefined_thenReturnFooRedefined() throws Exception {
        ByteBuddyAgent.install();
    
        new ByteBuddy()
            .redefine(Call1.class)
            .method(named("callingCall2"))
            .intercept(to(Interceptor.class))
            .make()
            .load(ByteBuddyTest2.class.getClassLoader(),
                  ClassReloadingStrategy.fromInstalledAgent());
    
        Call1 f = new Call1();
        assertEquals(f.callingCall2().getModel(), "2011");
        assertEquals(f.callingCall2().getName(), "Maruti");
    }
    
    public static class Interceptor {
        public static Car callingCall2() {
          Car carTest = new Car();
          carTest.setModel("2011");
          carTest.setName("Maruti");
          return carTest;
        }
    }