Является ли «финал» окончательным во время выполнения? - PullRequest
21 голосов
/ 30 июля 2011

Я играл с ASM , и я считаю, что мне удалось добавить модификатор final в поле экземпляра класса; однако затем я приступил к созданию экземпляра указанного класса и вызвал для него установщик, который успешно изменил значение поля now-final. Я делаю что-то не так с моими изменениями байт-кода, или в конечном итоге применяется только компилятор Java?

Обновление: (31 июля) Вот код для вас. Основные части

  1. простой POJO с private int x и private final int y,
  2. MakeFieldsFinalClassAdapter, который делает каждое поле, которое он посещает, окончательным, если это не так,
  3. и AddSetYMethodVisitor, который заставляет метод setX () POJO также устанавливать y в то же значение, которое он установил для x.

Другими словами, мы начинаем с класса с одним финальным (x) и одним не финальным (y) полем. Мы делаем х финал. Мы делаем setX () set y в дополнение к настройке x. Мы бегаем. И x, и y устанавливаются без ошибок. Код находится на github . Вы можете клонировать его с помощью:

git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/asm-playground

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

Еще одно обновление: (1 августа) Протестировано с 1.6.0_26-b03 и 1.7.0-b147 с одинаковыми результатами. То есть JVM успешно изменяет конечные поля во время выполнения.

Окончательное (?) Обновление: (19 сентября) Я удаляю полный источник из этого поста, потому что он был довольно длинным, но все еще доступен на github (см. Выше).

Полагаю, я убедительно доказал , что JDK7 JVM нарушает спецификацию . (См. Отрывок в ответе Стивена .) После использования ASM для изменения байт-кода, как описано ранее, я записал его обратно в файл класса. Используя отличный JD-GUI , этот файл класса декомпилируется в следующий код:

package rds.asm;

import java.io.PrintStream;

public class TestPojo
{
  private final int x;
  private final int y;

  public TestPojo(int x)
  {
    this.x = x;
    this.y = 1;
  }

  public int getX() {
    return this.x;
  }

  public void setX(int x) {
    System.out.println("Inside setX()");
    this.x = x; this.y = x;
  }

  public String toString()
  {
    return "TestPojo{x=" +
      this.x +
      ", y=" + this.y +
      '}';
  }

  public static void main(String[] args) {
    TestPojo pojo = new TestPojo(10);
    System.out.println(pojo);
    pojo.setX(42);
    System.out.println(pojo);
  }
}

Краткий взгляд на это должен сказать, что класс никогда не скомпилируется из-за переназначения конечного поля, и, тем не менее, запуск этого класса в обычном ванильном JDK 6 или 7 выглядит следующим образом:

$ java rds.asm.TestPojo
TestPojo{x=10, y=1}
Inside setX()
TestPojo{x=42, y=42}
  1. Кто-нибудь еще имеет информацию, прежде чем я сообщу об ошибке на этом?
  2. Кто-нибудь может подтвердить, является ли это ошибкой в ​​JDK 6 или только в 7?

Ответы [ 3 ]

20 голосов
/ 30 июля 2011

Является ли «final» окончательным во время выполнения?

Не в том смысле, в каком вы имеете в виду.

AFAIK, семантика модификатора final обеспечивается толькокомпилятор байт-кода.

Не существует специальных байт-кодов для инициализации полей final, и верификатор байт-кода (по-видимому) также не проверяет «недопустимые» назначения.

Однако компилятор JIT может обрабатыватьМодификатор final как намек на то, что вещи не нуждаются в пополнении.Таким образом, если ваши байт-коды изменяют переменную, помеченную как final, вы можете вызвать непредсказуемое поведение.(И то же самое может произойти, если вы используете отражение для изменения переменной final. Спецификация ясно говорит об этом ...)

И, конечно, вы можете изменить поле final, используя отражение.


ОБНОВЛЕНИЕ

Я взглянул на спецификацию Java 7 JVM, и она частично противоречит тому, что я сказал выше.В частности, описание кода операции PutField гласит:

"Связывание исключений ... В противном случае, если поле является окончательным, оно должно быть объявлено в текущем классе, и должна произойти инструкцияв методе инициализации экземпляра (<init>) текущего класса. В противном случае выдается IllegalAccessError. ".

Итак, в то время как вы можете (теоретически) назначитьfinal поле несколько раз в конструкторе объекта, верификатор байт-кода должен предотвращать любые попытки загрузить метод, который содержит байт-код, который присваивается final.Что ... когда ты думаешь о песочницах безопасности Java ... это хорошо.

5 голосов
/ 30 июля 2011

Если поле является окончательным, оно все равно может иметь ситуацию, когда оно назначено.Например в конструкторе.Эта логика применяется компилятором, как указано в этой статье .Сама JVM не будет применять такие правила, поскольку цена производительности будет слишком высокой, и верификатор байт-кода не сможет легко определить, назначено ли поле только один раз.

Поэтому создание поля final через ASM, вероятно, делаетне имеет особого смысла.

1 голос
/ 29 августа 2014

Вы можете перезаписать конечные поля во время выполнения, используя отражение.Gson делает это все время, связывая JSON с объектами Java.

...