Search code examples
javabytecodeinstrumentationbyte-buddy

Running into issues migrating from Byte Buddy 0.7.7 to 1.0.2


I'm running into some issues in my test suite, migrating from Byte Buddy 0.7.7 to 1.0.2

Here is a simplified example:

public class ReproBug {

    @Test
    public void test() {
        ByteBuddyAgent.install();

        new AgentBuilder.Default().type(nameStartsWith("test"))
                .transform(new AgentBuilder.Transformer() {

                    @Override
                    public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription) {

                        return builder.method(isDeclaredBy(typeDescription)).intercept(to(new Object() {

                            @RuntimeType
                            public void intercept(@SuperCall Callable<?> zuper, @Origin Method method) {
                                System.out.println("intercepting " + method.getName());
                            }
                        }));
                    }
                }).installOnByteBuddyAgent();

        MyClass.staticMethod();
    }
}

Code for MyClass:

class MyClass {
    public static void staticMethod() {
        System.out.println("in staticMethod");
    }
}

No errors are reported by Byte Buddy 0.7.7 however with 1.0.2 I get errors like Cannot resolve type description for test.MyClass$auxiliary$dUGbkato.

Full logs (from the AgentBuilder.Listener): http://pastebin.com/ytsQR5bi

Note that the method is intercepted.

However in some of my tests I get double the amount of interceptions because it intercepts the method call of the auxiliary classes.


Solution

  • I was able to reproduce your problem when adding a listener. The difference between 0.7.7 and 1.0.3 is the time at which Byte Buddy loads an auxiliary class. In 1.0.3, Byte Buddy loads a class as a part of the class's static initializer. This way, Byte Buddy makes sure that no axiliary class triggers a premature loading of the instrumented class, for example, if the auxiliary class is a subtype of the instrumented class. In the past, this has aborted the instrumentation and Byte Buddy would have failed with an error.

    As an implication, auxiliary classes are no longer loaded as a part of an instrumentation procedure and emit a class loading event to the agent API such that the transformer gets active. As your matcher includes all types with test and since auxiliary types are found in the same package as their instrumented type, the attempted to instrument these auxiliary types but could not find a class file. Therefore, the exception was raised.

    In Byte Buddy 1.1.0 (to be released), there is a new method in the builder API, ignoreTypes where one can specify any types that should be ignored entirely. By default, Byte Buddy will now ignore any synthetic types. Since auxiliary types are synthetic, the problem that you experience is no longer happening in the default setup.

    In practice, the information in the log does however not have any effect to your program. The original type is always instrumented as it is supposed to. A problem only occurs when Byte Buddy can as a matter of fact instrument your auxiliary types where the duplicate instrumentation is happening.

    On a general note, in a unit test, you should always make sure to remove a transformer after you registrered the agent, e.g:

    Instrumentation inst = ByteBuddyAgent.install();
    ClassFileTransformer cft = agentBuilder.installOnByteBuddyAgent();
    try {
     // run test
    finally {
      inst.removeTransformer(cft);
    }
    

    It is also a good practice to be very specific in a test for what classes you want to intercept, e.g. by specifying a class's fully qualified name.