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

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

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

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

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

Иными словами, мы начинаем с класса с одним конечным (x) и одним не конечным (y) полем. Мы делаем x final. Мы устанавливаем setX() множество 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 (см. Выше).

Я считаю, что я убедительно доказал, что JVK7 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}
  • Есть ли кто-нибудь еще, прежде чем сообщать об ошибке?
  • Кто-нибудь может подтвердить, что это ошибка в JDK 6 или только в 7?
4b9b3361

Ответ 1

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

Не в том смысле, о котором вы говорите.

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

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

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

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


UPDATE

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

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

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

Ответ 2

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

Таким образом, создание поля final через ASM, вероятно, не имеет большого смысла.

Ответ 3

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