Почему выполняется выполнение кода 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?

Ответ 1

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

Как указано в JLS Section 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).

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

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


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

Ответ 2

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

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

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

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

Это причина другой странной функции, о которой даже не упоминалось: синтаксис \uuuuuuxxxx:

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

Ответ 3

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

В исходном коде 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.

Ответ 4

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

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

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

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

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

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

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

Ответ 5

Я согласен с @zwol, что это ошибка дизайна; но я даже более критично отношусь к нему.

\u escape полезен в строках и char литералах; и что единственное место, в котором оно должно существовать. Его следует обрабатывать так же, как и другие escape-последовательности, такие как \n; и "\u000A" должно означать точно "\n".

Нет абсолютно никакого смысла иметь \uxxxx в комментариях - никто не может это прочитать.

Точно так же нет смысла использовать \uxxxx в другой части программы. Единственное исключение, вероятно, в общедоступных API, которые принудительно содержат некоторые символы не-ascii - что в последний раз мы видели?

У дизайнеров были свои причины в 1995 году, но через 20 лет это кажется неправильным выбором.

(вопрос читателям - почему этот вопрос продолжает получать новые голоса? этот вопрос связан с чем-то популярным?)

Ответ 6

Это был преднамеренный выбор дизайна, который полностью возвращается к оригинальному дизайну Java.

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

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

Ответ 7

Единственные люди, которые могут ответить, почему экраны Unicode были реализованы, так как они были людьми, которые написали спецификацию.

Вероятная причина этого заключается в том, что было желание разрешить весь BMP как возможные символы исходного кода Java. Это создает проблему, хотя:

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

Это невероятно сложно, когда Unicode выйдет из игры: он создает целый набор новых правил лексера.

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

Поверхность этого заключается в том, что ее легко указать, поэтому упрощает ее спецификацию и ее легко реализовать.

Недостатком является, ну, ваш пример.

Ответ 8

Компилятор не только переводит escape-последовательности Unicode в символы, которые они представляют, прежде чем он анализирует программу в токенах, но делает это до удаления комментариев и пробелов.

Эта программа содержит единственный Unicode escape (\ u000d), расположенный в единственном комментарии. Как говорится в комментарии, этот escape представляет символ перевода строки, а компилятор переводит его перед , отбрасывая комментарий.

Это зависит от платформы. В некоторых формах плат, таких как UNIX, он будет работать на других, таких как Windows, это не будет. Хотя результат может выглядеть невооруженным глазом, он может легко вызвать проблемы, если он был сохранен в файле или передан в другую программу для последующей обработки.