Estoy tratando de compilar y cargar código Java generado dinámicamente durante el tiempo de ejecución. Dado que tanto ClassLoader::defineClass como Unsafe::defineAnonymousClass tienen serios inconvenientes en este escenario, intenté usar clases ocultas a través de Lookup::defineHiddenClass en su lugar. Esto funciona bien para todas las clases que intenté cargar, excepto aquellas que llaman a expresiones lambda o contienen clases anónimas.
Llamar a una expresión lambda arroja la siguiente excepción:
Exception in thread "main" java.lang.NoClassDefFoundError: tests/HiddenClassLambdaTest$LambdaRunner/0x0000000800c04400 at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:22) Caused by: java.lang.ClassNotFoundException: tests.HiddenClassLambdaTest$LambdaRunner.0x0000000800c04400 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519) ... 1 moreLa ejecución de código que instancia una clase anónima arroja el siguiente error:
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack Exception Details: Location: tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400.run()V @5: invokespecial Reason: Type 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' (current frame, stack[2]) is not assignable to 'tests/HiddenClassLambdaTest$LambdaRunner' Current Frame: bci: @5 flags: { } locals: { 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' } stack: { uninitialized 0, uninitialized 0, 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' } Bytecode: 0000000: bb00 1159 2ab7 0013 4cb1 at java.base/java.lang.ClassLoader.defineClass0(Native Method) at java.base/java.lang.System$2.defineClass(System.java:2193) at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2446) at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2427) at java.base/java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2133) at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:25)Este es un breve ejemplo que recrea el problema:
import java.lang.invoke.MethodHandles; public class HiddenClassLambdaTest { /** This class is to be loaded and executed as hidden class */ public static final class LambdaRunner implements Runnable { @Override public void run() { Runnable runnable = () -> System.out.println("Success"); runnable.run(); } } public static void main(String[] args) throws Throwable { // Path to the class file of the nested class defined above String nestedClassPath = HiddenClassLambdaTest.class.getTypeName().replace('.','/') + "$LambdaRunner.class"; // Class file content of the LambdaRunner class byte[] classFileContents = HiddenClassLambdaTest.class.getClassLoader().getResourceAsStream(nestedClassPath).readAllBytes(); Class<?> lambdaRunnerClass = MethodHandles.lookup().defineHiddenClass(classFileContents, true).lookupClass(); Runnable lambdaRunnerInstance = (Runnable) lambdaRunnerClass.getConstructor().newInstance(); lambdaRunnerInstance.run(); } }Ya intenté compilar y ejecutar el código con diferentes JDK, usando diferentes formas de crear nuevas instancias de la clase oculta, buscando errores en https://bugs.openjdk.java.net/ , jugando con el código de bytes en sí y varios otras cosas. No soy un experto en los aspectos internos de Java, por lo que no estoy seguro de haber entendido correctamente el JEP que introdujo las clases ocultas.
¿Estoy haciendo algo mal, es simplemente imposible o es un error?
Editar: Los estados JEP
La migración debe tener en cuenta lo siguiente: Para invocar métodos privados de instancias anidadas desde el código de una clase oculta, use invoquevirtual o invocarinterfaz en lugar de invocar especial. El código de bytes generado que usa invocar especial para invocar un método de instancia de compañero de anidamiento privado fallará en la verificación. invocar especial solo debe usarse para invocar constructores anidados privados.
Este podría ser el problema de la clase anónima. ¿Hay alguna forma de compilar el código de manera que se evite la función de invocación especial en el código de bytes?