Search code examples
javabytecodeinstrumentationbyte-buddy

Intercepting method calls to my Java and Groovy code using Byte Buddy: Strange java.lang.VerifyError


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.VerifyErrors 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?


Solution

  • 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.