Abstracto
Estoy leyendo los archivos de clase utilizando ASM, y mi MethodVisitor
consigue un argumento extraño cuando se visita una enumeración: El owner
argumento de que visitMethodInsn
se supone que es un nombre interno (por ejemplo, mre/DoStuff
), pero para una enumeración, me sale un owner
bajo la forma de un descriptor de matriz, por ejemplo, [Lmre/Stuff;
.
Explicación con el Ejemplo
Una versión condensada maravilloso de cómo estoy usando el ClassReader
, ClassVisitor
y MethodVisitor
es la siguiente:
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)
}
Cuando se utiliza este con, por ejemplo, la mre/OneClass.class
de este ejemplo:
class OtherClass { void run() {} }
class OneClass {
void runOther() {
new OtherClass().run();
}
}
... entonces consigo el argumento de nombre interno esperada mre/OtherClass
para la run
llamada al método:
M:visitMethodInsn(182, mre/OtherClass, run, ()V, false)
Sin embargo, cuando se ejecuta en el mre/OneEnum.class
de esta enumeración:
enum OneEnum {a, b}
... entonces inesperadamente consigo un argumento descriptor [Lmre/OneEnum;
a una visita método clone:
M:visitMethodInsn(182, [Lmre/OneEnum;, clone, ()Ljava/lang/Object;, false)
Mientras que esta inconsistencia parece como un insecto a mí, me pregunto si me estoy perdiendo algo. He intentado alternar la versión del código de bytes que se genera entre 7,8 y 11, pero parece que no hay diferencia.
Pregunta
Así, en pocas palabras: ¿Estoy usando los visitantes correctamente, y es mi confusión sobre el argumento descriptor para la enumeración justificado?
El receptor de una invocación de método puede ser un tipo de matriz.
Para demostrar que sin utilizar el libary 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();
}
}
huellas dactilares
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
}
lo que demuestra que ambas clone()
llamadas, el de example
en una matriz de cadenas y el uno en el generado por el compilador values()
método en un array enum, utilizan el tipo de matriz como receptor método.
Tenga en cuenta que los tipos de matriz también pueden aparecer en los literales (clase String[].class
), tipo de moldes, y como segundo argumento al instanceof
operador. Un tipo fundido a un tipo de matriz tiene ya sucede en el código que se muestra, justo después de las clone()
invocaciones. En todos estos casos, la instrucción se referirá a una CONSTANT_Class_info
entrada de la piscina , cuyo nombre interno será una firma matriz.
Considere § 5.1 de la especificación JVM :
Una referencia simbólica a una clase o interfaz se deriva de una
CONSTANT_Class_info
estructura ( §4.4.1 ). Referencia un Tal da el nombre de la clase o interfaz de la siguiente forma:
Para una clase nonarray o una interfaz, el nombre es el nombre binario ( §4.2.1 ) de la clase o interfaz.
Para una clase de matriz de n dimensiones, el nombre comienza con n apariciones de la ASCII
[
caracteres seguido de una representación del tipo de elemento:
Si el tipo de elemento es un tipo primitivo, que está representado por el descriptor de campo correspondiente ( §4.3.2 ).
De lo contrario, si el tipo de elemento es un tipo de referencia, que está representado por el ASCII
L
caracteres seguido por el nombre del archivo binario del tipo de elemento seguido por el ASCII;
del carácter.Siempre que este capítulo se refiere al nombre de una clase o interfaz, el nombre se debe entender que estar en la forma anteriormente. (Esta es también la forma devuelto por el
Class.getName
método).