Встраивание tryCatchBlock приводит к тому, что размер стека текущего кадра не соответствует исключению карты стека.

Я использую ASM для встраивания тела Callee::calcualte(int,int)int, содержащего блок try-catch, в метод Caller::test. Сгенерированный байт-код выглядит нормально, но проверка не удалась из-за исключения:

Exception in thread "main" java.lang.VerifyError: Instruction type does not match stack map
Exception Details:
  Location:
    CallerI.test(II)V @50: iload
  Reason:
    Current frame's stack size doesn't match stackmap.
  Current Frame:
    bci: @50
    flags: { }
    locals: { 'CallerI', integer, integer, integer, integer, 'code/sxu/asm/example/Callee', integer, 'java/lang/ReflectiveOperationException' }
    stack: { }
  Stackmap Frame:
    bci: @50
    flags: { }
    locals: { 'CallerI', integer, integer, integer, integer, 'code/sxu/asm/example/Callee', integer, 'java/lang/Object' }
    stack: { integer }
  Bytecode:
    0000000: 1b1c 602a b400 0e1b 1c3e 3604 3a05 0336
    0000010: 06b8 002e 1230 1232 b200 3812 30b8 003e
    0000020: b600 443a 07a7 000d 3a07 b200 4a12 4cb6
    0000030: 0052 1506 a700 0364 3605 b200 5515 05b6
    0000040: 0058 b200 5512 5ab6 0052 b1            
  Exception Handler Table:
    bci [17, 37] => handler: 40
    bci [17, 37] => handler: 40
  Stackmap Table:
    full_frame(@40,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer},{Object[#101]})
    full_frame(@50,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer,Object[#4]},{Integer})
    full_frame(@55,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer,Object[#4]},{Integer,Integer})

Инструкции байт-кода от метки 14 до метки 52 в сгенерированном коде взяты из тела Callee::calculate, а три инструкции от метки 9 до 12 выводят два аргумента типа int и получателя (вызываемого).

 //The generated bytecode method.
 public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=6, locals=8, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: aload_0       
         4: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         7: iload_1       
         8: iload_2       
         9: istore_3      
        10: istore        4
        12: astore        5
        14: iconst_0      
        15: istore        6
        17: invokestatic  #46                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
        20: ldc           #48                 // class java/lang/String
        22: ldc           #50                 // String say
        24: getstatic     #56                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        27: ldc           #48                 // class java/lang/String
        29: invokestatic  #62                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        32: invokevirtual #68                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        35: astore        7
        37: goto          50
        40: astore        7
        42: getstatic     #74                 // Field java/lang/System.err:Ljava/io/PrintStream;
        45: ldc           #76                 // String I find exception in the catch
        47: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: iload         6
        52: goto          55
        55: isub          
        56: istore        5
        58: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        61: iload         5
        63: invokevirtual #88                 // Method java/io/PrintStream.println:(I)V
        66: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        69: ldc           #90                 // String 1..........
        71: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        74: return        
      Exception table:
         from    to  target type
            17    37    40   Class java/lang/NoSuchMethodException
            17    37    40   Class java/lang/IllegalAccessException
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              14      41     0  this   Lcode/sxu/asm/example/Callee;
              14      41     1     t   I
              14      41     2     p   I
              17      38     3   tmp   I
              42       8     4     e   Ljava/lang/ReflectiveOperationException;
               0      75     0  this   LCallerI;
               0      75     1     a   I
               0      75     2     b   I
              58      17     5     r   I
      LineNumberTable:
        line 16: 0
        line 18: 14
        line 26: 17
        line 27: 37
        line 29: 42
        line 31: 50
        line 18: 58
        line 19: 66
        line 20: 74
      StackMapTable: number_of_entries = 3
           frame_type = 255 /* full_frame */
          offset_delta = 40
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 4
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int, int ]

}

Может ли кто-нибудь дать совет для него? Я был заглушкой этой проблемы в течение трех дней. Здесь должно быть что-то не так с обработкой карты стека, но я понятия не имею, как исправить эту ошибку.

Для вашего удобства я также публикую исходные методы Caller и Callee:

public class Callee {
    public final String _a;
    public final String _b;
    public Callee(String a, String b){
        _a = a;
        _b = b;
    }
  ....
    public int calculate(int t, int p){
        int tmp=0;
        try {
            MethodHandle handle  = MethodHandles.publicLookup().findVirtual(String.class, "say", MethodType.methodType(void.class, String.class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            System.err.println("I find exception in the catch");
        }
        return tmp;
    }
}

public class Caller {
    final Callee _callee;

     public Caller(Callee callee){
    _callee = callee;
}
  ...   
public void test(int a, int b){
    int r = a+b-_callee.calculate(a, b);

    System.out.println(r);
    System.out.println("1..........");
}
}

ОБНОВИТЬ

Оригинальный байт-код Callee::calculate:

 public int calculate(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=5, args_size=3
         0: iconst_0      
         1: istore_3      
         2: invokestatic  #26                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
         5: ldc           #32                 // class java/lang/String
         7: ldc           #34                 // String say
         9: getstatic     #36                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        12: ldc           #32                 // class java/lang/String
        14: invokestatic  #42                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        17: invokevirtual #48                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        20: astore        4
        22: goto          35
        25: astore        4
        27: getstatic     #54                 // Field java/lang/System.err:Ljava/io/PrintStream;
        30: ldc           #60                 // String I find exception in the catch
        32: invokevirtual #62                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        35: iload_3       
        36: ireturn       
      Exception table:
         from    to  target type
             2    22    25   Class java/lang/NoSuchMethodException
             2    22    25   Class java/lang/IllegalAccessException
      LineNumberTable:
        line 18: 0
        line 26: 2
        line 27: 22
        line 29: 27
        line 31: 35
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      37     0  this   Lcode/sxu/asm/example/Callee;
               0      37     1     t   I
               0      37     2     p   I
               2      35     3   tmp   I
              27       8     4     e   Ljava/lang/ReflectiveOperationException;
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class code/sxu/asm/example/Callee, int, int, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 9 /* same */

Мой код также помещается в репозиторий Github, и класс MainInliner можно запустить сразу после добавления библиотеки ASM в путь к классам.

Основная процедура в проекте — MethodCallInliner: :visitMethodInsn(..), в котором создается новый InliningAdapter и используется для посещения Callee::calculate() тела.

=========================================

Обновление для LocalVariableTable:

Согласно объяснению @Holger и некоторым вариантам:

  • Избегайте объявления каких-либо формальных переменных и позвольте ASM сделать все выводы.

Чтобы отключить объявление формальной переменной, visitLocalVariable переопределяется, но пустая реализация как в InliningAdapter, так и в MethodCallInliner, LocalVariableTable исчезла в сгенерированном коде, но проверка по-прежнему завершается с той же ошибкой. я тоже пробовал

 ClassReader.accept(, ClassReader.EXPAND_FRAME|ClassReader.SKIP_DEBUG)

Но результат такой же, как и пустое переопределение visitLocalVariable

  • Объединить локальную переменную Callee со сгенерированной таблицей.

Полные сгенерированные байт-коды:

  public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=6, locals=8, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: aload_0       
         4: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         7: iload_1       
         8: iload_2       
         9: istore_3      
        10: istore        4
        12: astore        5
        14: iconst_0      
        15: istore        6
        17: invokestatic  #46                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
        20: ldc           #48                 // class java/lang/String
        22: ldc           #50                 // String say
        24: getstatic     #56                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        27: ldc           #48                 // class java/lang/String
        29: invokestatic  #62                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        32: invokevirtual #68                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        35: astore        7
        37: goto          50
        40: astore        7
        42: getstatic     #74                 // Field java/lang/System.err:Ljava/io/PrintStream;
        45: ldc           #76                 // String I find exception in the catch
        47: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: iload         6
        52: goto          55
        55: isub          
        56: istore_3      
        57: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        60: iload_3       
        61: invokevirtual #88                 // Method java/io/PrintStream.println:(I)V
        64: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        67: ldc           #90                 // String 1..........
        69: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        72: return        
      Exception table:
         from    to  target type
            17    37    40   Class java/lang/NoSuchMethodException
            17    37    40   Class java/lang/IllegalAccessException
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              14      41     5  this   Lcode/sxu/asm/example/Callee;
              14      41     4     t   I
              14      41     3     p   I
              17      38     6   tmp   I
              42       8     7     e   Ljava/lang/ReflectiveOperationException;
               0      73     0  this   LCallerI;
               0      73     1     a   I
               0      73     2     b   I
              57      16     3     r   I
      LineNumberTable:
        line 20: 0
        ..
        line 24: 72
      StackMapTable: number_of_entries = 3
           frame_type = 255 /* full_frame */
          offset_delta = 40
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 4
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int, int ]

}

и исходные LocalVariableTables:

Callee: LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      37     0  this   Lcode/sxu/asm/example/Callee;
               0      37     1     t   I
               0      37     2     p   I
               2      35     3   tmp   I
              27       8     4     e   Ljava/lang/ReflectiveOperationException;
Caller: LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      30     0  this   Lcode/sxu/asm/example/Caller;
               0      30     1     a   I
               0      30     2     b   I
              14      16     3     r   I

Слияние выглядит нормально (я думаю, что нет необходимости избегать конфликта имен переменных, если эти имена находятся в разных регионах, например, два символа this в разных регионах). Но проверка по-прежнему завершается ошибкой @50::iload с тем же сообщением.


person shijie xu    schedule 05.05.2015    source источник
comment
Спасибо @Holger. visitLocalVariable может вызываться косвенно, потому что два основных адаптера расширяются от LocalVariablesSorter при встраивании. Меня смущает переменная №7, потому что эта переменная (№3) создается в исходном коде. Встраивание в мой код только перенумеровывает эти локальные переменные. Я также отправил свой код на Github (см. внизу моего поста). Спасибо   -  person shijie xu    schedule 05.05.2015
comment
Вы пытались попросить ASM пересчитать карту стека для всего метода?   -  person Antimony    schedule 06.05.2015
comment
Спасибо @Antimony. Я уже сделал это. ASM пересчитывает карту стека, инициализируя ClassWriter с параметрами ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS и вызывая visitMax в конце тела метода.   -  person shijie xu    schedule 06.05.2015
comment
@Holger, спасибо за четкое объяснение. Я пробовал варианты: избегайте любого формального объявления переменных и объединяйте LocalVariableTable вызывающего и вызываемого вместе. Проверка по-прежнему завершается с ошибкой с тем же сообщением (см. раздел «Обновление для LocalVariableTable» в моем первом сообщении). Есть еще предложения?   -  person shijie xu    schedule 06.05.2015
comment
Хорошо, забудьте о локальной переменной, я ошибся. Слияние устарело, но не проблема. Это всего лишь несоответствие глубины стека операндов. Я вставил это в ответ ...   -  person Holger    schedule 07.05.2015


Ответы (1)


Перед вызовом calculate в стеке есть одно значение int, которое будет использоваться после вызова. Вызовы методов, которые завершаются нормально, потребляют только соответствующие значения параметров и оставляют нетронутыми все остальные значения стека операндов, независимо от того, что происходит внутри вызываемого метода. Если метод не void, возвращаемое значение впоследствии будет помещено в стек.

Все меняется, когда вы встраиваете код метода. Затем код может повлиять на стек операндов, помимо использования параметров и отправки одного возвращаемого значения. В вашем случае есть обработчик исключений, который возобновит нормальное выполнение. Но, как обсуждалось в этом ответе, обработчики исключений начинаются со стека операндов, содержащего только одно значение — обнаруженное исключение. Все значения, помещенные в стек до возникновения исключения, будут сброшены. После встраивания кода метода в вызывающую программу это также влияет на стек операндов вызывающей стороны.

Итак, в вашем случае два пути кода сливаются в конце встроенного кода, один для случая нормального завершения, когда значение int в стеке будет сохранено, и путь обработчика исключений, где значение было удалено. Это несоответствие вызывает ошибку VerifyError.

Нет простого решения. Вы не можете заставить обработчики исключений сохранять значение, поэтому вам нужно переписать код, чтобы он не полагался на то, что переданное значение будет сохранено, что делает вашу первоначальную идею встраивания, просто вставляя инструкции, неработающими. Вы должны знать, что возможно даже обратное: когда метод сталкивается с инструкцией возврата, не имеет значения, сколько дополнительных неиспользованных значений болтается в стеке, так как кадр стека будет уничтожен при возврате к вызывающей стороне. Как следствие, наивно встроенный код может оставить в стеке дополнительные значения, которые вызовут ошибки, если у вас есть переходы по методу или если метод имеет более одной инструкции возврата.

person Holger    schedule 07.05.2015
comment
Спасибо @Holger и указал на другую возможность. Ты прав. Что я сейчас пытаюсь сделать, так это то, что перед встраиванием Callee все стекированные переменные из стека извлекаются, а их идентификаторы переменных, а также тип, затем помещаются в отдельный стек по порядку. Разделенный стек затем используется для перестроения стеков при выходе Callee. Здесь я использую AnalyzerAdapter для вычисления глубины стека в начале Callee::calculate. Это должно работать, но производительность может быть не очень хорошей. - person shijie xu; 07.05.2015