Подтвердить что ты не робот

Странная запись "! *" В LocalVariableTypeTable при компиляции с компилятором eclipse

Скомпилируйте следующий код с помощью компилятора ECJ из пакета Eclipse Mars.2:

import java.util.stream.*;

public class Test {
    String test(Stream<?> s) {
        return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1));
    }
}

Команда компиляции следующая:

$ java -jar org.eclipse.jdt.core_3.11.2.v20160128-0629.jar -8 -g Test.java

После успешной компиляции позвольте проверить полученный файл класса с помощью javap -v -p Test.class. Наиболее интересным является синтетический метод, сгенерированный для (a, t) -> {} lambda:

  private static void lambda$1(java.lang.String, java.lang.Object);
    descriptor: (Ljava/lang/String;Ljava/lang/Object;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0     a   Ljava/lang/String;
            0       1     1     t   Ljava/lang/Object;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       1     1     t   !*

Я был очень удивлен, увидев эту запись !* в LocalVariableTypeTable. Спецификация JVM охватывает атрибут LocalVariableTypeTable и говорит:

Запись constant_pool в этом индексе должна содержать структуру CONSTANT_Utf8_info (§4.4.7), представляющую сигнатуру поля, которая кодирует тип локальной переменной в исходной программе (§4.7.9.1).

§4.7.9.1 определяет грамматику для сигнатур поля, которая, если я правильно понимаю, не охватывает ничего похожего на !*.

Следует также отметить, что ни javac-компилятор, ни старые версии ECJ 3.10.x не генерируют эту запись LocalVariableTypeTable. Является ли !* некоторым нестандартным расширением Eclipse или я что-то пропускаю в спецификации JVM? Означает ли это, что ECJ не соответствует спецификации JVM? Что означает !* и есть ли другие подобные строки, которые могут появляться в атрибуте LocalVariableTypeTable?

4b9b3361

Ответ 1

токен ! используется ecj для кодирования типа захвата в общих подписях. Следовательно, !* означает захват неограниченного подстановочного знака.

Внутри ecj использует два варианта CaptureBinding, один для реализации, что JLS 18.4 вызывает "переменные нового типа", другой для реализации захватывает a la JLS 5.1.10 (в котором используется тот же самый язык "переменных свободного типа" ). Оба создают подпись с помощью !. При более близком рассмотрении в этом примере у нас есть "старомодный" захват: t имеет тип capture#1-of ?, захватывая <T> в Stream<T>.

Проблема заключается в следующем: JVMS 4.7.9.1., похоже, не определяет кодировку для таких переменных нового типа (которые среди других свойств не имеют соответствия в исходном коде и, следовательно, нет имени).

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

Учитывая, что оба компилятора согласны с выводом t на захват, почему один компилятор генерирует LVTT, а другой нет? JVMS 4.7.14 имеет этот

Эта разница важна только для переменных, тип которых использует переменную типа или параметризованный тип.

В соответствии с JLS, захваты представляют собой переменные типа new, поэтому запись LVTT значительна, и в JVMS отсутствует пропуск, чтобы не указывать формат для этого типа.

Последствия

Приведенное выше описывает и объясняет статус-кво, демонстрируя, что никакая спецификация не говорит компилятору вести себя иначе, чем текущий статус. Очевидно, что это не совсем желательная ситуация.

  • Кто-то может обратиться в Oracle, отметив, что Java 8 представляет ситуацию, которая не распространяется на части JVMS. Эта ситуация может стать еще более актуальной, как только локальные переменные станут объектом вывода типа
  • Любой, кто наблюдает за негативным воздействием текущей ситуации, приглашается перезвонить в rfe 494198 (ecj), который в противном случае имеет низкий приоритет.

Update: Между тем кто-то сообщил пример, где требуется обычный Signature (который не может быть оппортунически опущен), чтобы кодировать тип, который не может быть закодирован в соответствии с JVMS. В этом случае также javac создает неуказанный байтовый код. Согласно follow-up никакая переменная не должна иметь такого типа, но я не думаю, что это обсуждение завершено (и, по общему признанию, JLS еще не обеспечивает эту цель).

Обновление 2: Получив совет от автора спецификации, я вижу три части в конечном решении:

(1) Каждая подпись типа в любом атрибуте байткода должна придерживаться грамматики в JVMS 4.7.9.1. Ни ecj !, ни javac <captured wildcard> не является законным.

(2) Составители должны аппроксимировать сигнатуры типов, где нет юридического кодирования, например, используя стирание вместо захвата. Для записи LVTT такое приближение должно рассматриваться как законное.

(3) JLS должен гарантировать, что только типы, кодируемые с использованием JVMS 4.7.9.1, появляются в позициях, где генерация атрибута Signature является обязательной.

Для будущих версий элементов ecj (1) и (2) были разрешено. Я не могу говорить о графиках, когда javac и JLS будут исправлены соответственно.