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 引入了在接口中定义非抽象方法的能力,称为默认方法。在字节码级别,更改的效果是您现在可以使用接口方法以及带有 invokespecialinvokestatic 指令的 class 方法。

在Java8之前,在生成字节码的时候,可以简单的通过指令判断常量池入口的类型:如果操作码是invokeinterface,生成一个InterfaceMethod条目,否则生成一个 Method 条目。在Java8中,这已经不可能了,因为invokespecialinvokestatic是有歧义的,这意味着用户需要能够显式传入方法是接口方法还是不是。这意味着他们必须向几乎所有方法 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 选择一个默认值。

所以它不是忽略方法调用,它只是委托给正确的实现。