11

I need to generate new classes (via generation of java byte code) from existing classes. I will analyse the body (expressions) of the methods of a class. The expressions will determine what code I will generate.

For me it is importand to set the source file for the new classes (same as base java file) as well as controlling line numbers (when an exception is thrown the stacktrace should contain line numbers of the base java file).

Example: I have the file BaseClass.java. The compiler generates a BaseClass.class from this. I'd like to analyse this class file and generate the byte codes for a GeneratedClass.class. When at c an exception is thrown the stacktrace should contain "BaseClass.java line 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:}

My question: are there libraries that support this requirement? Javassist, ASM or BCEL? What to use for this purpose? Hints how to do it or example code would be especially helpfull.

Edit: Hints what library NOT to use because the requirement can NOT be fullfiled would be helpfull, too :).

4

2 に答える 2

5

asmでは、メソッドvisitSourceおよびvisitLineNumberを使用して、生成されたクラスでこのデバッグ情報を作成できます。

編集:これは最小限の例です:

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();
    }
}

これを実行すると、スタック トレースの行番号を確認するためだけに RuntimeException をスローする main メソッドを含むクラスが生成されます。まず、逆アセンブラがこれをどうするか見てみましょう:

$ 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
}

したがって、このクラスは存在しない txt ファイルからコンパイルされたものです :)、LineNumberTable は、オフセット 0 で始まるバイトコードがこの架空のファイルの 123 行目に対応することを示しています。このファイルを実行すると、このファイルと行番号がスタック トレースにも含まれていることがわかります。

$ java -cp target/classes/ test.Test
Exception in thread "main" java.lang.RuntimeException
        at test.Test.main(this/file/does/not/exist.txt:123)
于 2011-02-18T16:25:19.147 に答える
2

BCEL には、クラス ファイル内の行番号情報を表すクラス LineNumber および LineNumberTable があります。見た目では、コードを生成しているクラスのテーブルを作成して設定できます。おそらく、情報はクラスファイルに書き出されます。

于 2011-02-18T15:07:37.340 に答える