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

时间:2015-06-09 09:02:17

标签: java unicode comments

以下代码生成输出“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规范允许这样做?

8 个答案:

答案 0 :(得分:720)

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

JLS Section 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

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

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

  

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

来源:Java: Executing code in comments?!

答案 1 :(得分:136)

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

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

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

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

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

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

答案 2 :(得分:100)

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

在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那样。

答案 3 :(得分:64)

\u000d转义终止了注释,因为在程序被标记化之前,\u转义符被统一转换为相应的Unicode字符。您也可以使用\u0057\u0057代替//开始评论。

这是您的IDE中的一个错误,应该在语法上突出显示该行,以明确\u000d结束评论。

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

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

\u

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

1 对于学生:我知道C的这个方面是100%有意的,理由 - 我不是这样做的 - 它会允许你用机械强制拟合代码任意长线到穿孔卡上。这仍然是一个不正确的设计决定。

答案 4 :(得分:21)

这是一种有意的设计选择,一直追溯到Java的原始设计。

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

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

答案 5 :(得分:20)

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

\u转义在字符串和字符文字中很有用;这是唯一应该存在的地方。应该像处理\n之类的其他转义一样处理它;并且"\u000A" 应该正好代表"\n"

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

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

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

(向读者提问 - 为什么这个问题会继续获得新的投票?这个问题是否与流行的地方有关?)

答案 6 :(得分:11)

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

这可能的原因是,希望允许整个BMP成为Java源代码的可能字符。这提出了一个问题:

  • 您希望能够使用任何BMP角色。
  • 您希望能够轻松输入任何BMP字符。执行此操作的方法是使用Unicode转义。
  • 您希望保持词汇规范易于人类阅读和编写,并且相当容易实现。

当Unicode转义进入战斗时,这是非常困难的:它会创建一大堆新的词法分析器规则。

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

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

缺点是你的榜样。

答案 7 :(得分:-1)

编译器不仅会在将程序解析为令牌之前将Unicode转义符转换为它们所代表的字符,但它会在丢弃注释和空格之前进行转换。

此程序包含一个Unicode转义符(\ u000d),位于其唯一注释中。正如评论告诉你的那样,这个转义表示换行字符,编译器在放弃评论之前正确地将其翻译为

这与平台有关。在某些平台上,例如UNIX,它可以工作;在其他方面,例如Windows,它不会。虽然肉眼可能看起来相同,但如果将其保存在文件中或通过管道传输到另一个程序进行后续处理,则很容易引起问题。