Почему JVM позволяет нам называть функцию, начинающуюся с цифры в байт-коде?
Идентификаторы хорошо определены Спецификацией языка Java, Java SE 7 Edition (§3.8)
An identifier is an unlimited-length sequence of Java letters and Java digits, the
first of which must be a Java letter.
Насколько мне известно, поскольку имя метода является идентификатором, не следует указывать метод, начинающийся с цифры в java, и javac
соблюдает это правило.
Итак, почему виртуальная машина Java, похоже, не соблюдает это правило, позволяя нам называть функцию, начинающуюся с чисел, в Bytecode?
Этот простой фрагмент фактически напечатает имя метода f99()
и значение его параметра.
public class Test {
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.f99(100));
}
public int f99(int i){
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName());
return i;
}
}
Компиляция и выполнение:
$ javac Test.java
$ java Test
Вывод:
f99
100
Можно разобрать код после компиляции и переименовать все f99
вхождения на 99
(с помощью такого инструмента, как reJ).
$ java Test
Вывод:
99
100
Итак, имя метода фактически "99"?
Ответы
Ответ 1
Спецификация языка Java ограничивает символы допустимыми именами методов, чтобы сделать разбор языка Java недвусмысленным.
JVM был разработан, чтобы поддерживать языки, отличные от Java. Таким образом, ограничения не должны быть одинаковыми; если мы не хотим заставить все языки, не относящиеся к Java, иметь те же ограничения. Ограничения, выбранные для JVM, - это минимальное множество, допускающее однозначный синтаксический анализ сигнатур метода, формат, который появляется в спецификации JVM, а не JLS.
Взято из JVM Spec
a name must not contain any of the ASCII characters . ; [ / < > :
Таким образом, следующие действительные сигнатуры JVM [Lcom/foo/Bar;
, и его специальные символы были исключены из имен методов.
<>
дополнительно зарезервировано для разделения специальных методов JVM из методов приложения, в частности <init>
и <clinit>
, которые являются именами обоих методов, которые JLS не разрешает.
Ответ 2
Итак, имя метода фактически "99"?
Реальные программисты не используют парсеры, они используют sed
:
javac Test.java
sed -i 's/\d003f99/\d00299/' Test.class
java Test
Вывод:
99
100
Это работает, потому что мы знаем, что имя метода хранится в пуле констант как открытый текст в записи Utf8, а JVMS говорит, что Utf8 записи имеют вид:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
поэтому у нас было что-то вроде:
01 | 00 03 | 'f' '9' '9'
(идентификатор длиной 3 байта), а команда sed заменила 03 | 'f' '9' '9'
на 02 | '9' '9'
(теперь на 2 байта).
Позже я проверил с javap -v Test.class
, что sed
сделал то, что я хотел. До:
#18 = Utf8 f99
После:
#18 = Utf8 99
После редактирования, запуска, декомпиляции и сравнения .class
с JVMS вручную можно сделать вывод, что имя метода должно быть 99
: -)
Так что это просто ограничение языка Java, отсутствующее в байт-коде.
Почему Java предотвращает такие имена?
Вероятно, чтобы синтаксис выглядел как C.
Не начинающийся с цифр упрощает дифференцирование идентификаторов из целых литералов для людей и парсеров.
См. также: