byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

Is there a more efficient way for the interweaved code to access the `Method` object?

Open FrankChen021 opened this issue 3 years ago • 5 comments

For the Advice example below,

    @Advice.OnMethodEnter
    public static boolean onEnter(final @Advice.Origin Method method) {
         Context ctx = new Context(method);
   }

The interweaved code calls getMethod to retrieve the Method object.

    public void close() {
        Context var1 = new Context(DruidDataSource.class.getMethod("close"));

I think it's not high efficient, each time the target method is called, the getMethod will be invoked. And If we want to get the Method object both in the OnMethodEnter and OnMethodExit, the generated code will call such method twice.

And if the Advice is like this:

    @Advice.OnMethodEnter
    public static boolean onEnter(final @Advice.Origin Method method) {
         Context ctx = new Context(method, method.getDeclaringClass());
   }

We can see that the injected code calls the getMethod twice:

    public void close() {
        Context var1 = new Context(DruidDataSource.class.getMethod("close"), 
                                   DruidDataSource.class.getMethod("close").getDeclaringClass());

I also checked the interweaved code of MethodDelegation way. I found that for any Method object, it's cached in a static object which is initialized in the static initializer, which is more efficient than the Advice way.

public class DruidDataSource {
static {
        cachedValue$L47wuwfW$kqgv4v0 = DruidDataSource.class.getMethod("close");
    }

    public void close() {
          ....
          delegate$jgtt6t1.intercept(cachedValue$L47wuwfW$kqgv4v0);
          ....
     }
}

So, for the Advice, is it able to interweave the code for the Method object as the way MethodDelegation does?

FrankChen021 avatar Feb 21 '22 02:02 FrankChen021

You can use caching in advice, too. The origin annotation has a cached property that you can set. As advice is often used for retransformation, and since retransformation does not support adding fields, this is normally disabled.

You can otherwise use the origin on a string, unless you need to process method objects in a more complex manner.

raphw avatar Feb 22 '22 11:02 raphw

Thank you @raphw for your response. In my case, I really need to process the method objects.

But I didn't see this cached property on Advice.Origin annotation. Do I miss something? The net.bytebuddy.implementation.bind.annotation.Origin has a cache property.

FrankChen021 avatar Feb 22 '22 11:02 FrankChen021

You are right, I must have planned but never implemented this.

You could however simply create your own offset mapping and use MethodConstant.of(instrumentedType).cached() as the resolved value. You can do this via Advice.withCustomBinding().bind(MyAnnotation.class, ...). Simply have a look at the binding for Origin to see how this can be done.

raphw avatar Feb 22 '22 11:02 raphw

OK, I will try it. Very appreciate your response.

FrankChen021 avatar Feb 22 '22 11:02 FrankChen021

@raphw It works by defining a customer mapping as below.

public class TargetMethodMapping implements Advice.OffsetMapping {
    @Override
    public Target resolve(TypeDescription instrumentedType,
                          MethodDescription instrumentedMethod,
                          Assigner assigner,
                          Advice.ArgumentHandler argumentHandler,
                          Sort sort) {
        return new Target.ForStackManipulation(MethodConstant.of(instrumentedMethod.asDefined()).cached());
    }
}

The interweaved code shows that the method objects are cached in static variables which are initialized in static initializer.

    static {
        cachedValue$fbnROjZ8$kqgv4v0 = DruidDataSource.class.getMethod("close");

Thank you again.

FrankChen021 avatar Feb 22 '22 13:02 FrankChen021