Вопрос: Почему выполняется выполнение кода Java в комментариях с некоторыми символами Unicode?


Следующий код выводит результат «Hello World!». (нет, попробуйте).

public static void main(String... args) {

   // The comment below is not a typo.
   // \u000d System.out.println("Hello World!");
}

Причина этого в том, что компилятор Java анализирует символ Unicode \u000dкак новая линия и преобразуется в:

public static void main(String... args) {

   // The comment below is not a typo.
   //
   System.out.println("Hello World!");
}

Таким образом, результат «выполняется».

Поскольку это можно использовать для «скрытия» вредоносного кода или того, что может себе представить злой программист, почему это разрешено в комментариях ?

Почему это разрешено спецификацией Java?


1240


источник


Ответы:


Декодирование Unicode происходит перед любым другим лексическим переводом. Ключевым преимуществом этого является то, что он делает тривиальным переходить между ASCII и любой другой кодировкой. Вам даже не нужно выяснять, где начинаются и заканчиваются комментарии!

Как указано в JLS Раздел 3.3 это позволяет любому инструменту на основе ASCII обрабатывать исходные файлы:

[...] Язык программирования Java определяет стандартный способ преобразования программы, написанной в Unicode, в ASCII, которая изменяет программу в форму, которая может обрабатываться инструментами на основе ASCII. [...]

Это дает фундаментальную гарантию независимости платформы (независимость поддерживаемых наборов символов), которая всегда была ключевой целью платформы Java.

Возможность записи любого символа Юникода в любом месте файла является аккуратной функцией и особенно важной в комментариях при документировании кода на нелатинских языках. Тот факт, что он может помешать семантике такими тонкими способами, является всего лишь (неудачным) побочным эффектом.

На эту тему много ошибок. Java Puzzlers Джошуа Блох и Нил Гаффер включили следующий вариант:

Является ли это законной Java-программой? Если да, то что он печатает?

\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d

(Эта программа оказывается простой программой Hello World.)

В решении головоломки они указывают на следующее:

Более серьезно, эта головоломка помогает укрепить уроки предыдущих трех: Выделения Unicode необходимы, когда вам нужно вставить символы, которые не могут быть представлены каким-либо другим способом в вашу программу. Избегайте их во всех других случаях.


Источник: Java: Выполнение кода в комментариях ?!


682



Поскольку это еще не рассмотрено, объясните, почему перевод escape-кодов Unicode происходит до любой другой обработки исходного кода:

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

Таким образом, исходный код Java может быть записан в любой кодировке и позволяет использовать широкий диапазон символов в идентификаторах, символах и Stringлитералов и комментариев. Затем, чтобы передать его без потерь, все символы, не поддерживаемые целевой кодировкой, заменяются их экранами Unicode.

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

В этом причина еще одной странной особенности, о которой даже не упоминалось: \uuuuuuxxxxсинтаксис:

Когда инструмент перевода ускользает от символов и встречает последовательность, которая уже является экранированной последовательностью, она должна вставить дополнительный uв последовательность, преобразование \ucafeв \uucafe, Значение не меняется, но при преобразовании в другое направление инструмент должен просто удалить один uи заменить только последовательности, содержащие один uих символами Юникода. Таким образом, даже Unicode-экраны сохраняются в исходной форме при конвертации взад и вперед. Думаю, никто никогда не использовал эту функцию ...


132



Я собираюсь полностью неэффективно добавить точку, просто потому, что я не могу помочь себе, и я еще не видел ее, что вопрос недействителен, поскольку он содержит скрытую предпосылку, которая неверна, а именно, что код находится в комментарий!

В исходном коде Java \ u000d во всех отношениях эквивалентен символу ASCII CR. Это конец строки, простой и простой, где бы он ни возникал. Форматирование в вопросе вводит в заблуждение, что соответствует синтаксически соответствует эта последовательность символов:

public static void main(String... args) {
   // The comment below is no typo. 
   // 
 System.out.println("Hello World!");
}

ИМХО самый правильный ответ: код выполняется потому, что он не в комментарии; это на следующей строке. «Исполнение кода в комментариях» не разрешено на Java, как и следовало ожидать.

Большая часть путаницы проистекает из того факта, что подсветка синтаксиса и IDE недостаточно сложны, чтобы учитывать эту ситуацию. Они либо вообще не обрабатывают экраны unicode, либо делают это после разбора кода вместо него, например javacделает.


97



\u000dпобег завершает комментарий, потому что \uэкраны равномерно преобразуются в соответствующие символы Юникода до программа символизируется. Вы также можете использовать \u0057\u0057вместо //в начать комментарий.

