长度超过 65535 字节的 Java 字符串文字的字节码

Bytecode for Java string literals longer than 65535 bytes

我一直在阅读来自各种文件的 Java 字节码,以帮助我理解项目的 .class 文件,在该项目中我需要与没有集成的第 3 方库集成可用的源代码和糟糕的文档。

为了我自己的娱乐,我通过我的 maven 存储库 运行 Apache BCEL 库来查看在哪里使用了更罕见的 class 和方法属性(例如类型注释)以及为什么。

我偶然发现了一个特定 jar 的问题,该 jar 无法解码其中一个常量字段 - CONSTANT_Utf8_info 具体而言。该库是 icu4j-2.6.1.jar (com.ibm.icu:icu4j),特别是 LocaleElements_zh__PINYIN.class 文件。 Apache BCEL 失败(我自己尝试使用符合 JVMS 版本 8 和 9 的快速字节码 reader)遇到了同样的问题,他们误读了这个常量,然后读取了下一个评估为不正确的常量标记的字节( 0x3C/60).

快速检查我是否可以在 IDE 中使用 class 失败(无法解析符号)。使用十六进制编辑器调查实际字节码,表明该偏移量 (0x1AC) 处的常量是一个长度为 0x480E 的 Utf8 常量 (tag=0x01)。向前移动文件中的那个数量确实在那个位置有一个字节 0x3C。目视查看文件,我可以看到有问题的常量在位置 0x149BD 处结束,这使得字符串的实际长度 0x1480E (本质上是位置 0x1AC 的前三个字节).根据 JVM class 文件规范,这当然是不可能的,对于 Utf8 常量,文件规范的最大长度为 0xFFFF 或 65535。 class 文件很旧 - 版本 46 或 Java 1.2.

我仔细研究了规范并尝试了不同的可能实现(包括更严格的和更严格的)来尝试解析此常量,但它要么无法解析它,要么会破坏其他有效 Utf8 常量的读取。

然后我的问题是,我是否遗漏了什么,或者是编译器错误,在这种情况下,我的第二个问题是,这首先是如何发生的——编译器往往会被相对彻底地检查。最后,Java 编译器通常如何管理长度超过 65535 字节 的字符串文字?

既然你说“class文件很旧 - 版本 46 或 Java 1.2”,确实有可能 class 文件由于当时的编译器在超出限制时不会拒绝代码。

JDK-4309152 : # Compiler silently generates bytecode that exceeds VM limits:

The compiler does not properly enforce certain limits on the number or size of various classfile components. This results in code that appears to compile successfully, but fails at runtime during verification.

These were originally reported as separate bugs, which have now been closed as duplicates of this one. The original bug numbers are included with each item below.

  1. There is a 64k limit on UTF-8 encoded strings. (4071592)

据报道,此错误已在 1.3.1_10 内修复,因此符合时间范围。

请注意,引用的错误 #4071592 指的是在 1.2.0 及更早版本中尝试写入过大的字符串时抛出 UTFDataFormatException,但 #4303354 报告会静默生成无效字符串在 1.3.0。因此,如果有问题的 class 文件是由 javac 生成的,它一定是在 1.3.01.3.1_10 之间的版本 -target 1.2

自修复以来,编译器的标准行为是在某个构造超出 class file/JVM 限制时生成编译器错误。