ClassWriter COMPUTE_FRAMES in ASM



我一直在尝试通过在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构造函数。

相关内容

  • 没有找到相关文章

最新更新