Improve around advice to retransform loaded class
eaglemartin opened this issue · 4 comments
@raphw firstly thanks to your great job.
I'm working on a javaagent based AOP framework which supports around advice via Byebuddy for both unloaded and loaded class.
Yes, MethodDelegation is a good choice. But after some research on the generated code, I find MethodDelegation could not retransform loaded classes, because Bytebuddy adds package-level auxiliary methods to allow auxiliary type accessing instrumented method (javac uses this approach to support Java enclosing and inner class's private member access), but this changes class file structure.
@Advice.Enter/Exit could retransform loaded classes, but advice code is just added to the method at the beginning and end. So the code does not have a chance to get a method reference, such as Method or MethodHandle, to instrumented method. Also it is not a good idea to get a method reference at runtime which introduces extra performance lost, and could not be tuned by JIT.
So back to MethodDelegation approach, we have two questions,
- Is it possible to access instrumented class's private auxiliary method in auxiliary type?
Since lambda inner class can access private method, we can create funtional interface instance to access private instrumented method, and remove package-level auxiliary method.
JDK lambda is based on VM anonymous class (JDK 8 -14) and hidden class (JDK 15+) that might access nestmate's private member which is stardardized in JEP 181 https://openjdk.org/jeps/181 and JEP 371 https://openjdk.org/jeps/371.
- Is it possible to add or remove method to loaded class at runtime?
I read JVMTI source code about java.lang.instrument.Instrumentation, and find at least on JDK 8+, it is possible to add ro remove private final or private static method when redefining or retransforming classes.
For more details, please refer to jvmtiError VM_RedefineClasses::compare_and_normalize_class_versions method https://github.com/openjdk/jdk8u/blob/master/hotspot/src/share/vm/prims/jvmtiRedefineClasses.cpp
So with some modern Java features, on JDK 8 or above maybe we can support around advice like below pseudo-code,
public class InstrumentedClass {
// 1.generate private final|static auxiliary method to contain origin source code
// JVMTI implementation of Instrumentation::retransformClasses and redefineClasses allows adding or removing
// private final|static methods when retransforming loaded class.
private final Object instrumentedMethod$origin$0(Object... args) {
// origin source code
}
private Object instrumentedMethod(Object... args) {
// 2.create functional interface instance via MethodHandle BSM and LambdaMetafactory
// LambdaMetafactory will generate VM anonymous class (JDK 8-14) or hidden class (JDK 15+) which can access private fnal|static instrumentedMethod$origin$0 method
Callable<Object> callable= () -> {
return instrumentedMethod$origin$1(args);
}
// 3.inline or call around advice logic with callable
}
}
public class AroundAdvice {
@Advice.OnMethodAround
public static Object advcie(@Adivce.SuperCall Callable<Object> callable) {
try {
// enter method code
return callable.call();
} finally {
// exit method code
}
}
}
Can I reuse existing bytebuddy functionalities to implement it or can bytebuddy provide built-in support?
You can emulate this in different ways. This question was recently asked, have a look at my answer: #1664
My mistake did not introduce requirement clearly.
In the AOP framework mentioned, I want to encapsulate implementation details about bytebuddy, and provide interfaces similar to org.aopalliance.intercept.MethodInterceptor and org.aopalliance.intercept.MethodInvocation which requires reference to instrumented method.
With @advice(repeatOn), I'm afraid I could not get this reference.
Requiring (non-annotation) types causes class loader issues, unfortunately. I do however not see how this sets any functional restrictions?
To follow the JVMTI RedefineClasses spec, Openjdk 13+ requires extra -XX:AllowRedefinitionToAddOrDeleteMethods option to allows private final or private static methods (https://bugs.openjdk.org/browse/JDK-8192936), and this option might be removed in future JDK release. So based on above non-standard JVMTI RI is not reasonable.
I will adjust my design to use @Advice.OnMethodEnter and @Advice.OnMethodExit.
Thanks for your time.