Come emettere ed eseguire bytecode Java in fase di runtime?

Sto scrivendo un interprete in Java per un linguaggio specifico del dominio con alcune funzionalità di scripting. Ho già implementato un parser e ora devo fare un back-end. A tal fine, sto pensando di scrivere il mio interprete (sia lavorando con alberi sintattici astratti o con alcuni bytecode personalizzati) sia come target JVM (emetti ed esegui bytecode Java in fase di runtime).

Qualcuno con più esperienza in questo settore potrebbe dire quanto sia fattibile l’approccio di targeting per JVM e quali librerie consiglieresti di utilizzare per l’emissione di bytecode Java?

Ecco un “ciao mondo” funzionante realizzato con ObjectWeb ASM (una libreria che raccomando):

package hello; import java.lang.reflect.Method; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class HelloWorldASM implements Opcodes { public static byte[] compile(String name) { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/HelloWorld", null, "java/lang/Object", null); cw.visitSource("HelloWorld.java", null); { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(4, l0); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitInsn(RETURN); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLocalVariable("this", "Lhello/HelloWorld;", null, l0, l1, 0); mv.visitMaxs(1, 1); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(7, l0); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn(String.format("Hello, %s!", name)); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(8, l1); mv.visitInsn(RETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 0); mv.visitMaxs(2, 1); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } public static class DynamicClassLoader extends ClassLoader { public Class define(String className, byte[] bytecode) { return super.defineClass(className, bytecode, 0, bytecode.length); } }; public static void main(String[] args) throws Exception { DynamicClassLoader loader = new DynamicClassLoader(); Class helloWorldClass = loader.define("hello.HelloWorld", compile("Test")); Method method = helloWorldClass.getMethod("main", String[].class); method.invoke(null, (Object) new String[] {}); } } 

Per generare il codice, ho trovato molto utile il Bytecode Outline per il plug-in Eclipse . Sebbene tu possa usare ASMifier (incluso con ASM) in questo modo:

 ClassReader cr = new ClassReader(new FileInputStream("HelloWorld.class")); cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0); 

In fase di runtime, se è necessario ottenere l’object Class per la class creata, è ansible caricare la class estendendo un programma di caricamento classi e pubblicando (tramite un altro metodo, ad esempio) il metodo defineClass e fornendo la class come array di byte, come elencato nell’esempio.

Puoi anche gestire la class creata con un’interfaccia, come in questo esempio:

 package hello; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class HelloWorldPlugin implements Opcodes { public static interface Plugin { void sayHello(String name); } public static byte[] compile() { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/MyClass", null, "java/lang/Object", new String[] { "hello/HelloWorldPlugin$Plugin" }); cw.visitInnerClass("hello/HelloWorldPlugin$Plugin", "hello/HelloWorldPlugin", "Plugin", ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE); { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(5, l0); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitInsn(RETURN); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l1, 0); mv.visitMaxs(1, 1); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "(Ljava/lang/String;)V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(9, l0); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("Hello, "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(10, l1); mv.visitInsn(RETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l2, 0); mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1); mv.visitMaxs(4, 2); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } public static class DynamicClassLoader extends ClassLoader { public DynamicClassLoader(ClassLoader parent) { super(parent); } public Class define(String className, byte[] bytecode) { return super.defineClass(className, bytecode, 0, bytecode.length); } }; public static void main(String[] args) throws Exception { DynamicClassLoader loader = new DynamicClassLoader(Thread .currentThread().getContextClassLoader()); Class helloWorldClass = loader.define("hello.MyClass", compile()); Plugin plugin = (Plugin) helloWorldClass.newInstance(); plugin.sayHello("Test"); } } 

Divertiti.

PS: Posso aggiungere commenti al codice se non abbastanza chiaro. Non l’ho fatto perché la risposta è già troppo lunga. Tuttavia, il mio suggerimento per te è quello di provare il debugging.

Posso suggerire di dare un’occhiata a queste librerie:

  • CGLIB
  • BCEL
  • asm
  • Javassist

Scopri Jetbrains MPS . Costruito da ragazzi che ci hanno portato IDEA.

Da una prospettiva diversa, ti chiedo se hai considerato l’utilizzo di XText . Questo è progettato per consentire di creare DSL, editor di codice con completamento del codice, compilatore, generatore di codice e così via. Penso che sia molto bello e abbia una buona documentazione . Vale la pena dare un’occhiata a questo. Puoi creare facilmente un compilatore basato su di esso per il tuo DSL.