Перехват вызовов методов для моего кода Java и Groovy с использованием Byte Buddy: Strange java.lang.VerifyError

Я пытаюсь перехватить вызовы методов классов из смешанного проекта Java (8) и Groovy (2.4.5), используя Byte Buddy 0.7.1.

Идея состоит в том, чтобы создать что-то вроде небольшого «общего регистратора полетов» для вызовов методов и их аргументов для классов в конкретном пакете, таком как foo.

Я использую Byte Buddy AgentBuilder и свой собственный LogInterceptor, чтобы сделать это при запуске приложения:

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(...);
  }
}

Перехват метода отлично работает для всех классов Java. И он отлично работает для всех классов Groovy с аннотацией @CompileStatic.

Но это не работает для классических (динамических) классов Groovy со странными java.lang.VerifyError, такими как

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

Что тут происходит? Поддерживает ли Byte Buddy перехват методов Groovy?


person Peti    schedule 26.11.2015    source источник


Ответы (1)


По неизвестной мне причине Groovy генерирует классы уровня класса 1.4, где определенные конструкции байтового кода, которые генерирует Byte Buddy, недопустимы. Это генерирует файл VerifyError. Это, конечно, не очень конструктивное сообщение об ошибке при работе с Byte Buddy, поэтому валидатор класса, который применяет Byte Buddy, теперь проверяет недопустимое использование современных концепций в байт-коде Java 1.4.

Чтобы обойти это ограничение, Byte Buddy 0.7.2 (выпущенный сегодня) включает ClassVisitorWrapper, который исправляет современный байтовый код для представления совместимыми устаревшими инструкциями при регистрации добавленного TypeConstantAdjustment. Эта корректировка не идеальна, так как в случае отсутствия класса она вызовет ClassNotFoundException, тогда как JLS обычно требует NoClassDefFoundError. Если вы можете жить с этим ограничением, его можно использовать. Коррекция сама определяет, требуется ли она (Java 4 или старше), поэтому вы можете просто добавить ее, если она вам нужна.

person Rafael Winterhalter    schedule 26.11.2015
comment
Интересно, что Groovy 2.4.5 по-прежнему создает байт-код с файлом класса версии 48 (Java 1.4), поскольку сам требует Java 6+ (Groovy 2.4.x). В Java 6 появилась версия файла класса 50. Это дополнительная работа для библиотек манипулирования байтовым кодом, таких как Byte Buddy! ;-) - person Peti; 26.11.2015
comment
Я не знаю о версии 48, но гораздо проще создавать файлы классов версии 49 по сравнению с 50+, потому что StackMapTables не требуются. На практике JVM примет версию 50 без карт стека, но это не гарантируется стандартом. 49 также является последней версией, поддерживающей jsr/ret. - person Antimony; 26.11.2015