raphw / byte-buddy

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

[question] How can replace field access with another class? #1687

Closed lucas-myx closed 2 months ago

lucas-myx commented 3 months ago

@raphw Sorry to inflict myself on you again like this!
as shown in the question, I encountered a problem while using MemberSubstitution to replace field A.foo access with another class B.bar() , as follows:

public class A {
    public String foo;
    public String foo() {
        return foo; // 'return B.bar()' after replaced
    }
}
public class B {
    public static String bar() {
        return "bar";
    }
}

an exception was thrown while executing the following code:

new ByteBuddy()
        .redefine(A.class)
        .visit(MemberSubstitution.relaxed()
                .field(named("foo"))
                .onRead()
                .replaceWith(B.class.getDeclaredMethod("bar"))
                .on(named("foo")))
        .make();

exception:

java.lang.IllegalStateException: Cannot invoke public static java.lang.String net.bytebuddy.asm.B.bar() on 1 parameters
    at net.bytebuddy.asm.MemberSubstitution$Substitution$ForMethodInvocation.resolve(MemberSubstitution.java:1266)

if field foo is static, there is no problem, but I understand that there should be no restriction on replacing with a static method, as long as the type and variable returned by the bar method are consistent?

I'm not very familiar with it, please correct me. Thank you very much!

raphw commented 3 months ago

You cannot make a direct replacement of this field access with a static method. The field can be seen as a read on this. With a direct replacement, it would expect the same. You can however use a chain where you have more flexibility. Instead of using replaceWith, you use:

.replaceWithChain(
  new MemberSubstitution.Substitution.Chain.Step.ForInvocation.Factory(
    B.class.getDeclaredMethod("bar")
  )
)

It's called a chain because you can add multiple replacement instructions. In a chain the arguments to a replaced instruction (like this to a field read) are stored in the local variable array. This is why you can apply a substitution that takes different arguments.

lucas-myx commented 3 months ago

Thank you very much for taking the time to reply! I found a similar usage in your Unit test: MemberSubstitutionTest.java
which was written in great detail, I need to learn more about it. Thanks again!