question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Overwriting method with MethodDelegation silently ignored when applied from ByteBuddy agent

See original GitHub issue

My use case

I am looking into using ByteBuddy for weaving in some extra logic in one of our methods related to performing DB calls. Since the method is called very often, and the extra logic involves a ConcurrentHashMap being accessed (and in the worst case, written to), we want to avoid doing this in the “real” production code.

Instead, we are trying to create a dynamic type which only gets used when we run our integration tests. We use this by utilizing the @BeforeSuite annotation in testng, to let us install the ByteBuddy Agent and our transformations at an early-enough stage.

The class we are trying to replace is DatabaseMediator in the example below.

What works

        ByteBuddyAgent.install();

        new AgentBuilder.Default()
                .ignore( none() )
                .with( AgentBuilder.InitializationStrategy.NoOp.INSTANCE )
                .with( AgentBuilder.TypeStrategy.Default.REBASE )
                .with( AgentBuilder.RedefinitionStrategy.REDEFINITION )
                .type( named( DatabaseMediator.class.getName() ) ).transform( new AgentBuilder.Transformer() {
            @Override
            public DynamicType.Builder<?> transform( DynamicType.Builder<?> builder, TypeDescription typeDescription, lassLoader classLoader, JavaModule javaModule ) {
                return new ByteBuddy()
                        .redefine( DatabaseMediator.class )
                        .method( named( "getQuery" ) )
                        .intercept( FixedValue.value( "OVERWRITTEN VALUE BY BYTEBUDDY!" ) );
            }
        } )
                .installOnByteBuddyAgent();

The approach above overwrites our private String getQuery( String queryId ) method with a new dummy method, returning the fixed string above. When running this, all DB queries fail since they try to execute OVERWRITTEN VALUE BY BYTEBUDDY! as an SQL query.

Unfortunately though, this is not fully enough for our use case. 😉

What does not work

This is closer to what we actually need:

    @BeforeSuite
    public void beforeSuite() {
        Builder<DatabaseMediator> databaseMediatorBuilder = new ByteBuddy()
                .rebase( DatabaseMediator.class )
                .method( named( "getQuery" ) )
                .intercept( MethodDelegation.to( SeenQueriesInterceptor.class ) );

        // Added to avoid simple errors in the definition (will be silently ignored by `ByteBuddyAgent`)
        databaseMediatorBuilder.make();

        ByteBuddyAgent.install();

        new AgentBuilder.Default()
                .ignore( none() )
                .with( NoOp.INSTANCE )
                .with( AgentBuilder.TypeStrategy.Default.REDEFINE )
                .with( AgentBuilder.RedefinitionStrategy.REDEFINITION )
                .type( named( DatabaseMediator.class.getName() ) ).transform( new Transformer() {
            @Override
            public Builder<?> transform( Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule ) {
                return databaseMediatorBuilder;
            }
        } )
                .installOnByteBuddyAgent();
    }

The SeenQueriesInterceptor looks like this:

    public static class SeenQueriesInterceptor {

        @RuntimeType
        public static Object intercept( @SuperCall Callable<?> zuper, @AllArguments Object[] args ) throws Exception {
            System.out.println( "Hello world from method interceptor" );
            return "foo";
        }
    }

Unfortunately, whenever I install the transformation above, the transformation is completely ignored and no exceptions are thrown. The original getQuery() method is executed instead.

Things tested thus far

Apart from the @BeforeSuite example above, I’ve also tried calling my interceptor from a simpler example like this (note, no ByteBuddy Agent involved):

    public static class Foo {
        public String getQuery( String id ) {
            return "value from Foo";
        }
    }

    @BeforeSuite
    public void beforeSuite() throws Exception {
        DynamicType.Loaded<Foo> loaded = new ByteBuddy()
                .subclass( Foo.class )
                .method( isDeclaredBy( Foo.class ) )
                .intercept( MethodDelegation.to( SeenQueriesInterceptor.class ) )
                .make()
                .load( Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER );
        Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        String s = instance.getQuery( "foo" );
    }

    public static class SeenQueriesInterceptor {

        @RuntimeType
        public static Object intercept( @SuperCall Callable<?> zuper, @AllArguments Object[] args ) throws Exception {
            System.out.println( "Hello world from method interceptor" );
            return "foo";
        }
    }

The above works, but note an important difference here: the getQuery() method is public; the real DatabaseMediator.getQuery() method which I’m trying to replace is private.

I feel quite stuck here. Any ideas on what could be the problem here?


One thing that is a bit frustrating in this is that whenever I try to do these transformations via the ByteBuddy Agent, it feels like all errors are silently disregarded. That’s why I move the actual transformation out of the transform()method and added the explicitdatabaseMediatorBuilder.make(). This was helpful in making sure the SeenQueriesInterceptor.intercept()` method has the correct method signature. 🙏

I guess there isn’t any way to enable more logging from the ByteBuddy Agent, if something goes wrong with a particular transformation? It’s much easier to debug errors which gets printed somewhere than just the dreaded “nothing works, and no exception is thrown”-type of errors. 😅

Thanks in advance for any help with this, appreciated.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
raphwcommented, Aug 18, 2021

Just for reference, instead of:

.with( AgentBuilder.Listener.WithErrorsOnly.StreamWriting.toSystemError() )

you would write

.with( AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly() )

The former does not what you expect as WithErrorsOnly does inherit all nested classes of its parent. You can also add withTransformationsOnly what will print errors and transformed classes.

That the delegation example is working surprises me, but I assume that there’s something else going wrong.

1reaction
raphwcommented, Aug 17, 2021

The default behavior of a ClassFileTransformer does exactly that: disregard the error. You can register an AgentBuilder.Listener to be notified of any errors, for example by printing them to the console.

As for your setup, you would probably want rebasing if you were using a MethodDelegation. The original code would be copied to another method if you require a SuperCall what retransformation does not allow. I assume this would be what the exception would be telling you, too.

Rather then method delegation, you should probably use Advice which allows you to add logic to a method without requiring any additional methods. You should then also use .disableClassFormatChanges().

Read more comments on GitHub >

github_iconTop Results From Across the Web

Unable to use MethodDelegation with method(..).intercept(..)
I am using ByteBuddy version 1.10.18 I am unable to use MethodDelegation ... MethodDelegation$Appender.apply(MethodDelegation.java:1346) at ...
Read more >
Byte Buddy's method delegation leads to StackOverflowError
In order to do this I use Byte Buddy with method delegation. Agent: //Agent code before... private static void instrument(String agentOps, ...
Read more >
A Guide to Byte Buddy - Baeldung
A quick and practical example of using ByteBuddy - a tool for runtime ... is a subclass of Object.class and override the toString()...
Read more >
Java Code Manipulation with Byte Buddy - Sergio Martin Rubio
Byte Buddy is a library to help you create and modify Java classes and provides a feature for generating Java Agents.
Read more >
Java static code analysis: "@Override" should be used on ...
It elicits a warning from the compiler if the annotated method doesn't actually override anything, as in the case of a misspelling. It...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found