我在ASM框架的帮助下创建
Java字节码检测工具,需要确定并可能更改方法的局部变量类型.很快我遇到了一个简单的情况,其中变量和堆栈映射节点看起来有些奇怪,并且没有给我足够的有关正在使用的变量的信息:
public static void test() {
List l = new ArrayList();
for (Object i : l) {
int a = (int)i;
}
}
给出以下字节码(来自Idea):
public static test()V
L0
LINENUMBER 42 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList. ()V
ASTORE 0
L1
LINENUMBER 43 L1
ALOAD 0
INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator;
ASTORE 1
L2
FRAME APPEND [java/util/List java/util/Iterator]
ALOAD 1
INVOKEINTERFACE java/util/Iterator.hasNext ()Z
IFEQ L3
ALOAD 1
INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;
ASTORE 2
L4
LINENUMBER 44 L4
ALOAD 2
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 3
L5
LINENUMBER 45 L5
GOTO L2
L3
LINENUMBER 46 L3
FRAME CHOP 1
RETURN
L6
LOCALVARIABLE i Ljava/lang/Object; L4 L5 2
LOCALVARIABLE l Ljava/util/List; L1 L6 0
MAXSTACK = 2
MAXLOCALS = 4
可以看出,所有4个显式和隐式定义的变量都占用1个时隙,4个时隙被保留,但只有2个被定义,以奇怪的顺序(地址0之前的地址2)和它们之间的“漏洞”.稍后使用ASTORE 1将List迭代器写入此“漏洞”,而不首先声明此变量的类型.只有在此操作后才会出现堆栈映射框,但我不清楚为什么只有2个变量放入其中,因为后来使用了2个以上的变量.稍后,使用ISTORE 3,int再次写入变量槽,没有任何声明.
此时看起来我需要完全忽略变量定义,并通过解释字节码来推断所有类型,运行JVM堆栈的模拟.
尝试了ASM EXPAND_FRAME选项,但它没用,仅将单帧节点的类型更改为F_NEW,其余部分仍然与以前完全相同.
任何人都可以解释为什么我会看到这样一个奇怪的代码,除了编写自己的JVM解释器之外还有其他选择吗?
结论,基于所有答案(如果我错了,请再次纠正我):
变量定义仅用于将源变量名称/类型匹配到在特定代码行访问的特定变量槽,显然是由JVM类验证程序和代码执行期间忽略的.可能缺席或与实际字节码不匹配.
变量槽被视为另一个堆栈,虽然通过32位字索引访问,并且只要您使用匹配类型的加载和存储指令,它总是可以用不同的临时值覆盖其内容.
堆栈帧节点包含从变量帧的开头分配到最后一个变量的变量列表,该变量将在后续代码中加载而不先存储.无论采用何种执行路径到达其标签,预期此分配映射都是相同的.它们还包含类似操作数堆栈的映射.它们的内容可以指定为相对于前一个堆栈帧节点的增量.
仅存在于线性代码序列中的变量将仅出现在堆栈帧节点中,如果存在在较高的插槽地址处分配的具有较长寿命的变量.