下面的代码产生输出“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转义的翻译发生在任何其他源代码处理之前:
它背后的想法是,它允许在不同字符编码之间无损地转换Java源代码。今天,Unicode得到了广泛的支持,这看起来不像是一个问题,但在当时,对于一个西方国家的开发人员来说,从他的亚洲同事那里收到一些包含亚洲字符的源代码,进行一些更改(包括编译和测试),并将结果发送回去,而不破坏任何东西是不容易的。
因此,Java源代码可以用任何编码编写,并允许在标识符、字符和字符串字面量和注释中使用广泛的字符。然后,为了无损地传输它,目标编码不支持的所有字符都被它们的Unicode转义替换。
这是一个可逆的过程,有趣的是,翻译可以通过一个不需要了解任何Java源代码语法的工具来完成,因为翻译规则不依赖于它。这是因为在编译器中转换到实际Unicode字符的过程也独立于Java源代码语法。这意味着您可以在两个方向上执行任意数量的翻译步骤,而不会改变源代码的含义。
这就是为什么会出现另一个没有提到的奇怪特性:\uuuuuuxxxx语法:
当翻译工具转义字符时,遇到一个已经是转义序列的序列,它应该在序列中插入一个额外的u,将\ucafe转换为\uucafe。含义不会改变,但是当转换到另一个方向时,该工具应该只删除一个u,并仅用Unicode字符替换包含单个u的序列。这样,在来回转换时,即使是Unicode转义也会保留其原始形式。我想,没有人使用过这个功能……
唯一能回答为什么Unicode转义会这样实现的人是编写规范的人。
这样做的一个合理的原因是希望允许整个BMP作为Java源代码的可能字符。但这也带来了一个问题:
您希望能够使用任何BMP字符。
您希望能够相当容易地输入任何BMP字符。一种方法是使用Unicode转义。
您希望保持词汇规范易于人们阅读和编写,并且相当容易实现。
当Unicode escape进入争论时,这是非常困难的:它创建了一大堆新的词法分析器规则。
最简单的方法是分两步进行词法分析:首先搜索并将所有Unicode转义替换为它所代表的字符,然后解析结果文档,就好像Unicode转义不存在一样。
这样做的好处是易于指定,因此使规范更简单,而且易于实现。
缺点是,你的例子。
“这样做的原因是Java编译器将Unicode字符\u000d解析为新行”。
如果为真,那么这正是错误发生的地方。
Java编译器也许应该拒绝编译这个源代码,因为(作为Java源代码)它是格式不正确的,因此要么一开始就不好,要么在途中被篡改,要么被不理解转换规则的工具链中的某些东西改变了。他们不应该盲目地改造它。
如果所讨论的编辑器是一个只使用ascii的工具,那么所述编辑器正在做正确的事情——将Unicode转义序列视为(格式错误的)注释中无意义的字符串。
如果所讨论的编辑器是一个支持Unicode的工具,那么它也在做正确的事情——保持Unicode转义序列“原样”,并将其视为(格式错误的)注释中无意义的字符串。
无损可逆转换需要将1-1映射到——因此两个集合的交集必须为空。在这里,即使正确实现的escape-ify-ing转换没有修改字符,这两个问题集也可以重叠,因为范围(000-07F)中的escaping - unicode可能已经出现在输入流中。
如果目标是在Unicode和ASCII之间进行无损、可逆的转换,则转换到/从ASCII的要求是转义/重新编码任何大于hex 007F的Unicode字符,并保留其余的字符。
做到这一点后,Unicode感知的语言将把转义后的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一样。