Reputation: 3
I'm using java agent and bytebuddy to intercept the "read" and "write" methods in FileIOStreams. A feature to implement is "to call the original methods under certain circumstances, else pass". Due to this, I need to have full control of the invoking flow using method delegation instead of wrapping it with Advice.
The method interception works fine when @Morph is not there, but it does not work when I add @Morph to parameters. I have tested with some other annotations:
adding @AllArguments, @This will not block the delegation, the method will run as my interceptor;
adding @Morph, @SuperCall will block the delegation. No exception will be thrown: the original method will run as what it used to be.
Here is the code I want to implement:
public static void mountAgent(Instrumentation inst) {
new AgentBuilder.Default()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.").or(isSynthetic()), any(), any()))
.with(new AgentBuilder.Listener.Filtering(
new StringMatcher("java.io.FileInputStream", StringMatcher.Mode.EQUALS_FULLY)
.or(new StringMatcher("java.io.FileOutputStream", StringMatcher.Mode.EQUALS_FULLY)),
AgentBuilder.Listener.StreamWriting.toSystemOut()))
.type(named("java.io.FileOutputStream"))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module) {
return builder.method(named("write").and(not(isNative())).and(takesArgument(0, byte[].class)))
.intercept(MethodDelegation
.withDefaultConfiguration()
.withBinders(Morph.Binder.install(Morphing.class))
.to(WriteInterceptor.class));
}})
.installOn(inst);
}
(Code for appending interceptors to BootstrapClassLoaderSearch is skipped)
And following is my interceptors:
public interface Morphing<T> {
T Object invoke(Object[] agrs);
}
@SuppressWarnings("unused")
public static class WriteInterceptor {
@RuntimeType
public static void write(
//change the input here
byte[] bytes,
@AllArguments Object[] args,
@Morph Morphing<Void> morphing
) throws Exception {
if (true) {
morphing.invoke(args);
}
else {
// do something
throw new Exception();
}
}
}
If the input of intercepting function is empty or only byte[] bytes, the delegation will work and Exception is thrown:
[Byte Buddy] IGNORE java.io.FileInputStream [null, module java.base, loaded=true]
[Byte Buddy] COMPLETE java.io.FileInputStream [null, module java.base, loaded=true]
[Byte Buddy] DISCOVERY java.io.FileOutputStream [null, module java.base, loaded=true]
[Byte Buddy] TRANSFORM java.io.FileOutputStream [null, module java.base, loaded=true]
[Byte Buddy] COMPLETE java.io.FileOutputStream [null, module java.base, loaded=true]
Exception: java.lang.Exception thrown from the UncaughtExceptionHandler in thread "main"
If the input is
byte[] bytes, @AllArguments Object[] args, @Morph Morphing morphing
or
@AllArguments Object[] args, @Morph Morphing morphing
the built-in write function is called and the output is
[Byte Buddy] IGNORE java.io.FileInputStream [null, module java.base, loaded=true]
[Byte Buddy] COMPLETE java.io.FileInputStream [null, module java.base, loaded=true]
[Byte Buddy] DISCOVERY java.io.FileOutputStream [null, module java.base, loaded=true]
[Byte Buddy] TRANSFORM java.io.FileOutputStream [null, module java.base, loaded=true]
[Byte Buddy] COMPLETE java.io.FileOutputStream [null, module java.base, loaded=true]
What's the reason that the delegation does not work after adding @Morph, but bytebuddy still says transform is completed? How to get the correct morphing for this case? Thank you!
Upvotes: 0
Views: 1097
Reputation: 44032
I assume that your retransformation is already failing. Did you try to add a listener to the retransformation process. What does Exception: java.lang.Exception thrown from the UncaughtExceptionHandler in thread "main" mean?
One thing that I noticed is that you do not adjust the module graph. The java.base module will not be able to see your interceptor which is most likely loaded in the unnamed module of the bootstrap loader. Did you try to add assureReadEdgeTo in your transformation where you point to your interceptor class?
Also, please note that Advice
does allow you to skip or even repeat a method execution. Have a look at the javadoc of the enter or exit methods. Typically, when instrumenting bootstrap classes advice tends to be more reliable.
Upvotes: 0