ASM MethodVisitor::visitMethodInsn 在读取枚举时获取描述符 - 错误或错误使用?
ASM MethodVisitor::visitMethodInsn gets descriptor when reading enum - bug or wrong usage?
摘要
我正在使用 ASM 读取 class 文件,我的 MethodVisitor
在访问枚举时得到一个奇怪的参数: visitMethodInsn
的 owner
参数 (例如,mre/DoStuff
),但对于枚举,我得到数组描述符形式的 owner
,例如,[Lmre/Stuff;
.
举例说明
我如何使用 ClassReader
、ClassVisitor
和 MethodVisitor
的浓缩 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.)
摘要
我正在使用 ASM 读取 class 文件,我的 MethodVisitor
在访问枚举时得到一个奇怪的参数: visitMethodInsn
的 owner
参数 mre/DoStuff
),但对于枚举,我得到数组描述符形式的 owner
,例如,[Lmre/Stuff;
.
举例说明
我如何使用 ClassReader
、ClassVisitor
和 MethodVisitor
的浓缩 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.)