raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.29k stars 807 forks source link

Side effects of bytebuddy rebasing to weaving process? #1665

Closed rage5474 closed 4 months ago

rage5474 commented 4 months ago

Inspired from this stackoverflow question byte-buddy-and-osgi-weaving-hook, I tried following WeavingHook example with Eclipse RCP equinox runtime:

Code

// WeavingHookService.java
public class WeavingHookService implements WeavingHook {

  @Override
  public void weave(WovenClass wovenClass) {

    if (wovenClass.getClassName().endsWith("MyPrinter")) {
      try {
        byte[] originalBytes = wovenClass.getBytes();

        Unloaded<MyPrinter> newPrinter = new ByteBuddy()
            .rebase(MyPrinter.class,
                ClassFileLocator.Simple
                    .of(wovenClass.getClassName(),
                        originalBytes))
            .method(ElementMatchers.named("getName"))
            .intercept(FixedValue.value("Raphael"))
            .make();

        // setBytes has no effect.
        wovenClass.setBytes(newPrinter.getBytes());
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

// MyPrinter.java
public class MyPrinter {
  public String getName() {
    return "Unknown";
  }
}

Expectation My expectation is that if I call new MyPrinter().getName() in my code, Raphael is returned.

Observation If I call new MyPrinter().getName() in my code, Unknown is returned.

Already analyzed If I set a hard-coded byte array, like new byte[]{'0', '1'} instead of doing bytebuddy rebase call, setBytes throws at least some exception. But this does not happen, if a bytebuddy rebase call is done before setBytes is called.

Question So, for me it seems that the bytebuddy rebase call has side-effects to the weaving process. But maybe I am doing something wrong here. Does anybody have an idea what's going wrong?

raphw commented 4 months ago

Byte Buddy implements an immutable builder, there should be no side effects. Maybe the weaver fails as you refer to the loaded MyPrinter class?

You can use a TypePool to describe s class from class bytes.

rage5474 commented 4 months ago

Thanks for the hint. This code is working fine for me. With this service it is even possible to modify not exported classes without modifying anything in the manifest. This is really great, because I can modify cross-cutting things in OSGi without using java agents.

@Component(immediate = true)
public class WeavingHookService implements WeavingHook {

  private static final String CLASS_TO_MODIFY = "my.waevinghook.example.internal.MyPrinter";

  @Override
  public void weave(WovenClass wovenClass) {

    if (wovenClass.getClassName().equals(CLASS_TO_MODIFY)) {
      try {
        byte[] originalBytes = wovenClass.getBytes();

        ClassFileLocator locator = ClassFileLocator.Simple
            .of(wovenClass.getClassName(),
                originalBytes);

        Unloaded<Object> newPrinter = new ByteBuddy()
            .rebase(TypePool.Default.of(wovenClass.getBundleWiring().getClassLoader())
                .describe(
                CLASS_TO_MODIFY).resolve(),
                locator)
            .method(ElementMatchers.named("getName"))
            .intercept(FixedValue.value("Raphael"))
            .make();

        wovenClass.setBytes(newPrinter.getBytes());
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}