ASM MethodVisitor::visitMethodInsn 在读取枚举时获取描述符 - 错误或错误使用?

ASM MethodVisitor::visitMethodInsn gets descriptor when reading enum - bug or wrong usage?

摘要

我正在使用 ASM 读取 class 文件,我的 MethodVisitor 在访问枚举时得到一个奇怪的参数: visitMethodInsnowner 参数 (例如,mre/DoStuff),但对于枚举,我得到数组描述符形式的 owner,例如,[Lmre/Stuff;.

举例说明

我如何使用 ClassReaderClassVisitorMethodVisitor 的浓缩 Groovy 版本如下:

package mre

import org.objectweb.asm.*
import java.nio.file.Paths

class ClassTracer extends ClassVisitor {
    ClassTracer() { super(Opcodes.ASM8) }

    @Override
    void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        println "C:visit($version, $access, $name, $signature, $superName, $interfaces)"
    }

    @Override
    MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        println "C:visitMethod($access, $name, $descriptor, $signature, $exceptions)"
        new MethodTracer(super.visitMethod(access, name, descriptor, signature, exceptions))
    }
}

class MethodTracer extends MethodVisitor {
    MethodTracer(MethodVisitor parent) { super(Opcodes.ASM8, parent) }

    @Override
    void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        println "M:visitMethodInsn($opcode, $owner, $name, $descriptor, $isInterface)"
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
    }
}

static void main(String[] args) {
    if (!args) throw new IllegalArgumentException(("Need class file path argument"))
    new ClassReader(Paths.get(args[0]).toFile().bytes).accept(new ClassTracer(), ClassReader.SKIP_FRAMES)
}

将其与此示例中的 mre/OneClass.class 一起使用时:

class OtherClass { void run() {} }

class OneClass {
    void runOther() {
        new OtherClass().run();
    }
}

... 然后我得到预期的内部名称参数 mre/OtherClass 用于 run 方法调用:

M:visitMethodInsn(182, mre/OtherClass, run, ()V, false)

然而,当 运行 在这个枚举的 mre/OneEnum.class 上时:

enum OneEnum {a, b}

... 然后我意外地得到一个描述符参数 [Lmre/OneEnum; 到克隆方法访问:

M:visitMethodInsn(182, [Lmre/OneEnum;, clone, ()Ljava/lang/Object;, false)

虽然这种不一致对我来说似乎是一个错误,但我想知道我是否遗漏了什么。我曾尝试在 7,8 和 11 之间切换生成的字节码版本,但似乎没有什么区别。

问题

所以,简而言之:我是否正确使用了访问者,我对枚举的描述符参数的困惑是否合理?

方法调用的接收者可以是数组类型。

在不使用 ASM 库的情况下进行演示:

public class ArrayMethodCall {
    enum SomeEnum { ;
        public static String[] example(String[] array) {
            return array.clone();
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException {
        Path javap = Paths.get(System.getProperty("java.home"), "bin", "javap");
        new ProcessBuilder(
                javap.toString(), "-c",// "-v",
                "-cp", System.getProperty("java.class.path"),
                "ArrayMethodCall$SomeEnum"
        ).inheritIO().start().waitFor();
    }
}

打印

Compiled from "ArrayMethodCall.java"
final class ArrayMethodCall$SomeEnum extends java.lang.Enum<ArrayMethodCall$SomeEnum> {
  public static ArrayMethodCall$SomeEnum[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[LArrayMethodCall$SomeEnum;
       3: invokevirtual #2                  // Method "[LArrayMethodCall$SomeEnum;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[LArrayMethodCall$SomeEnum;"
       9: areturn

  public static ArrayMethodCall$SomeEnum valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class ArrayMethodCall$SomeEnum
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class ArrayMethodCall$SomeEnum
       9: areturn

  public static java.lang.String[] example(java.lang.String[]);
    Code:
       0: aload_0
       1: invokevirtual #7                  // Method "[Ljava/lang/String;".clone:()Ljava/lang/Object;
       4: checkcast     #8                  // class "[Ljava/lang/String;"
       7: areturn

  static {};
    Code:
       0: iconst_0
       1: anewarray     #4                  // class ArrayMethodCall$SomeEnum
       4: putstatic     #1                  // Field $VALUES:[LArrayMethodCall$SomeEnum;
       7: return
}

这表明两个 clone() 调用,在 example 中对字符串数组的调用和在编译器生成的 values() 方法中对枚举数组的调用,都使用数组输入方法接收者。

请注意,数组类型也可能出现在 class 文字 (String[].class)、类型转换中,并作为 instanceof 运算符的第二个参数。在 clone() 调用之后,显示的代码中确实已经发生了对数组类型的类型转换。在所有这些情况下,指令将引用一个 CONSTANT_Class_info pool entry,其内部名称将是一个数组签名。

考虑 §5.1 of the JVM specification:

A symbolic reference to a class or interface is derived from a CONSTANT_Class_info structure (§4.4.1). Such a reference gives the name of the class or interface in the following form:

  • For a nonarray class or an interface, the name is the binary name (§4.2.1) of the class or interface.

  • For an array class of n dimensions, the name begins with n occurrences of the ASCII [ character followed by a representation of the element type:

    • If the element type is a primitive type, it is represented by the corresponding field descriptor (§4.3.2).

    • Otherwise, if the element type is a reference type, it is represented by the ASCII L character followed by the binary name of the element type followed by the ASCII ; character.

Whenever this chapter refers to the name of a class or interface, the name should be understood to be in the form above. (This is also the form returned by the Class.getName method.)