为什么在允许某些Unicode字符的注释中执行Java代码?

Reg*_*Reg 1326 java unicode comments

以下代码生成输出"Hello World!" (不,真的,试试吧).

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

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

原因是Java编译器将Unicode字符解析\u000d为新行并转换为:

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

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

从而导致评论被"执行".

由于这可以用来"隐藏"恶意代码或恶意程序员可以设想的任何东西,为什么在评论中允许它

为什么Java规范允许这样做?

aio*_*obe 729

Unicode解码在任何其他词汇翻译之前进行.这样做的主要好处是可以在ASCII和任何其他编码之间来回切换.你甚至不需要弄清楚评论的开始和结束位置!

JLS第3.3节所述,这允许任何基于ASCII的工具处理源文件:

[...] Java编程语言指定了一种将用Unicode编写的程序转换为ASCII的标准方法,该程序将程序更改为可由基于ASCII的工具处理的形式.[...]

这为平台独立性(支持的字符集的独立性)提供了基本保证,这一直是Java平台的关键目标.

能够在文件中的任何位置编写任何Unicode字符是一个简洁的功能,在使用非拉丁语言编写代码时,在评论中尤其重要.它以这种微妙的方式干扰语义的事实只是(不幸的)副作用.

关于这个主题有许多问题,Joshua Bloch和Neal Gafter的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
Run Code Online (Sandbox Code Playgroud)

(这个程序原来是一个简单的"Hello World"程序.)

在解决益智游戏的过程中,他们指出了以下内容:

更严重的是,这个谜题有助于强化前三个课程的教训:当您需要插入无法以任何其他方式表示到您的程序中的字符时,Unicode转义是必不可少的.在所有其他情况下避免它们.


