为什么在允许某些Unicode字符的注释中执行Java代码?
以下代码产生输出“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程序吗? 如果是这样,它打印什么?
u0070u0075u0062u006cu0069u0063u0020u0020u0020u0020
u0063u006cu0061u0073u0073u0020u0055u0067u006cu0079
u007bu0070u0075u0062u006cu0069u0063u0020u0020u0020
u0020u0020u0020u0020u0073u0074u0061u0074u0069u0063
u0076u006fu0069u0064u0020u006du0061u0069u006eu0028
u0053u0074u0072u0069u006eu0067u005bu005du0020u0020
u0020u0020u0020u0020u0061u0072u0067u0073u0029u007b
u0053u0079u0073u0074u0065u006du002eu006fu0075u0074
u002eu0070u0072u0069u006eu0074u006cu006eu0028u0020
u0022u0048u0065u006cu006cu006fu0020u0077u0022u002b
u0022u006fu0072u006cu0064u0022u0029u003bu007du007d
(这个程序原来是一个普通的“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
一样。
上一篇: Why is executing Java code in comments with certain Unicode characters allowed?