ASM中的ClassWriter COMPUTE_FRAMES [英] ClassWriter COMPUTE_FRAMES in ASM
问题描述
我一直在尝试通过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屋!