ASM: visitLabel генерирует слишком много меток и nop инструкций - PullRequest
0 голосов
/ 09 ноября 2018

Документация ASM гласит, что метка представляет базовый блок и является узлом в графе управления. Поэтому я проверяю метод visitLabel на этом простом примере:

public static void main(String[] args) {
    int x = 3, y = 4;
    if (x < y) {
        x++;
    }
}

Для метода visitLabel я применяю его с собственным API: setID(int id), где идентификатор является инкрементным. В этом примере CFG должен иметь 3 узла: по одному в начале и по одному на каждую ветвь оператора if. Поэтому я ожидаю, что setID будет вызван в 3 местах. Однако он вызывается 5 раз, и существует множество nop инструкций. Кто-нибудь может объяснить мне, почему?

Вот инструментированный байт-код для вышеуказанной программы.

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: invokestatic  #13                 // Method setId:(I)V
       4: iconst_3
       5: istore_1
       6: iconst_3
       7: invokestatic  #13                 // Method setId:(I)V
      10: iconst_4
      11: istore_2
      12: iconst_4
      13: invokestatic  #13                 // Method setId:(I)V
      16: iload_1
      17: iload_2
      18: if_icmpge     28
      21: iconst_5
      22: invokestatic  #13                 // Method setId:(I)V
      25: iinc          1, 1
      28: bipush        6
      30: invokestatic  #13                 // Method setId:(I)V
      33: return
      34: nop
      35: nop
      36: nop
      37: nop
      38: athrow

Я не понимаю, почему перед каждой инструкцией istore стоит label. Нет ветвления, чтобы сделать его новым узлом в CFG.

1 Ответ

0 голосов
/ 12 ноября 2018

Основной целью Label является обозначение позиции в последовательности байт-кода. Поскольку это необходимо для целей ветвления, вы можете использовать их для идентификации основных блоков. Но вы должны знать, что они также используются для сообщения номеров строк, когда присутствует атрибут LineNumberTable, и для сообщения областей локальной переменной, когда атрибут LocalVariableTable присутствует, а также, для более новых файлов классов, их аннотации типов, записанные в атрибуте RuntimeVisibleTypeAnnotations. Кроме того, метки могут отмечать защищенную область обработчика исключений. Для кода, сгенерированного из исходного кода Java, эта защищенная область соответствует блоку try, поэтому он является базовым блоком, но его не нужно хранить для другого байт-кода.

См

Поскольку область действия локальных переменных может охватывать последнюю инструкцию return, после этой последней инструкции возможно встретить метки, что и происходит в вашем случае. Вы вводите bipush 7, invokestatic #13 после инструкции return, что приводит к недоступности кода.

Очевидно, вы также используете опции COMPUTE_FRAMES, чтобы позволить ASM пересчитать кадры стековой карты с нуля, но невозможно рассчитать кадры для недоступного кода из-за неизвестного начального состояния стека. ASM решает эту проблему, заменяя недоступный код инструкциями nop, за которыми следует один оператор athrow. Для этой последовательности можно указать допустимый начальный кадр стека, и это не повлияет на выполнение (так как код недоступен).

Как видите, четыре nop инструкции плюс одна athrow инструкция охватывают пять байтов, что соответствует размеру замененной последовательности bipush 7, invokestatic #13.

Вы можете избавиться от большинства указанных меток, указав ClassReader.SKIP_DEBUG для его accept метод . Затем вы получите только одну сообщаемую метку для вашего примера, цель перехода, связанную с оператором if. Но вы должны обработать visitJumpInsn, чтобы определить начало условного кода.

Итак, чтобы идентифицировать все основные блоки, вы должны обработать все инструкции ветвления, т. Е. Через visitJumpInsn, visitLookupSwitchInsn и visitTableSwitchInsn, а также все конечные инструкции, т. Е. athrow и все варианты return. Далее вам необходимо обработать все visitTryCatchBlock звонки. Если вам нужно идентифицировать потенциальные цели инструкций ветвления за один проход, я бы использовал visitFrame вместо меток, поскольку фреймы обязательны для всех целей ветвления для версии файла класса 51 (Java 7) или выше.

Кстати, когда все, что вы вводите, это последовательность загрузки константы и вызова статического метода (в доступных местах), я бы использовал COMPUTE_MAXS вместо COMPUTE_FRAMES, так как дорогой пересчет не необходимо, когда общая структура кода не изменяется.

...