Guan Hao
Guan Hao

Reputation: 136

How to replace input arguments using ByteBuddy's @Advice.AllArguments?

I am using ByteBuddy's @Advice to transform my classes and it works fine until I try to replace input arguments.

I have a FooService with a join method which just joins two strings with a space.

public class FooService {
    public String join(String message, String message1) {
        return message + " " + message1;
    }
}

And I have another method which takes a Object[] args array input and changes elements in the array.

    public static ArgsProcessor argsProcessor = args -> {
        args[0] = args[0] + "-suffix";
        args[1] = "replaced";
    };

I've tried different ways to use the argsProcessor to manipulate the input arguments in @Advice.OnMethodEnter method. For me, the following advice implementations are almost equivalent and should all work, somehow only Advice1 works.

    public static class Advice1 {
        @Advice.OnMethodEnter
        public static void onEnter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args) {
            Object[] newArgs = Arrays.copyOf(args, args.length);
            ArgsProcessor argsProcessor = Demo.argsProcessor;
            argsProcessor.process(newArgs);
            args = newArgs;
        }
    }

    public static class Advice2 {
        @Advice.OnMethodEnter
        public static void onEnter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args) {
            Object[] newArgs = new Object[args.length];
            ArgsProcessor argsProcessor = Demo.argsProcessor;
            argsProcessor.process(args);
            System.arraycopy(args, 0, newArgs, 0, args.length);
            args = newArgs;
        }
    }

    public static class Advice3 {
        @Advice.OnMethodEnter
        public static void onEnter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args) {
            Object[] newArgs = Arrays.copyOf(args, args.length);
            try {
                ArgsProcessor argsProcessor = Demo.argsProcessor;
                argsProcessor.process(newArgs);
                args = newArgs;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class Advice4 {
        @Advice.OnMethodEnter
        public static void onEnter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args) {
            ArgsProcessor argsProcessor = Demo.argsProcessor;
            argsProcessor.process(args);
        }
    }

    public static class Advice5 {
        @Advice.OnMethodEnter
        public static void onEnter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] args) {
            ArgsProcessor argsProcessor = Demo.argsProcessor;
            argsProcessor.process(args);
            args = Arrays.copyOf(args, args.length);
        }
    }

Output

Advice1 a-suffix replaced
Advice2 a b
Advice3 a b
Advice4 a b
Advice5 a b

The code snippet https://gist.github.com/raptium/ab7830e5d7f7cba43bbd2c2a5c7b38e0

Upvotes: 3

Views: 750

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

Running your code, I get

Advice1 a-suffix replaced 
Advice2 a b 
Advice3 a-suffix replaced 
Advice4 a b 
Advice5 a b

what is what I expect. Byte Buddy uses advice methods as templates. This code is not really exeucted. When you read args in your method, Byte Buddy creates a new array every time.

Therefore, computing args == args would return false since Byte Buddy creates a new array containing all arguments every time! If you change the args array, you have to write it back for Byte Buddy to discover the corresponding byte code and to map it back to an assignment.

Upvotes: 1

Related Questions