JVM操作数堆栈必须是确定性的吗?

g6baxovj  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(120)
  • 需要说明的是,这是一个关于字节码验证器规范的问题,特别是在处理不是由javac生成的字节码时。*

操作数堆栈可以以非确定性的方式修改吗?换句话说,是否有可能让条件代码根据仅在运行时才知道的信息,将堆栈转换为不同的状态(大小,堆栈顶部的类型......)?

可能不验证的方法示例(伪代码)

float f(boolean b) {
  if (b) {
    push 1.0 // float
  } else {
    push 2L // long
  }
  dup // generic instructions that work for both floats and longs
  pop
  if (b) {
    return // return the float
  } else {
    l2f // convert the long to float and return it
    return
  }
}

个字符
关于第二个例子,我知道操作数堆栈是有界的,所以拥有任意大的堆栈本身就已经是个问题了。
虽然这两种方法在语义上都是正确的,但我认为在非平凡的情况下,在恒定的时间内检查它们的正确性是不可能的。我闻到了一点停机问题/赖斯定理的味道。
当Java运行时不能证明字节码的正确性时,它也可以回退到解释器,但据我所知,在Java中,字节码总是被主动检查(在链接时)。

fumotvh3

fumotvh31#

简而言之,操作数堆栈必须是确定性的。
规范中有两个地方解决了您的大部分问题:

§2.6.2.操作数堆栈:

来自操作数堆栈的值必须以适合其类型的方式进行操作。例如,不可能推送两个int值并随后将其视为long,或者推送两个float值并随后使用 iadd 指令将其相加。少量的Java虚拟机指令(dup 指令(§dup)和 swap(§swap))作为原始值在运行时数据区域上操作,而不考虑它们的特定类型;这些指令以这样的方式定义,即它们不能用于修改或分解各个值。这些对操作数堆栈操作的限制是通过class文件验证(第4.10节)强制执行的。
在任何时间点,操作数堆栈都具有相关联的深度,其中longdouble类型的值向深度贡献两个单位,并且任何其他类型的值贡献一个单位。
注意句子“These restrictions on operand stack manipulation are enforced through class file verification”,告诉您验证是强制性的。
此外,

§4.9.2.结构限制:

...

  • 如果一条指令可以沿着几条不同的执行路径执行,那么在执行该指令之前,操作数堆栈必须具有相同的深度(§2.6.2),而不管所采用的路径如何。

因此,没有办法推送或弹出动态数量的元素。
请注意,从Java 6开始,分支合并点必须有一个堆栈Map框架,描述此时的局部变量和操作数堆栈条目,并且所有代码路径都根据此描述进行验证。这种静态描述还在技术层面上排除了动态堆栈值。
进一步注意,这种帧中的类型描述可以表示xlm 6 nlx、xlm 7 nlx、xlm 8 nlx、xlm 9 nlx和引用类型,以及“xlm 10 nlx”,即验证类型系统的根,其表示不可用的条目,而不是“类型1”或“类型2”。
因此,当规范说“dup可以处理第1类计算类型的值”时,它是说它可以同等地处理intfloat和引用类型的缩写,但它总是在特定的代码位置。不存在它可能遇到非特定“类型1”的情况。
虽然可以有两个分支,一个推送float,另一个推送int,将两者合并到“top”类型的条目中,但不可能以任何方式使用它,甚至不能使用pop删除值,因为值的类型“top”既不是“type 1”也不是“type 2”。
后续代码唯一能做的就是忽略这个值,直到通过returnthrow退出该方法。
在Java 6之前,没有堆栈Map,分支必须由验证器根据以下规则合并:
若要合并两个操作数堆栈,每个堆栈上的值的数量必须相同。然后,比较两个堆栈上的对应值,并计算合并堆栈上的值,如下所示:

  • 如果一个值是基元类型,则对应的值必须是相同的基元类型。合并的值是基元类型。
  • 如果一个值是非数组引用类型,则对应的值必须是引用类型(数组或非数组)。合并的值是对两个引用类型的第一个公共超类型的示例的引用。...
  • 如果对应的值都是数组引用类型,则检查它们的维度。如果数组类型具有相同的维度,则合并值是对数组类型的示例的引用,该数组类型是两个数组类型的第一公共超类型。...

如果无法合并操作数堆栈,则方法验证失败。
请注意,没有合并不同基元类型或合并基元类型和引用类型的规则,甚至没有像更新的类文件那样合并为“top”。
换句话说,在旧的类文件中,上面描述的两个代码路径根本不能合并。

相关问题