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

Hans :

Abstract

I am reading class files using ASM, and my MethodVisitor gets a strange argument when visiting an enum: The owner argument to visitMethodInsn is supposed to be an internal name (e.g., mre/DoStuff), but for an enum, I get an owner in the form of an array descriptor, e.g., [Lmre/Stuff;.

Explanation with Example

A condensed Groovy version of how I am using the ClassReader, ClassVisitor, and MethodVisitor is the following:

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)
}

When using this with, e.g., the mre/OneClass.class from this example:

class OtherClass { void run() {} }

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

... then I get the expected internal name argument mre/OtherClass for the run method call:

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

However, when run on the mre/OneEnum.class of this enum:

enum OneEnum {a, b}

... then I unexpectedly get a descriptor argument [Lmre/OneEnum; to a clone method visitation:

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

While this inconsistency seems like a bug to me, I am wondering whether I am missing something. I have tried toggling the generated byte code version between 7,8, and 11, but it seems to make no difference.

Question

So, in a nutshell: Am I using the visitors correctly, and is my confusion about the descriptor argument for the enum justified?

Holger :

The receiver of a method invocation can be an array type.

To demonstrate it without using the ASM libary:

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();
    }
}

prints

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
}

which shows that both clone() calls, the one in example on a string array and the one in the compiler-generated values() method on an enum array, use the array type as method receiver.

Note that array types also may appear in class literals (String[].class), type casts, and as the second argument to the instanceof operator. A type cast to an array type does already happen in the shown code, right after the clone() invocations. In all these cases, the instruction will refer to a CONSTANT_Class_info pool entry whose internal name will be an array signature.

Consider §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.)

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=401228&siteId=1