I try to intercept calls to methods of classes from a mixed Java (8) and Groovy (2.4.5) project using Byte Buddy 0.7.1.
The idea is to create something like a little "generic logging flight recorder" for method calls and their arguments for classes in a specific package like foo
.
I use a Byte Buddy AgentBuilder
and my custom LogInterceptor
to do this at application startup:
static {
final Instrumentation inst = ByteBuddyAgent.install();
new AgentBuilder.Default()
.type(ElementMatchers.nameContainsIgnoreCase("foo")) // simplified
.transform((builder, typeDescription) ->
builder.method(ElementMatchers.any())
.intercept(MethodDelegation.to(LogInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)))
.installOn(inst);
}
public static class LogInterceptor {
@RuntimeType
public static void log(@Origin Method method, @AllArguments Object[] arg) throws Exception {
// flightRecorder.log(...);
}
}
The method interception works fine for all Java classes. And it works fine for all Groovy classes with a @CompileStatic
annotation.
But it fails for classic (dynamic) Groovy classes with strange java.lang.VerifyError
s like
java.lang.VerifyError: (class: foo/MyInterceptedClass$barMethod, method: <clinit> signature: ()V) Illegal type in constant pool
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.getConstructor(Class.java:1825)
at org.codehaus.groovy.reflection.ClassLoaderForClassArtifacts.defineClassAndGetConstructor(ClassLoaderForClassArtifacts.java:83)
at org.codehaus.groovy.runtime.callsite.CallSiteGenerator.compileStaticMethod(CallSiteGenerator.java:246)
at org.codehaus.groovy.reflection.CachedMethod.createStaticMetaMethodSite(CachedMethod.java:288)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.createStaticMetaMethodSite(StaticMetaMethodSite.java:114)
at groovy.lang.MetaClassImpl.createStaticSite(MetaClassImpl.java:3385)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:77)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:162)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
...
What's going on here? Does Byte Buddy support Groovy method interception?
For a reason unknown to me, Groovy generates classes of a class level 1.4 where certain byte code constructs that Byte Buddy generates are not legal. This generates a VerifyError
. This is of course not a very constructive error message when working with Byte Buddy, which is why the class validator that Byte Buddy applies does now check for illegal use of modern concepts in Java byte code 1.4.
To overcome this limitation, Byte Buddy 0.7.2 (released today) includes a ClassVisitorWrapper
which fixes the modern byte code to be represented by compatible, legacy intstructions when registering the added TypeConstantAdjustment
. This adjustment is not perfect as, in case a class is missing, it will cause an ClassNotFoundException
where the JLS normally requires a NoClassDefFoundError
. If you can live with this limitation, it is save to use. The adjustment discovers by itself if it is required (Java 4 or older), so you can simply add it if you require it.