Это ошибка в вашей среде IDE, которая должна синтаксически выделять строку, чтобы было ясно, что \u000dзавершает комментарий.

Это также ошибка дизайна на языке. Теперь это не может быть исправлено, потому что это может сломать программы, зависящие от него. \uescapes должен либо быть преобразован в соответствующий символ Unicode компилятором только в контекстах, где это «имеет смысл» (строковые литералы и идентификаторы и, возможно, нигде больше), или им должно быть запрещено создавать символы в диапазоне U + 0000-007F , или оба. Любая из этих семантик помешала бы комментарию прекратить \u000dизбежать, не мешая случаям, когда \uвыходы полезны - обратите внимание, что включает использование \uвытесняет внутренние комментарии как способ кодирования комментариев в нелатинском скрипте, потому что текстовый редактор может принимать более широкое представление о том, где \uescapes значительны, чем компилятор. (Я не знаю ни одного редактора или IDE, которые будут отображаться \uэкранирует как соответствующие символы в Любые контекст, хотя.)

Подобная ошибка конструкции в семействе C, 1 где обратная косая черта-новая строка обрабатывается до того, как будут определены границы комментариев, т.е.

// this is a comment \
   this is still in the comment!

Я привожу это, чтобы проиллюстрировать, что бывает легко сделать эту конкретную ошибку дизайна, и не осознавать, что это ошибка, пока не будет слишком поздно ее исправлять, если вы привыкли думать о токенизации и анализировать, как программисты компилятора думают о токенизации и синтаксическом анализе. В принципе, если вы уже определили свою формальную грамматику, а затем кто-то придумает синтаксический специальный случай - триграфы, обратную косую черту - новую строку, кодировку произвольных символов Unicode в исходных файлах, ограниченных ASCII, что угодно - что нужно вклинивать, проще добавить проход преобразования до токенизатор, чем переопределить токенизатор, чтобы обратить внимание на то, где имеет смысл использовать этот специальный случай.

1 Для педантов: Я знаю, что этот аспект C был на 100% преднамеренным, с обоснованием - я этого не делаю - чтобы он позволял вам механически форсировать код с произвольно длинными строками на перфокарты. Это было неправильное дизайнерское решение.


63



This was an intentional design choice that goes all the way back to the original design of Java.

To those folks who ask "who wants Unicode escapes in comments?", I presume they are folks whose native language uses the Latin character set. In other words, it is inherent in the original design of Java that folks could use arbitrary Unicode characters wherever legal in a Java program, most typically in comments and strings.

It is arguably a shortcoming in programs (like IDEs) used to view the source text that such programs cannot interpret the Unicode escapes and display the corresponding glyph.


21



I agree with @zwol that this is a design mistake; but I'm even more critical of it.

\u escape is useful in string and char literals; and that's the only place that it should exist. It should be handled the same way as other escapes like \n; and "\u000A" should mean exactly "\n".

There is absolutely no point of having \uxxxx in comments - nobody can read that.

Similarly, there's no point of using \uxxxx in other part of the program. The only exception is probably in public APIs that are coerced to contain some non-ascii chars - what's the last time we've seen that?

The designers had their reasons in 1995, but 20 years later, this appears to be a wrong choice.

(question to readers - why does this question keep getting new votes? is this question linked from somewhere popular?)


21



The only people who can answer why Unicode escapes were implemented as they were are the people who wrote the specification.

A plausible reason for this is that there was the desire to allow the entire BMP as possible characters of Java source code. This presents a problem though:

  • You want to be able to use any BMP character.
  • You want to be able to input any BMP charater reasonably easy. A way to do this is with Unicode escapes.
  • You want to keep the lexical specification easy for humans to read and write, and reasonably easy to implement as well.

This is incredibly difficult when Unicode escapes enter the fray: it creates a whole load of new lexer rules.

The easy way out is to do lexing in two steps: first search and replace all Unicode escapes with the character it represents, and then parse the resulting document as if Unicode escapes don't exist.

The upside to this is that it's easy to specify, so it makes the specification simpler, and it's easy to implement.

The downside is, well, your example.


11



The compiler not only translates Unicode escapes into the characters they represent before it parses a program into tokens, but it does so before discarding comments and white space.

This program contains a single Unicode escape (\u000d), located in its sole comment. As the comment tells you, this escape represents the linefeed character, and the compiler duly translates it before discarding the comment.

This is platform-dependent. On certain platforms, such as UNIX, it will work; on others, such as Windows, it won’t. Although the output may look the same to the naked eye, it could easily cause problems if it were saved in a file or piped to another program for subsequent processing.


1