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?
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.)