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

2022-08-31 03:54:54

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


答案 1

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:在注释中执行代码?!


答案 2

由于这个问题还没有得到解决,这里要解释一下,为什么Unicode转义的翻译发生在任何其他源代码处理之前:

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

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

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

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

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


推荐