Требует ли JLS встраивания конечных строковых констант?

Я столкнулся с проблемой при манипулировании некоторым байт-кодом, когда некоторая константа final String не была встроена компилятором java (Java 8), см. Пример ниже:

public class MyTest
{
  private static final String ENABLED  = "Y";
  private static final String DISABLED = "N";

  private static boolean isEnabled(String key) {
      return key.equals("A");
  }

  private static String getString(String key, String value) {
      return key + value;
  }

  public static void main(String[] args) throws Exception {
    String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag);

    String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag2);
  }
}

Результирующий байт-код с javac (1.8.0_101)

public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #8                  // String F
         2: ldc           #2                  // String A
         4: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
         7: ifeq          16
        10: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        13: goto          19
        16: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        19: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        22: astore_1
        23: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc           #8                  // String F
        32: ldc           #2                  // String A
        34: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
        37: ifeq          46
        40: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        43: goto          49
        46: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        49: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        52: astore_2
        53: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        56: aload_2
        57: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: return

Вы можете видеть, что во второй раз, когда осуществляется доступ к полям ENABLED и DISABLED, компилятор не встраивал их значения (используя ldc), а вместо этого использовал getstatic для прямого доступа к полю. Тестирование с другими компиляторами (Java 7, Eclipse) не вызывало такого же поведения, и константы всегда были встроены.

Можно ли это считать ошибкой компилятора или разрешено не постоянно вставлять строковые константы в соответствии с JLS?


person T. Neidhart    schedule 08.09.2016    source источник
comment
Определите «встроенный». JLS требует объединения. Ты об этом говоришь?   -  person user207421    schedule 08.09.2016
comment
Встраивание означает замену инструкции getstatic инструкцией, которая напрямую помещает значение константы в стек, например ldc в случае строк или iconst_X / bipush / sipush / ... в случае целочисленных примитивов, ...   -  person T. Neidhart    schedule 08.09.2016
comment
JLS не упоминает ни никаких инструкций байт-кода, ни концепцию постоянного пула - он описывает язык.   -  person piet.t    schedule 08.09.2016
comment
Конечно, я хотел привести конкретный пример, основанный на реальных инструкциях. Я понимаю, что JLS не говорит об инструкциях уровня байтового кода в этом вопросе.   -  person T. Neidhart    schedule 08.09.2016
comment
@ piet.t: я удалил тег байтового кода, поскольку спецификация языка действительно не касается байтового кода. Однако поведение определено без двусмысленности, что позволяет ответить на вопрос.   -  person Holger    schedule 08.09.2016
comment
Хорошо подмечено. Это действительно ошибка, и она исправлена ​​с помощью исправления для JDK-8066871 который был интегрирован в JDK 9 и обратно перенесен в JDK 1.8.0_102, то есть 1.8.0_101 плюс набор исправлений. Обратите внимание, что JDK-8066871 имеет разные симптомы, но одно и то же исправление исправляет эту ошибку, а также описанную здесь.   -  person Stuart Marks    schedule 08.09.2016
comment
Я только что отправил отчет об ошибке (JI-9043578), так как не нашел соответствующей записи в базе данных об ошибках. Думаю, тогда его можно закрыть.   -  person T. Neidhart    schedule 08.09.2016
comment
@ T.Neidhart Спасибо за сообщение об ошибке! Я связал их вместе как дубликаты. Я думаю, что нам, вероятно, понадобится регрессионный тест в этой области, поскольку эта ошибка присутствовала в первом выпуске JDK 8 и после двухлетних обновлений.   -  person Stuart Marks    schedule 13.09.2016


Ответы (1)


Да, "встраиваемое" поведение предусмотрено спецификацией:

13.1. Форма двоичного файла

  1. Ссылка на поле, которое является постоянной переменной (§4.12.4), должна быть разрешена во время компиляции до значения V, обозначенного инициализатором постоянной переменной.

    Если таким полем является static, то в коде двоичного файла не должно быть ссылки на поле, включая класс или интерфейс, в котором объявлено поле. Такое поле всегда должно выглядеть инициализированным (§12.4.2); начальное значение по умолчанию для поля (если оно отличается от V) никогда не должно соблюдаться.

    Если такое поле не static, то в коде двоичного файла не должно быть ссылки на поле, за исключением класса, содержащего это поле. (Это будет класс, а не интерфейс, поскольку интерфейс имеет только static полей.) Класс должен иметь код для установки значения поля в V во время создания экземпляра (§12.5).

Обратите внимание, как это точно соответствует вашему сценарию: «Если такое поле static, то в коде двоичного файла не должно быть ссылки на поле, включая класс или интерфейс, в котором объявлено поле ».

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


В качестве дополнения отправной точкой для поиска этой информации было:

4.12.4. final переменные

Постоянная переменная - это final переменная примитивного типа или типа String, которая инициализируется константным выражением (§15.28). Независимо от того, является ли переменная постоянной или нет, может иметь значение инициализация класса (§12.4.1), двоичная совместимость (§13.1, §13.4.9) и определенное присвоение (§16 (Определенное назначение)).

person Holger    schedule 08.09.2016
comment
Большое спасибо, это именно то, что я искал, так что это подтверждает, что наблюдаемое поведение действительно является ошибкой компилятора. - person T. Neidhart; 08.09.2016
comment
Я проверил все эти ссылки, кроме бинарной совместимости :) - person Marko Topolnik; 08.09.2016