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

1 answer


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.


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

All answers to this question, which has the identifier 60998619

The best answer:

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


Compiled from "" 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.)

Last questions

how do i remove the switch on my home screen?
how to edit the JS date and time to update atuomatically?
How to utilize data stored in a multidimensional array
Powermockito not mocking URL constructor in URI.toURL() method
Android Bluetooth LE Scanner only scans when phone's Location is turned on in some devices
docker wordpress container can't connect to mysql container
How can I declare a number in java that is more than 64-bits? [duplicate]
Optaplanner solutionClass entityCollectionProperty should never return null error when simple JSON object passed to controller
Anylogic, get the time a pedestrain is in a queue
How do I fix this syntax issue with my .flex file?
Optimizing query in PHP
How to find the highest number of a column and print two columns of that row in R?
Ideas on “Error: Type is referenced as an interface from”?
JCIFS SmbFile.exists() and SmbFile.isDirectory() return false when it exists and I can listFiles()
PHP total order
Laravel booking system design
neural net - undefined column selected
How to indicate y axis does not start from 0 in ggplot?
Fragments in backStack
Spinner how to change the data