"LinkageError: intento de definición de clase duplicada" al instrumentar dinámicamente clases de Java con ASM

0

Escribí un javaagent por mí mismo para instrumentar dinámicamente las clases de Java con ASM (no estoy usando COMPUTE_MAXSo COMPUTE_FRAMESde ASM, lo hago manualmente por mí mismo). En realidad, solo estoy tratando de usar un gran bloque try-catch para métodos no constructores para capturar las Excepciones o Errores no detectados y registrar tales eventos (mi código es en realidad la versión revisada del código en esta pregunta ).

Sin embargo, cuando intenté usar mi javaagent en el proceso de prueba de un proyecto de código abierto joda-time , se produjo el siguiente error:

org.apache.maven.surefire.testset.TestSetFailedException: org.joda.time.TestAllPackages
    at org.apache.maven.surefire.junit.JUnitTestSet.execute(JUnitTestSet.java:116)
    at org.apache.maven.surefire.junit.JUnit3Provider.executeTestSet(JUnit3Provider.java:140)
    at org.apache.maven.surefire.junit.JUnit3Provider.invoke(JUnit3Provider.java:113)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:379)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:340)
    at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:413)
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "org/joda/time/DateTimeZone"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.joda.time.TestChronology.<clinit>(TestChronology.java:47)
    at org.joda.time.TestAll.suite(TestAll.java:37)
    at org.joda.time.TestAllPackages.suite(TestAllPackages.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.common.junit3.JUnit3Reflector.createInstanceFromSuiteMethod(JUnit3Reflector.java:157)
    at org.apache.maven.surefire.common.junit3.JUnit3Reflector.constructTestObject(JUnit3Reflector.java:124)
    at org.apache.maven.surefire.junit.JUnitTestSet.execute(JUnitTestSet.java:75)
    ... 6 more

A mi entender, cada vez que sun/misc/Launcher$AppClassLoaderse intenta cargar una clase, se ingresa el transformmétodo de ClassFileTransformer. La clase finalmente se carga después de que modifiqué la clase y devolví la matriz de bytes modificada. Por lo tanto, cada clase debe cargarse solo una vez.

Además traté de imprimir el nombre de la clase y el cargador que lo carga al principio del transformmétodo, encontré que org/joda/time/DateTimeZoneaparece solo una vez, lo cual es consistente con mi entendimiento.

Así que ahora lo único que no concuerda con mi comprensión es el error. ¿Por qué sun/misc/Launcher$AppClassLoader attempted duplicate class definitioncon mi agente? Todo salió tan mal cuando eliminé mi -javaagentopción.

1
  • 1
    En el código de la pregunta vinculada, no veo nada sospechoso. Dado que está introduciendo una dependencia MyRecorderen la clase instrumentada, vale la pena intentar inyectar solo un System.out.println(…);, para ver si hace una diferencia.
    Holger
    14 de oct a las 8:49
1

Para calcular tramas, ASM necesita encontrar una superclase común de varias clases en los objetivos de las instrucciones de salto. Para hacerlo, ASM carga las clases para explorar su jerarquía. Si carga una clase de esta manera mientras también está instrumentada durante su primera carga, la clase ya se cargará después de la instrumentación y terminará con este error.

Para evitar esto, puede anular el método getCommonSuperClass de ClassWriter de ASM. Debería analizar los archivos de clase de estas clases pasados ​​al método en lugar de cargarlos. Si desea una implementación inmediata de esto, puede usar Byte Buddy, que expone ASM y resuelve su ClassWriter de esa manera.

2
  • 2
    Cuando el OP todavía está usando el enfoque descrito en la pregunta vinculada, no está usando la COMPUTE_FRAMESopción. Entonces este no puede ser el problema. Que yo sepa, las dependencias circulares no se resuelven cargándolo de nuevo de todos modos. Más bien, obtienes un NoClassDefFoundErrorcuando intentas cargar la clase que solo estás instrumentando.
    Holger
    14 de oct a las 8:41
  • Sí, no estoy usando COMPUTE_FRAMES, agregaré esa información a la pregunta.
    Instein
    14 oct a las 16:21