ASM库中MethodNode的'visitMethodInsn'方法的实现
Implementation of 'visitMethodInsn' method of MethodNode in ASM library
这是 MethodNode
visitMethodInsn
方法的主体 class:
@Override
public void visitMethodInsn(
final int opcode,
final @InternalForm String owner,
final @Identifier String name,
final @MethodDescriptor String descriptor,
final boolean isInterface) {
if (api < Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, descriptor, isInterface));
}
如您所见,如果 asm api 版本低于 5
,则字节码不会添加到 instructions list
。背后的原因是什么?
对 Gitlab 历史的一些调查表明,有问题的代码是在 this commit 中添加的,提交消息为 Added support for invokespecial and invokestatic on interfaces.
Java 8 引入了在接口中定义非抽象方法的能力,称为默认方法。在字节码级别,更改的效果是您现在可以使用接口方法以及带有 invokespecial
和 invokestatic
指令的 class 方法。
在Java8之前,在生成字节码的时候,可以简单的通过指令判断常量池入口的类型:如果操作码是invokeinterface
,生成一个InterfaceMethod
条目,否则生成一个 Method
条目。在Java8中,这已经不可能了,因为invokespecial
和invokestatic
是有歧义的,这意味着用户需要能够显式传入方法是接口方法还是不是。这意味着他们必须向几乎所有方法 api 添加一个额外的参数。
但是,他们不想破坏向后兼容性,这意味着他们需要保留具有旧签名的方法(即没有 itf 参数)。这些方法将转发到新的方法,itf
默认为 true
用于 invokeinterface
指令,而 false
否则,这似乎是一个合理的默认值。这就是您在上面看到的 supercall 所做的。我不确定为什么 API < 5 开关在那里,但我怀疑它要么是为了确保向后兼容性,要么是为了打破他们的方法调度方案中的无限循环。
附带说明一下,MethodNode
作为主要代码重组的一部分在大约 8 个月前被删除,因此您不会在最新版本的 ASM 中看到它。
编辑:我看到您对方法委托感到困惑。这非常棘手,因为涉及四种不同的方法。
供参考,这里是涉及的代码:
MethodNode
:
@Deprecated
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
if (api >= Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, desc));
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if (api < Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, desc, itf));
}
然后在superclass里面有
@Deprecated
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
if (api >= Opcodes.ASM5) {
boolean itf = opcode == Opcodes.INVOKEINTERFACE;
visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc);
}
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if (api < Opcodes.ASM5) {
if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
throw new IllegalArgumentException(
"INVOKESPECIAL/STATIC on interfaces require ASM 5");
}
visitMethodInsn(opcode, owner, name, desc);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
更改之前,仅存在不带 itf
参数的方法。重载版本是新的。
仔细看可以发现,所有委托的效果是当API < 5时,不管你调用哪一个,最终都会调用旧的方法。如果您调用新方法,它将在委派之前验证 itf
参数。当 API >= 5 时,它最终会调用新方法,而不管你调用这两个中的哪一个。如果您调用旧方法,它将在委派之前为 itf
选择一个默认值。
所以它不是忽略方法调用,它只是委托给正确的实现。
这是 MethodNode
visitMethodInsn
方法的主体 class:
@Override
public void visitMethodInsn(
final int opcode,
final @InternalForm String owner,
final @Identifier String name,
final @MethodDescriptor String descriptor,
final boolean isInterface) {
if (api < Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, descriptor, isInterface));
}
如您所见,如果 asm api 版本低于 5
,则字节码不会添加到 instructions list
。背后的原因是什么?
对 Gitlab 历史的一些调查表明,有问题的代码是在 this commit 中添加的,提交消息为 Added support for invokespecial and invokestatic on interfaces.
Java 8 引入了在接口中定义非抽象方法的能力,称为默认方法。在字节码级别,更改的效果是您现在可以使用接口方法以及带有 invokespecial
和 invokestatic
指令的 class 方法。
在Java8之前,在生成字节码的时候,可以简单的通过指令判断常量池入口的类型:如果操作码是invokeinterface
,生成一个InterfaceMethod
条目,否则生成一个 Method
条目。在Java8中,这已经不可能了,因为invokespecial
和invokestatic
是有歧义的,这意味着用户需要能够显式传入方法是接口方法还是不是。这意味着他们必须向几乎所有方法 api 添加一个额外的参数。
但是,他们不想破坏向后兼容性,这意味着他们需要保留具有旧签名的方法(即没有 itf 参数)。这些方法将转发到新的方法,itf
默认为 true
用于 invokeinterface
指令,而 false
否则,这似乎是一个合理的默认值。这就是您在上面看到的 supercall 所做的。我不确定为什么 API < 5 开关在那里,但我怀疑它要么是为了确保向后兼容性,要么是为了打破他们的方法调度方案中的无限循环。
附带说明一下,MethodNode
作为主要代码重组的一部分在大约 8 个月前被删除,因此您不会在最新版本的 ASM 中看到它。
编辑:我看到您对方法委托感到困惑。这非常棘手,因为涉及四种不同的方法。
供参考,这里是涉及的代码:
MethodNode
:
@Deprecated
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
if (api >= Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, desc));
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if (api < Opcodes.ASM5) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
instructions.add(new MethodInsnNode(opcode, owner, name, desc, itf));
}
然后在superclass里面有
@Deprecated
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
if (api >= Opcodes.ASM5) {
boolean itf = opcode == Opcodes.INVOKEINTERFACE;
visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc);
}
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if (api < Opcodes.ASM5) {
if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
throw new IllegalArgumentException(
"INVOKESPECIAL/STATIC on interfaces require ASM 5");
}
visitMethodInsn(opcode, owner, name, desc);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
更改之前,仅存在不带 itf
参数的方法。重载版本是新的。
仔细看可以发现,所有委托的效果是当API < 5时,不管你调用哪一个,最终都会调用旧的方法。如果您调用新方法,它将在委派之前验证 itf
参数。当 API >= 5 时,它最终会调用新方法,而不管你调用这两个中的哪一个。如果您调用旧方法,它将在委派之前为 itf
选择一个默认值。
所以它不是忽略方法调用,它只是委托给正确的实现。