+-
java-ASM中的ClassWriter COMPUTE_FRAMES
我一直在尝试通过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调用完全相同的堆栈,因此我决定添加一个ICONST_1. IFEQ L10序列与ASM在L35之前.

当我反汇编(再次与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

如我所料

此外,该类也不会通过验证,因为不能调用StringBuilder#< init>.在上面.根据ASM手册,Top指的是未初始化的值,但是从跳转位置和之前的代码来看,它似乎都没有在代码中未初始化.我不知道跳伞有什么问题.

我插入的跳转是否有问题,从而使该类无法计算帧?这可能是ASM的ClassWriter的错误吗?

最佳答案
未初始化的实例很特殊.考虑到,当您复制引用时,您在堆栈上已经有两个对同一实例的引用,您可能会执行更多的堆栈操作,或者将引用转移到本地变量,然后从那里将其复制到其他变量或再次推入.尽管如此,在以任何方式使用引用之前,该引用的目标都应该被初始化一次.为了验证这一点,必须跟踪对象的身份,以便当您执行invokespecial< init>时,对同一对象的所有这些引用将从未初始化变为已初始化.在上面.

Java编程语言无法充分利用所有可能性,但适用于诸如
new Foo(new Foo(new Foo(),new Foo(b?new Foo(a):new Foo(b,c)))),它不应松散地了解哪个Foo实例已初始化,哪些没有初始化,何时分支成立.

因此,每个未初始化实例堆栈帧条目都与创建它的新指令相关联.传输或复制时,所有条目均保留引用(可轻松处理为remembering the byte code offset of the new instruction).仅在invokespecial< init>之后在已对其进行调用之后,指向同一新指令的所有引用都将变为声明类的普通实例,并且随后可以与其他类型兼容的条目合并.

这意味着不可能像您想要实现的那样建立分支.两个相同类型但由不同新指令创建的未初始化实例条目不兼容.并且不兼容的类型将合并到Top条目,这基本上是不可用的条目.如果您不尝试在分支目标上使用该条目,那么它甚至可能是正确的代码,因此ASM在将它们合并到Top时不会抱怨也没有做错任何事情.

注意,这还意味着不允许任何可能导致堆栈帧具有由同一条新指令创建的多个未初始化实例的循环.

点击查看更多相关文章

转载注明原文:java-ASM中的ClassWriter COMPUTE_FRAMES - 乐贴网