Qual biblioteca de bytecode ao controlar números de linha?
-
15-11-2019 - |
Pergunta
Preciso gerar novas classes (por meio da geração de código de bytes java) a partir de classes existentes.Analisarei o corpo (expressões) dos métodos de uma classe.As expressões determinarão qual código irei gerar.
Para mim é importante definir o arquivo fonte para as novas classes (igual ao arquivo java base), bem como controlar os números das linhas (quando uma exceção é lançada, o stacktrace deve conter os números das linhas do arquivo java base).
Exemplo:eu tenho o arquivo BaseClass.java.O compilador gera um BaseClass.class disto.Eu gostaria de analisar esse arquivo de classe e gerar os códigos de bytes para um Classe gerada.class.Quando em c uma exceção é lançada e o stacktrace deve conter "BaseClass.java linha 3".
BaseClass.java
1: class BaseClass {
2: void method() {
3: call();
4: }
5:}
GeneratesClaas.class
a: class GeneratedClass {
b: void generatedMethod() {
c: generatedCall();
d: }
e:}
Minha pergunta:existem bibliotecas que suportam esse requisito?Javassist, ASM ou BCEL?O que usar para esse fim?Dicas de como fazer isso ou código de exemplo seriam especialmente úteis.
Editar:Dicas sobre qual biblioteca NÃO usar porque o requisito NÃO pode ser atendido também seria útil :).
Solução
Com asm, você pode usar os métodos visitarFonte e visitLineNumber para criar essas informações de depuração na classe gerada.
Editar: Aqui está um exemplo mínimo:
import java.io.File;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.CheckClassAdapter;
import static org.objectweb.asm.Opcodes.*;
public class App {
public static void main(String[] args) throws IOException {
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter ca = new CheckClassAdapter(cw);
ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "test/Test", null, "java/lang/Object", null);
ca.visitSource("this/file/does/not/exist.txt", null); // Not sure what the second parameter does
MethodVisitor mv = ca.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
Label label = new Label();
mv.visitLabel(label);
mv.visitLineNumber(123, label);
mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "()V");
mv.visitInsn(ATHROW);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
ca.visitEnd();
File target = new File("target/classes/test/");
target.mkdirs();
FileOutputStream out = new FileOutputStream(new File(target, "Test.class"));
out.write(cw.toByteArray());
out.close();
}
}
A execução disso gera uma classe contendo um método principal que lança uma RuntimeException apenas para ver o número da linha no rastreamento de pilha.Primeiro vamos ver o que um desmontador acha disso:
$ javap -classpath target/classes/ -c -l test.Test
Compiled from "this.file.does.not.exist.txt"
public class test.Test extends java.lang.Object{
public static void main(java.lang.String[]);
Code:
0: new #9; //class java/lang/RuntimeException
3: dup
4: invokespecial #13; //Method java/lang/RuntimeException."<init>":()V
7: athrow
8: return
LineNumberTable:
line 123: 0
}
Então essa classe foi compilada a partir de um arquivo txt que não existe :), o LineNumberTable diz que o bytecode começando no offset 0 corresponde à linha 123 desse arquivo imaginário.A execução deste arquivo mostra que este arquivo e número de linha também estão contidos no rastreamento de pilha:
$ java -cp target/classes/ test.Test
Exception in thread "main" java.lang.RuntimeException
at test.Test.main(this/file/does/not/exist.txt:123)
Outras dicas
BCEL possui classes LineNumber e LineNumberTable que representam as informações do número da linha em um arquivo de classe.Pelo que parece, você pode criar e definir a tabela para alguma classe que está gerando código.Presumivelmente, as informações são gravadas no arquivo de classe.