ASM MethodVisitor :: visitMethodInsn obtiene al leer el descriptor de enumeración - fallo o mal uso?

Hans:

Abstracto

Estoy leyendo los archivos de clase utilizando ASM, y mi MethodVisitorconsigue un argumento extraño cuando se visita una enumeración: El ownerargumento de que visitMethodInsn se supone que es un nombre interno (por ejemplo, mre/DoStuff), pero para una enumeración, me sale un ownerbajo 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, ClassVisitory MethodVisitores 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.classde este ejemplo:

class OtherClass { void run() {} }

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

... entonces consigo el argumento de nombre interno esperada mre/OtherClasspara la runllamada al método:

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

Sin embargo, cuando se ejecuta en el mre/OneEnum.classde 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?

Holger:

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 exampleen 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 instanceofoperador. 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_infoentrada 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_infoestructura ( §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 Lcaracteres 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.getNamemétodo).

Supongo que te gusta

Origin http://10.200.1.11:23101/article/api/json?id=401331&siteId=1
Recomendado
Clasificación