VerifyError: неинициализированный объект существует в обратной ветке/спецификации JVM 4.10.2.4

Версия JVM Spec 4.10.2.4 7, последний абзац, говорит

Действительная последовательность инструкций не должна иметь неинициализированный объект в стеке операндов или в локальной переменной в цели обратной ветви, если специальный тип неинициализированного объекта объединен со специальным типом, отличным от него самого.

Вот пример, отвергнутый верификатором — подозреваю, что его следует принять:

public scala.Tuple2<scala.runtime.Null$, scala.runtime.Null$> apply(boolean);
  flags: ACC_PUBLIC
  Code:
    stack=4, locals=2, args_size=2
       0: new           #12                 // class scala/Tuple2
       3: dup           
       4: aconst_null   
       5: iload_1       
       6: ifne          5
       9: aconst_null   
      10: invokespecial #16                 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
      13: areturn       
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
             0      14     0  this   LC;
             0      14     1     x   Z
    StackMapTable: number_of_entries = 1
         frame_type = 255 /* full_frame */
        offset_delta = 5
        locals = [ class C, int ]
        stack = [ uninitialized 0, uninitialized 0, null ]

Сообщение об ошибке жалуется на скачок назад ifne 5

java.lang.VerifyError: Uninitialized object exists on backward branch 5
Exception Details:
  Location:
    C.apply(Z)Lscala/Tuple2; @6: ifne

В стеке цели перехода действительно есть неинициализированный объект; однако мне кажется, что «особый тип неинициализированного объекта» объединен с самим собой, как того требует спецификация.

Я думаю, что есть только один кадр карты стека, поэтому его нельзя объединить ни с чем другим.

Интересно, что в JVM Spec версии 8.

Однако Verifier в виртуальной машине Java 8 по-прежнему отклоняет пример.

Я неправильно прочитал спецификацию JVM или пример действительно не прошел проверку? Пробовал версии 1.7.0_60-b19 и 1.8.0_05-b13.


Общие исследования

Проблема проявляется в Scala (отчет об ошибке). Чтобы воспроизвести, возьмите scala 2.11.1, убедитесь, что вы используете JVM >= 1.7, и выполните следующее (обязательно передайте -target:jvm-1.7 в scala):

localhost:sandbox luc$ scala -target:jvm-1.7
Welcome to Scala version 2.11.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class C {
     |   def apply(x: Boolean) = new Tuple2(null, {
     |     while (x) { }
     |     null
     |   })
     | }
defined class C

scala> new C
java.lang.VerifyError: Uninitialized object exists on backward branch 5
Exception Details:
  Location:
    C.apply(Z)Lscala/Tuple2; @6: ifne
  Reason:
    Error exists in the bytecode
  Bytecode:
    0000000: bb00 0959 011b 9aff ff01 b700 0db0
  Stackmap Table:
    full_frame(@5,{Object[#2],Integer},{Uninitialized[#0],Uninitialized[#0],Null})

  ... 32 elided

Как упоминалось выше - отчет об ошибке JDK здесь, я надеюсь получить там ответ.

Ошибка JDK была исправлена ​​в JDK 8u25.


person Lukas Rytz    schedule 05.06.2014    source источник
comment
С другой стороны, этот байт-код кажется довольно бесполезным. Это бесконечные циклы в случае, если local1 равен 0, в противном случае он игнорируется.   -  person Antimony    schedule 05.06.2014
comment
Какую версию JVM вы используете? Верификатор в hg .openjdk.java.net/jdk7/jdk7/jdk/file/tip/src/share/native/ должен нормально принять ваш код (он даже не включает сообщение об ошибке, которое вы разместили в исходном коде) .   -  person Antimony    schedule 05.06.2014
comment
Я использую Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55 - попробую другие.   -  person Lukas Rytz    schedule 05.06.2014
comment
Я думаю, что вы правильно прочитали спецификацию, специальный тип не изменился до прыжка. Этот код (и байт-код) работает для меня на Arch с Scala version 2.11.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0)   -  person ggovan    schedule 05.06.2014
comment
Я только что попробовал Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_05 и получил тот же сбой. @ggovan, ты запускал интерпретатор scala, используя -target:jvm-1.7?   -  person Lukas Rytz    schedule 05.06.2014
comment
Пропустил этот момент, так что -target:jvm-1.7 тоже ломает мой.   -  person ggovan    schedule 05.06.2014
comment
Возможно, это ошибка в новом верификаторе.   -  person Antimony    schedule 06.06.2014
comment
P.S. Еще один шаг, который вы могли бы предпринять, чтобы сузить круг вопросов: генерирует ли Scala разные байт-коды для целей 1.7 и 1.8? Работает ли код 1.7, если вы вручную ухудшите classfile до 1.6 и удалите атрибут stackmap?   -  person Antimony    schedule 06.06.2014
comment
Да, я это проверил. Если вы не передадите -target:jvm-1.7 в scala, то он создаст файл класса с версией 50 (версия для JDK 1.6) вместо 51. В этом случае верификатор не обеспечивает правильность кадров карты стека (см. спецификацию JVM 4.10 .1), ошибка не срабатывает.   -  person Lukas Rytz    schedule 06.06.2014
comment
Вы смотрели не ту главу. Спецификация JVM §4.10.2 описывает верификатор вывода типа, который применяется только к версии ‹=49 (или ==50, если проверка StackMapTable не удалась). Для файлов класса ›50 StackMapTable являются обязательными, и возврат к верификатору вывода типа не разрешен. Следовательно, только JVM Spec § Применяется 4.10.1 (см. первые два предложения). Обратите внимание, что §4.9.2 также содержит ограничение на обратную ветвь, и оно также было удалено для Java 8. Но кажется, что §4.10.1 никогда не имел этого ограничения.   -  person Holger    schedule 27.06.2014
comment
Привет Хольгер, спасибо за ваш комментарий, это очень хороший момент. Теперь я еще больше запутался :) В спецификации для версии 6 Структурные ограничения говорят: никогда не должно быть неинициализированного экземпляра класса в стеке операндов [..] при любом переходе назад. Это ограничение было немного смягчено в версии 7 и удалено в версии 8. ОДНАКО, JDK 6 не жалуется на файл класса, который имеет такую ​​обратную ветвь, в то время как JDK 7 и 8 делают это.   -  person Lukas Rytz    schedule 29.06.2014
comment
Нет ничего необычного в наличии различий между спецификацией и реализацией. Таким образом, кажется, что верификатор вывода типов принял такую ​​обратную ветвь, и в этом главная цель нацеливания на Java 7, а не на более старые версии: с версией файла класса 50 (также известной как Java 6) верификатор вернется к алгоритму вывода типа, если проверка карты стека терпит неудачу. Хотя это было предназначено для обхода ошибок в инструментах создания байт-кода, оно также помогло не заметить ошибки в новом верификаторе…   -  person Holger    schedule 30.06.2014
comment
Эта ошибка, 8046233, указана как исправленная в исправлены ошибки 8u25.   -  person David Conrad    schedule 15.10.2014
comment
Почему ты удалил свой ответ?   -  person Holger    schedule 02.10.2018
comment
Я обновил вопрос, но, возможно, будет более понятно сохранить ответ.   -  person Lukas Rytz    schedule 03.10.2018


Ответы (1)


ошибка JDK была исправлена ​​в JDK 8u25

person Lukas Rytz    schedule 16.10.2014