Java类文件可以使用保留关键字作为名称吗?

61

我知道可编译的编程语言 Java 并不等同于 Java 字节码格式,用于 JVM 执行。有些东西在 .class 格式中是有效的,但在 .java 源代码中无效,例如无构造函数类和合成方法。

  1. 如果我们手工制作一个带有保留 Java 语言关键字(例如 intwhile)作为类、方法或字段名称的 .class 文件,Java 虚拟机是否会接受它进行加载?

  2. 如果该类已被加载,是否意味着访问该类或成员的唯一方式是通过 Java 反射,因为名称在 Java 编程语言中是语法非法的?


对于第二个问题,是的,因为您不能以非法方式使用Java关键字。我不明白为什么第一个问题不可能,因为关键字只是语言的构造。 - Dave Newton
1
一个小提示(也在一个答案中提到):一些混淆器利用这个事实。它们会将.class文件中的方法/类/变量名称替换为关键字,如dowhile等,以使反编译代码更加困难。(仍然可以反编译,但生成的代码基本上是无用的) - Marco13
1
@Marco13:但这只是一种防止简单攻击的措施。重命名标识符并创建合法名称,再进行反编译即可轻松解决。具有讽刺意味的是,混淆器本身可以用来完成此操作,因为它们提供了通常可配置的重命名操作,因此可以告诉它生成合法名称。 - Holger
4个回答

37

是的,您可以使用保留字。这些单词仅供编译器使用,不会出现在生成的字节码中。

在基于JVM的Scala语言中,有一个使用Java保留字的示例。Scala具有不同的结构和语法,但编译为可在JVM上运行的Java字节码。

下面是合法的Scala代码:

class `class`

这定义了一个名为class的类,并带有一个无参构造函数。运行已编译的class.class文件的反汇编器javap显示:

public class class {
    public class();
}

Scala可以对任何其他Java保留字进行相同的操作。
class int
class `while`
class goto

它们也可以用于方法或字段名称。

正如你所猜测的那样,除了反射,你无法从Java中使用这些类。你可以从类似“定制”的类文件中使用它们,例如从Scala编译器生成的类文件。

总之,这是javac(编译器)的限制,而不是java(VM /运行时环境)的限制。


3
让我们坚持使用反汇编器 ;) - Tobias
2
非常好,我不知道Scala本地允许您滥用Java保留关键字作为名称=) - Nayuki
1
@NayukiMinase,两者之间的区别在于它们是否生成源代码(反编译器)或仅生成可读表示(反汇编器)。javap仅生成签名--作为源代码--并省略实现,不像其他反编译器(例如DJ)和反汇编器(例如IDA),因此很难说。看起来,反汇编是更常见的词选择,所以我会改变它。 - Paul Draper
1
官方上说,它是一个反汇编器。如果你使用 -c 标志,它将实际进行反汇编,而不仅仅显示结构。 - Tobias
2
从技术上讲,这是Java语言的限制,而不是javac。任何符合规范的Java编译器都会强制执行相同的限制。 - Antimony
显示剩余2条评论

