我一直在尝试通过在ASM中玩跳跃来了解堆栈地图框架在Java中的工作方式。我创建了一种简单的方法来尝试一些事情:(用krakatau拆卸):
L0: ldc 'hello'
L2: astore_1
L3: getstatic Field java/lang/System out Ljava/io/PrintStream;
L6: new java/lang/StringBuilder
L9: dup
L10: invokespecial Method java/lang/StringBuilder <init> ()V
L13: ldc 'concat1'
L15: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
L18: aload_1
L19: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
L22: invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String;
L25: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
L28: getstatic Field java/lang/System out Ljava/io/PrintStream;
L31: new java/lang/StringBuilder
L34: dup
L35: invokespecial Method java/lang/StringBuilder <init> ()V
L38: ldc 'concat2'
L40: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
L43: aload_1
L44: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder;
L47: invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String;
L50: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V
L53: return
它所做的就是创建一个StringBuilder
,以与变量连接一些字符串。
由于L35处的InvokeSpecial呼叫与L10的Invokespecial呼叫完全相同,因此我决定在L35之前使用ASM添加ICONST_1; IFEQ L10
序列。
当我分解(再次与Krakatau)分解时,我发现结果很奇怪。ASM在L10上计算了堆栈框架为:
.stack full
locals Object [Ljava/lang/String; Object java/lang/String
stack Object java/io/PrintStream Top Top
.end stack
而不是
stack Object java/io/PrintStream Object java/lang/StringBuilder Object java/lang/StringBuilder
正如我所期望的。
此外,此类也不会通过验证,因为人们无法在Top
上调用StringBuilder#<init>
。根据ASM手册,Top
指的是一个非初始化的值,但是从跳跃位置和以前的代码中,它似乎并未在代码中进行非专业化。我不明白跳跃有什么问题。
我插入的跳跃是否有问题,以某种方式使该类无法计算帧?这可能是ASM的班级作者的错误吗?
非初始化的实例是特殊的。考虑到,当您dup
参考时,您已经有两个引用堆栈上的同一实例,并且您可能会执行更多的堆栈操作或将引用传输到本地变量,然后从那里将其复制到其他变量或再次推动它。尽管如此,在您以任何方式使用之前,应该精确地将参考的目标精确初始化。为了验证这一点,必须跟踪对象的身份,以便 all 这些对同一对象的引用将从 notialialized 初始化您在其上执行invokespecial <init>
。
Java编程语言并不使用所有可能性,而是用于
的法律代码 new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c)))
,它不应松开有关哪个 Foo
实例的轨道,哪些是在制作分支时的。
因此,每个非初始化的实例堆栈框架条目与创建它的new
指令挂钩。所有条目都会保留引用(可以在转移或复制时都可以轻松地处理new
指令的字节代码偏移)。只有在 invokespecial <init>
被调用后,所有指向相同new
指令的所有引用都可以转到声明类的普通实例,然后可以将其与其他类型的兼容条目合并。
这意味着像您试图实现的分支机构是不可能的。同一类型的两个非初始化的实例条目,但由不同的new
指令创建是不兼容的。并将不兼容的类型合并到Top
条目,这基本上是无法使用的条目。它甚至可能是正确的代码,如果您不尝试在分支目标上使用该条目,因此ASM在不抱怨的情况下将其合并到Top
时没有做错任何事情。
请注意,这也意味着不允许使用相同new
指令创建的一个以上非初始化的实例的堆栈框架的任何类型的循环。
new java/lang/StringBuilder
没有创建有效的StringBuilder
,而是一个单位化对象,该对象是在堆栈地图框架中捐赠的TOP
。当对象构建过程中添加跳跃指令时,使用此值,例如:
new Foo(a ? b : c);
被翻译成几个污点。
当对象在对象上调用构造函数时,该对象首先被视为StringBuilder
,即invokespecial Method java/lang/StringBuilder <init> ()V
。JVM不支持在不同位置捕捉该对象,因为验证者只能查看TOP
类型,该类型不会反映所需类型的实际阴影,该阴影是统一的StringBuilder
。您可能会争辩说,JVM应该支持这一点,但这需要较大的数组来包含堆栈地图框架以反映类型和初始化状态,这可能无法证明Java语言甚至没有使用的功率。
要清楚这一点,请考虑以下情况:
new Foo
dup
.stack full
locals
stack Top Top
.end stack
invokespecial Bar <init> ()V
,如果JVM允许在TOP
类型上进行未选中的初始化,则这将是有效的,但显然不应在Foo
上调用Bar
构造函数。