以下代码生成输出 “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 规范允许这样做?
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
(这个程序原来是一个简单的 “Hello World” 程序。)
在解决益智游戏的过程中,他们指出了以下内容:
更严重的是,这个谜题有助于强化前三个课程的教训: 当您需要插入无法以任何其他方式表示到您的程序中的字符时,Unicode 转义是必不可少的。在所有其他情况下避免它们。
来源: Java:在评论中执行代码?!
由于尚未解决,这里有一个解释,为什么 Unicode 转义的转换发生在任何其他源代码处理之前:
它背后的想法是它允许在不同的字符编码之间无损地翻译 Java 源代码。今天,有广泛的 Unicode 支持,这看起来不是一个问题,但是当时西方国家的开发人员从他的亚洲同事那里收到一些包含亚洲字符的源代码并不容易做出一些改变(包括编译和测试它并将结果发回,所有这些都不会损坏。
因此,Java 源代码可以用任何编码编写,并允许标识符,字符和String
文字和注释中的各种字符。然后,为了无损地传输它,目标编码不支持的所有字符都被它们的 Unicode 转义替换。
这是一个可逆的过程,有趣的是,转换可以通过一个工具来完成,该工具不需要知道任何关于 Java 源代码语法的知识,因为转换规则不依赖于它。这适用于编译器内部实际 Unicode 字符的转换也独立于 Java 源代码语法。这意味着您可以在两个方向上执行任意数量的转换步骤,而无需更改源代码的含义。
这就是另一个奇怪的功能,甚至没有提到的原因: \uuuuuuxxxx
语法:
当翻译工具转义字符并遇到已经是转义序列的序列时,它应该在序列中插入一个额外的u
,将\ucafe
转换为\uucafe
。意思不会改变,但是当转换到另一个方向时,工具应该只删除一个u
并仅用 Unicode 字符替换包含单个u
的序列。这样,即使 Unicode 转义在来回转换时也会以原始形式保留。我想,没有人曾经使用过这个功能......
我将完全无效地添加这一点,只是因为我无法帮助自己,我还没有看到它,但问题是无效的,因为它包含一个错误的隐藏前提,即代码在一条评论!
在 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
那样。