29
在字节码级别上,类名只有以下限制:不能包含字符 [, ., 或 ;,长度最多为65535个字节。这意味着您可以自由使用保留字、空格、特殊字符、Unicode甚至像换行符之类的奇怪字符。
理论上,甚至可以在类名称中使用空字符,但由于文件名中不可能有空字符,因此无法将此类文件包含在JAR文件中。但是您可能能够动态地创建并加载它们。
下面是一些您可以做的事情的示例(以Krakatau汇编语言编写):
; Entry point for the jar
.class Main
.super java/lang/Object

.method public static main : ([Ljava/lang/String;)V
    .limit stack 10
    .limit locals 10
    invokestatic int                                hello ()V
    invokestatic "-42"                              hello ()V
    invokestatic ""                                 hello ()V
    invokestatic "  some  whitespace and \t tabs"   hello ()V
    invokestatic "new\nline"                        hello ()V
    invokestatic 'name with "Quotes" in it'         hello ()V
    return
.end method
.end class


.class int
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from int"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "-42"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from -42"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

; Even the empty string can be a class name!
.class ""
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from "
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "  some  whitespace and \t tabs"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from   some  whitespace and \t tabs"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "new\nline"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from new\nline"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class 'name with "Quotes" in it'
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from name with \"Quotes\" in it"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

执行输出:
Hello from int
Hello from -42
Hello from
Hello from   some  whitespace and        tabs
Hello from new
line
Hello from name with "Quotes" in it

请查看Holger的回答,获取来自JVM规范的准确规则引用。


3
确实很有趣!您的代码使用的是什么语法?它是否可以使用现有工具编译成 .class 文件? - Nayuki
除了“JVM规范如此规定”之外,为什么标识符名称中有任何字符限制?而且为什么是那3个字符?据我所知,这些名称在代码的解释或编译中没有任何作用。[谁说文件名中不能有空值?我认为Unix或NTFS只要足够深入就不会在意。当然,从键盘上输入很困难。] - Ira Baxter
1
@Ira,你会发现Unix确实关心null。 - Paul Draper
@PaulDraper:啊,C字符串污染。好的。 - Ira Baxter
1
@Ira Baxter:JVM规范并不关心文件系统。它总是假定可以有替代存储形式或名称混淆方案来解决不支持字符的问题。请记住,当Java被发明时,文件系统中的Unicode支持并不标准。尽管如此,Java名称中的Unicode支持从一开始就存在。我回答了一个问题,解释了为什么不允许使用这些字符。 - Holger
1
@Nayuki 我使用的是Krakatau汇编语法。你可以使用Krakatau(https://github.com/Storyyeller/Krakatau)将其汇编成class文件。它基于Jasmin语法,但具有更多功能。 - Antimony

18

有关名称的限制在JVM规范中已经确定:

§4.2.1. 二进制类和接口名称

在类文件结构中出现的类和接口名称始终以完全限定形式表示,称为二进制名称 (JLS §13.1)。这些名称始终表示为 CONSTANT_Utf8_info 结构 (§4.4.7),因此可以从整个 Unicode 码空间中提取,除非另有约束…

由于历史原因,出现在类文件结构中的二进制名称的语法与 JLS §13.1 中记录的二进制名称的语法不同。在这种内部形式中,通常用于组成二进制名称的标识符的 ASCII 句点 (.) 被 ASCII 正斜杠 (/) 替换。这些标识符本身必须是未限定名称 (§4.2.2)。

§4.2.2. 未限定名称

方法、字段、局部变量和形式参数的名称存储为未限定名称。未限定名称必须包含至少一个 Unicode 代码点,并且不能包含任何 ASCII 字符 . ; [ /(即句点或分号或左方括号或正斜杠)。

方法名称进一步受到限制,除了特殊方法名称 <init><clinit> (§2.9) 之外,它们不能包含 ASCII 字符 <>(即左尖括号或右尖括号)。

因此答案是,在二进制级别上只有少数字符不能使用。首先,/ 是包分隔符。然后,;[ 不能使用,因为它们在字段签名方法签名中具有特殊含义,这些签名可能包含类型名称。在这些签名中,[ 开始一个数组类型,而; 则标记引用类型名称的结尾。
没有明确的原因说明为什么禁止使用 .。它不在JVM中使用,只有在通用签名中才具有意义,但是如果您正在使用通用签名,则类型名称受到进一步限制,不能包含<>:,因为这些字符在通用签名中也具有特殊含义。
因此,在标识符中使用.违反规范对JVM的主要功能没有影响。有些混淆器会这样做。生成的代码可以运行,但是当要求泛型类型签名时,可能会遇到Reflection问题。此外,通过将所有/替换为.来将二进制名称转换为源名称,如果二进制名称包含.,则将变得不可逆。

可能有趣的是,曾经有一个支持Java语法中所有可能标识符的提案(参见第3点,“奇异标识符”),但它没有被纳入最终的Java 7。而且似乎目前没有人正在尝试将其引入。


另外还有一个技术限制,即名称的Modified UTF-8表示长度不能超过65535个字节,因为字节数以无符号短整型值存储。


这是一个由技术证据支持的绝妙答案。我特别喜欢您解释为什么每个保留字符都很特殊,以及点号处于悬而未决状态时会发生什么。谢谢。 - Nayuki

10
  1. 关键字只有编译器知道。编译器将它们翻译成适当的字节码。因此,在编译后的字节码运行期间,它们不存在,并且在JVM上也无法验证。
  2. 当然,您无法访问在编译时未知的类成员。但是,如果您确定这样的类成员将存在于已编译的代码中(您将在那里“手工制作”它们),则可以使用反射来实现此目的,因为反射访问不受编译器验证。

(抱歉,我更改了编号)
  1. Python is a popular programming language for data analysis and machine learning.
  2. JavaScript is commonly used for web development and creating interactive user interfaces.
  3. Java is a versatile language that can be used for developing mobile apps, web applications, and enterprise software.
  4. C++ is a powerful language often used for developing operating systems, video games, and high-performance software.
  5. Ruby is a dynamic, object-oriented language known for its simplicity and productivity.
  6. PHP is a server-side scripting language used for web development and building dynamic web pages.
  7. Swift is a modern language developed by Apple for iOS and macOS app development.
  8. Kotlin is a relatively new language that has gained popularity for Android app development.
  9. SQL is a language used for managing and manipulating relational databases.
  10. HTML and CSS are markup languages used for creating and styling websites.
- Nayuki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接