ASM中的ClassWriter COMPUTE_FRAMES [英] ClassWriter COMPUTE_FRAMES in ASM

查看:48
本文介绍了ASM中的ClassWriter COMPUTE_FRAMES的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试通过ASM中的跳转来了解堆栈映射框架在Java中的工作方式.我创建了一个简单的方法来尝试一些操作:(用Krakatau分解):

I've been trying to understand how stack map frames work in Java by playing around with jumps in ASM. I created a simple method to try some things out: (disassembled with 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 来连接一些带有变量的字符串.

All it does is create a StringBuilder to join some strings with variables.

由于L35处的invokespecial调用与L10处的invokespecial调用具有完全相同的堆栈,因此我决定添加一个 ICONST_1;.IFEQ L10 序列在带有ASM的L35之前.

Since the invokespecial call at L35 has exactly the same stack as the invokespecial call at L10, I decided to add an ICONST_1; IFEQ L10 sequence just before L35 with ASM.

当我反汇编(再次与Krakatau一起)时,我发现结果很奇怪.ASM计算出L10处的堆栈帧为:

When I dissassembled (again with Krakatau), I found the results quite strange. ASM had computed the stack frame at L10 to be:

.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 指的是未初始化的值,但是从跳转位置和之前的代码来看,它似乎并未在代码中进行未初始化.我不知道跳伞有什么问题.

Furthermore, this class would also not pass verification as one cannot call StringBuilder#<init> on Top. According to the ASM manual, Top refers to an uninitialized value, but it doesn't seem to be uninitialized in code, both from the jump location and the code before. I don't understand what is wrong with the jump.

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

Is there something wrong with the jump I inserted that somehow makes the class impossible to compute frames for? Is this perhaps a bug with ASM's ClassWriter?

推荐答案

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

Uninitialized instances are special. Consider that, when you dup the reference, you have already two references to the same instance on the stack and you might perform even more stack manipulations or transfer the reference to a local variable and from there, copy it to other variables or push it again. Still, the target of the reference is supposed to be initialized exactly once before you use it in any way. To verify this, the identity of the object must be tracked, so that all these references to the same object will turn from uninitialized to initialized when you perform an invokespecial <init> on it.

Java编程语言并没有利用所有可能,而是用于诸如
new Foo(new Foo(new Foo(),new Foo(b?new Foo(a):new Foo(b,c))),它不应散布关于哪个 Foo实例已初始化,创建分支时未初始化.

The Java programming language doesn’t use all the possibilities, but for legal code like
new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c))), it should not loose track about which Foo instance has been initialized and which not, when the branch is made.

因此,每个未初始化实例堆栈帧条目都与创建它的 new 指令相关.所有条目都保留引用(可以像

So each Uninitialized Instance stack frame entry is tied to the new instruction that created it. All entries keep the reference (which can be handled as easy as remembering the byte code offset of the new instruction) when being transferred or copied. Only after invokespecial <init> has been invoked on it, all references pointing to the same new instruction turn to an ordinary instance of the declaring class and can be subsequently merged with other type compatible entries.

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

This implies that a branch, like you are trying to achieve, is not possible. The two Uninitialized Instance entries of the same type, but created by different new instructions, are incompatible. And incompatible types are merged to a Top entry, which is basically an unusable entry. It could be even correct code, if you don’t attempt to use that entry at the branch target, so ASM is not doing anything wrong when merging them to Top without complaining.

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

Note that this also implies that any kind of loop that could lead to a stack frame having more than one uninitialized instance created by the same new instruction, is not allowed.

这篇关于ASM中的ClassWriter COMPUTE_FRAMES的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