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

`MemberSubstitution` can't replace instructions within synthetic methods. #1679

Closed dogourd closed 4 months ago

dogourd commented 4 months ago

When using rebase or redefine, instructions within synthetic methods are not successfully replaced. However, using decorate works fine.

After some debugging, I discovered that it seems like the target method is ignored during the MethodRegistry.prepare execution stage when calling the toTypeWriter method.

Currently, the byte-buddy-maven-plugin seems to be using the rebase strategy, and I haven't found an effective parameter to control this option yet, which is limiting some of my plugin's functionality.

Below is a code example to reproduce this issue. I've saved the class files generated by these three methods in the execution directory. You can check the GETSTATIC instruction within the access$000 method to see the difference.

class Foo {
    private static final Logger log = Logger.getLogger(Foo.class.getName());

    private static class Bar {
        private void doSomething() {
            if (log.isLoggable(Level.FINE)) {
                log.fine("do something.");
            }
        }
    }
}

public class Main {

    public static void main(String[] args) throws Throwable {
        String fooName = Foo.class.getName(), julName = "java.util.logging.Logger";
        TypePool pool = TypePool.Default.ofSystemLoader();
        ByteBuddy byteBuddy = new ByteBuddy();
        TypeDescription jul = pool.describe(julName).resolve();
        TypeDescription foo = pool.describe(fooName).resolve();
        ClassFileLocator locator = ClassFileLocator.ForClassLoader.ofSystemLoader();

        MethodDescription julGetLoggerMethod = new MethodDescription.Latent(jul, new MethodDescription.Token(
                "getLogger", Modifier.PUBLIC | Modifier.STATIC, jul.asGenericType(),
                Collections.singletonList(TypeDescription.ForLoadedType.of(String.class).asGenericType())
        ));

        MemberSubstitution.Substitution.Factory julGetLoggerFactory =
                new MemberSubstitution.Substitution.Factory() {
                    @Override
                    public MemberSubstitution.Substitution make(
                            TypeDescription instrumentedType, MethodDescription instrumentedMethod,
                            TypePool typePool) {
                        return new MemberSubstitution.Substitution() {
                            @Override
                            public StackManipulation resolve(
                                    TypeDescription receiver, ByteCodeElement.Member original,
                                    TypeList.Generic parameters, TypeDescription.Generic result,
                                    JavaConstant.MethodHandle methodHandle, StackManipulation stackManipulation, int freeOffset) {
                                return new StackManipulation.Compound(
                                        ConstantValue.Simple.wrap(fooName).toStackManipulation(),
                                        MethodInvocation.invoke(julGetLoggerMethod));
                            }
                        };
                    }
                };
        AsmVisitorWrapper.ForDeclaredMethods substitution = MemberSubstitution.relaxed().field(fieldType(named(julName))).onRead().replaceWith(julGetLoggerFactory).on(any());

        decorate(byteBuddy, foo, locator).visit(substitution).make().saveIn(new File("."));

        /*
        rebase and redefine cannot replace the `GETSTATIC` in
        synthetic method: static synthetic access$000()Ljava/util/logging/Logger;
        but decorate can.
         */
        rebase(byteBuddy, foo, locator).visit(substitution).make().saveIn(new File("."));
        redefine(byteBuddy, foo, locator).visit(substitution).make().saveIn(new File("."));
    }

    private static <T> DynamicType.Builder<T> rebase(ByteBuddy byteBuddy, TypeDescription target, ClassFileLocator locator) {
        return byteBuddy.<T>rebase(target, locator).name(target.getName() + "_rebase");
    }

    private static <T> DynamicType.Builder<T> redefine(ByteBuddy byteBuddy, TypeDescription target, ClassFileLocator locator) {
        return byteBuddy.<T>redefine(target, locator).name(target.getName() + "_redefine");
    }

    private static <T> DynamicType.Builder<T> decorate(ByteBuddy byteBuddy, TypeDescription target, ClassFileLocator locator) {
        return byteBuddy.<T>decorate(target, locator);
    }
}
raphw commented 4 months ago

You can set DECORATE as initialization: https://github.com/raphw/byte-buddy/blob/master/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ByteBuddyMojo.java#L151

As for the synthetic methods, you can define .ignore(none()) on the agent builder to include them for delegation.

dogourd commented 4 months ago

You can set DECORATE as initialization: https://github.com/raphw/byte-buddy/blob/master/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ByteBuddyMojo.java#L151

This works great! Now the plugin is functioning as I expected.

As for the synthetic methods, you can define .ignore(none()) on the agent builder to include them for delegation.

I'm not sure what this means. I'm not using AgentBuilder here, and from what I understand after looking through the API, ignore(none()) in the context of AgentBuilder is used to ignore type matches, not methods. If you're referring to DynamicType.Builder#ignoreAlso(none()), I tried running it once, and it didn't seem to work.

It worked too, use ByteBuddy().ignore(none())