来源:Java:在评论中执行代码?!

  • 简而言之,Java有意允许它:"bug"在OP的IDE中? (84认同)
  • 另一个常见错误是在代码中粘贴Windows路径,如`// C:\ user\...`,这会导致编译错误,因为`\ user`不是有效的Unicode转义序列. (62认同)
  • @Bathsheba:这更像是人们的头脑.人们不会试图理解Java解析的工作原理,因此IDE有时会以错误的方式显示代码.在上面的例子中,注释应以`\ u000d`结尾,并且后面的部分应该有代码突出显示. (60认同)
  • 在`\ u000d`之后的日食部分突出显示.按Ctrl + Shift + F后,字符将替换为新行,其余行将被换行 (50认同)
  • @TheLostMind如果我正确理解了答案你也应该能够用块注释重现这个.`\ u002A /`应该结束评论. (20认同)
  • @Taemyr哇,`\ u002A /`真的很邪恶,eclipse完全无法解析它.将代码放在`/*\u002A /`和`/\u002a*/`之间,它完全隐藏为注释.发现这是[bug 3533](https://bugs.eclipse.org/bugs/show_bug.cgi?id=3533) (11认同)
  • 请注意,如果语言规范禁止使用`\ u'表示法来表示ASCII中可表示的任何内容,则可以完全避免这种情况. (8认同)
  • @UmaKanth,`//`注释一直跳到下一个换行符.`\ u000d`被解释为换行符. (7认同)
  • 如果有人怀疑并且想要测试hello world程序,那么该类应命名为"Ugly.java".还有其他有趣的事情可能由此引起...例如,插入`LRM`字符将允许您编译代码,例如`for(char c = 1; c> 0; c ++)` (6认同)
  • 虽然我同意@aioobe的答案,但源代码是有效的,问题出在IDE中(而StackOverflow上的源代码高亮显示),请注意代码中还有另一个"问题".作为unicode转义序列输入的CR字符被解释为新行的正确开始,但行号不会递增. (5认同)
  • @r好点,做得好.至少这应该是1级编译器警告. (4认同)
  • 我理解unicode字符的介绍,但不是为什么在评论中允许它? (2认同)
  • @Reg,该语言的许多功能与该语言的其他功能相结合是没有意义的.在这种情况下,语言设计者在解析器之前放置unicode转义处理,并且能够在共享中使用unicode转义只是一种(可能是不幸的)副作用. (2认同)
  • @R ..:这样一来,如果您在不使用 ASCII 的计算机上进行开发,那么您将无法输入许多字符,例如使用 EBCDIC 的 IBM 大型机,它不会有花括号。 (2认同)

Hol*_*ger 138

由于尚未解决,这里有一个解释,为什么Unicode转义的转换发生在任何其他源代码处理之前:

它背后的想法是它允许在不同的字符编码之间无损地翻译Java源代码.今天,有广泛的Unicode支持,这看起来不是一个问题,但是当时西方国家的开发人员从他的亚洲同事那里收到一些包含亚洲字符的源代码并不容易做出一些改变(包括编译和测试它并将结果发回,所有这些都不会损坏.

因此,Java源代码可以用任何编码编写,并允许标识符,字符,String文字和注释中的各种字符.然后,为了无损地传输它,目标编码不支持的所有字符都被它们的Unicode转义替换.

这是一个可逆的过程,有趣的是,转换可以通过一个工具来完成,该工具不需要知道任何关于Java源代码语法的知识,因为转换规则不依赖于它.这适用于编译器内部实际Unicode字符的转换也独立于Java源代码语法.这意味着您可以在两个方向上执行任意数量的转换步骤,而无需更改源代码的含义.

这就是另一个奇怪的功能,甚至没有提到的原因:\uuuuuuxxxx语法:

当翻译工具转义字符并遇到已经是转义序列的序列时,它应该u在序列中插入一个额外的转换\ucafe\uucafe.意思不会改变,但是当转换到另一个方向时,工具应该只删除一个u并仅用uUnicode字符替换包含单个序列的序列.这样,即使Unicode转义在来回转换时也会以原始形式保留.我想,没有人曾经使用过这个功能......

  • 请注意,这个设计目标可以在没有任何瑕疵的情况下实现; 最简单的方法是禁止`\ u`转义以生成U + 0000-007F范围内的字符.(所有这些字符都可以通过20世纪90年代相关的所有国家编码本地表示 - 好吧,可能除了一些控制字符外,但你不需要那些编写Java的人.) (10认同)
  • 是的,`native2ascii`旨在帮助准备资源包,将它们转换为iso-latin-1 as [`Properties.load`](http://docs.oracle.com/javase/8/docs/api/java/ util/Properties.html#load-java.io.InputStream-)被修复为仅读取latin-1.在那里,规则是不同的,没有`\ uuu ...`语法,没有早期处理阶段.在属性文件中,`property = multi\u000aline`确实与`property = multi \nline`相同.(与文档中"使用Java™语言规范第3.3节中定义的Unicode转义"这一短语相矛盾) (5认同)
  • @zwol:好吧,如果你排除了Java源代码中不允许的控制字符,你是对的.然而,这意味着使规则更复杂.今天,讨论这个决定为时已晚...... (3认同)
  • 有趣的是,`native2ascii` 似乎没有使用 `\uu...xxxx` 语法, (2认同)

Pep*_*itz 104

我将完全无效地添加这一点,只是因为我无法帮助自己,我还没有看到它,但问题是无效的,因为它包含一个错误的隐藏前提,即代码在一条评论!

在Java源代码中,\ u000d在各方面都与ASCII CR字符等效.它是一个结尾的行,简单明了,无论它出现在哪里.问题中的格式是误导性的,字符序列实际上在语法上对应的​​是:

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

恕我直言,最正确的答案是:代码执行,因为它不在评论中; 它在下一行.Java中不允许"在注释中执行代码",就像您期望的那样.

大部分混淆源于语法高亮显示器和IDE不够复杂以考虑这种情况.它们要么根本不处理unicode转义,要么在解析代码之后而不是之前处理它,就像javac它一样.

  • 我同意,这不是java"设计错误",但它是一个IDE错误. (6认同)
  • 问题在于,为什么代码_looks_喜欢对不熟悉语言的这个特定方面的人发表评论,而且可能没有引用语法高亮,实际上是_not_评论.在问题无效的前提下反对是不诚实的. (3认同)
  • @jmoreno 人们不应该_拥有_除了文本编辑器之外的任何东西来阅读代码。至少,它违反了最小意外原则,即 // 样式注释继续直到下一个 \n 字符 - 而不是最终被 \n 替换的任何其他序列。评论永远不会被删除。糟糕的预处理器。 (3认同)
  • 因此,为了提出一个问题,我们必须已经知道答案,才能确认我们的问题实际上是有效的?我不认为这个问题是“无效的”——尽管它可能包含错误的假设。 (3认同)

zwo*_*wol 65

所述\u000d逸出终止评论,因为\u逃逸均匀地转化为相应的Unicode字符之前被标记化的程序.你同样可以使用\u0057\u0057的,而不是//开始评论.

这是IDE中的一个错误,它应该语法突出显示该行,以明确\u000d结束注释.

这也是语言中的设计错误.它现在无法纠正,因为这会破坏依赖它的程序. \u转换器应该仅由编译器在"有意义"(字符串文字和标识符,可能不在其他地方)的上下文中转换为相应的Unicode字符,或者它们应该被禁止在U + 0000-007F范围内生成字符, 或两者.这些语义中的任何一个都会阻止注释被\u000d转义终止,而不会干扰\u转义有用的情况 - 请注意,这包括\u在注释中使用转义作为在非拉丁文脚本中编码注释的方法,因为文本编辑器可以更广泛地了解\u转义的重要性,而不是编译器.(我不知道任何编辑器或IDE会\u任何上下文中显示转义为相应的字符.)

在C系列中存在类似的设计错误,1其中在确定注释边界之前处理反斜杠换行符,例如

// this is a comment \
   this is still in the comment!
Run Code Online (Sandbox Code Playgroud)

我提出这个问题来说明这个特定的设计错误很容易发生,而且如果你习惯于考虑标记化和解析编译程序员的思维方式,那么直到修正它为时已经太晚才会发现它是错误的.关于标记化和解析.基本上,如果你已经定义了你的形式语法,然后有人想出一个语法特殊情况 - trigraphs,反斜杠换行,在源文件中编码任意Unicode字符,限制为ASCII,无论什么 - 需要楔入,它更容易在令牌化器之前添加转换传递,而不是重新定义令牌化器以注意使用该特殊情况的合理位置.

1对于学龄儿童:我知道C的这个方面是100%有意识的,理由是 - 我不是这样做的 - 它可以让你用任意长线机械强制编码代码到打孔卡上.这仍然是一个不正确的设计决定.

  • 我不会说这是一个设计*错误*.我同意你的意见,这是一个糟糕的设计选择,或者是一个带来不幸后果的选择,但我仍然认为它可以像语言设计者那样工作:它使你能够在文件的任何地方使用任何unicode字符,同时保持ASCII编码的文件. (17认同)
  • 话虽如此,我认为`\ u`的处理阶段的选择不如决定遵循C的主导使用前导零进行八进制表示法.虽然八进制符号有时是有用的,但我还没有听到任何人说出为什么一个前导零是指示它的好方法. (12认同)
  • @supercat如果禁止在U + 0000..U + 007F范围内生成字符,那么Java'\ u`作为预标记化转换就不会有问题.它是"这无处不在"和"这个具有语法意义的ASCII字符别名"的组合,将其从尴尬变为扁平错误. (7认同)
  • 关于你的"for pedants":当然当时[```单行注释不存在](http://stackoverflow.com/q/8284940/256431).并且由于C有一个不是新行的语句终结符,它主要用于长字符串,除了我可以确定K&R的"字符串文字串联"_was_. (4认同)
  • @supercat将该功能引入C89的人们推广了原始K&R预处理器的行为,而不是从头开始设计功能.我怀疑他们是否熟悉穿孔卡的最佳实践,我也怀疑该功能是否曾被用于其声明的目的,除了一两次逆向计算练习. (3认同)
  • @supercat:今天,IDE就是这么做的."松散匹配标准"通常由单个字母组成,然后IDE填写剩余字符以使其成为适当的"紧密匹配标准",并且我认为编译器不应该处理"松散匹配标准".也就是说,我不会有一个编译器能够愉快地解决`i`到'I`的发生,并且当有人在土耳其语言环境中编译它时,`i`突然被解决为'İ`... (3认同)

Jon*_*ons 21

这是一个有意的设计选择,一直回到Java的原始设计.

对于那些问"谁想要在评论中逃脱Unicode?"的人,我认为他们是那些母语使用拉丁字符集的人.换句话说,Java的原始设计中固有的,人们可以在Java程序中的任何合法地方使用任意Unicode字符,最常见的是在注释和字符串中.

可以说,用于查看源文本的程序(如IDE)的缺点是这些程序无法解释Unicode转义并显示相应的字形.

  • 现在我们使用UTF-8作为源代码,可以直接使用Unicode字符,不需要转义. (8认同)

Zho*_*gYu 20

我同意@zwol这是一个设计错误; 但我更加批评它.

\uescape在string和char文字中很有用; 这是唯一应该存在的地方.它应该像其他逃脱一样处理\n; 并且"\u000A" 应该完全意味着"\n".

\uxxxx在评论中绝对没有意义- 没有人可以阅读.

同样,\uxxxx在程序的其他部分也没有意义.唯一的例外可能是在强制包含一些非ascii字符的公共API中 - 我们最后一次看到它是什么?

设计师在1995年有他们的理由,但20年后,这似乎是一个错误的选择.

(向读者提问 - 为什么这个问题不断获得新的选票?这个问题是否从流行的地方联系起来?)

  • 他们可以使用适当的文件编码.当你能做`int整`时,为什么要写'int\u5431` (15认同)
  • 我不认为这已经改变了.Java的文档大部分时间都是全英文的.有一段时间保留了日语翻译,但维护***语言并没有真正支持为世界上所有语言环境维护它的想法(它反而证明了它).在此之前,无论如何都没有主流语言在标识符中支持Unicode.所以我猜,有人*认为*本地化的源代码是下一个重要的事情.我会说*谢天谢地*,它没有起飞. (8认同)
  • 我想,你不会闲逛,在API中使用非ASCII字符.有人使用它(不是我),例如在亚洲国家.当您在标识符中使用非ASCII字符时,禁止在文档注释中使用它们毫无意义.然而,允许它们在令牌内并允许它们改变令牌的含义或边界是不同的事情. (5认同)
  • 现在比1995年更清楚的是,如果你想编程,你最好懂英语.编程是一种国际互动,几乎所有资源都是英文的. (5认同)
  • 当你*必须针对他们的API编译代码并且不能使用正确的编码(假设1995年没有广泛的`UTF-8`支持)时,你会做什么?您只需调用一个方法,并且不希望为该单个方法安装操作系统的亚洲语言支持包(请记住,九十年代)... (3认同)
  • @bayou.io 当我必须建模没有英文名称的东西时我该怎么办?如果您曾经处理过法律或商业或类似缺乏这些东西的领域,那么这种情况很常见。特别是在法律领域,词语具有非常具体的含义。想象一下,如果标准字母表没有 C、X 或 Q。现在您有一个名为“KommonLaw”或其他名称的类。您想使用“C”。在你的世界里,这是错误的。但如果 KommonLaw 有其他含义呢?怎么办?您可能会在某个时候尝试使用一种允许您使用“C”的语言。 (2认同)

Mar*_*ijn 11

能够回答为什么Unicode转义被实现的唯一的人是编写规范的人.

一个似是而非的理由是,希望允许整个BMP成为Java源代码的可能字符.这提出了一个问题:

  • 您希望能够使用任何BMP角色.
  • 您希望能够相当容易地输入任何BMP字符.一种方法是使用Unicode转义.
  • 您希望保持词汇规范易于人类阅读和编写,并且相当容易实现.

当Unicode转义进入战斗时,这是非常困难的:它创建了一整套新的词法分析器规则.

最简单的方法是分两步执行lexing:首先使用它所代表的字符搜索并替换所有Unicode转义符,然后解析生成的文档,就好像Unicode转义不存在一样.

这样做的好处在于它易于指定,因此它使规范更简单,并且易于实现.

不好的是,你的榜样.

  • 或者,将\ uxxxx的使用限制为标识符,字符串文字和字符常量.这是C11的作用. (2认同)

归档时间:

查看次数:

73818 次

最近记录:

7 年,3 月 前