What bytecode library when controlling line numbers?
Asked Answered
F

2

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 :).

Flock answered 18/2, 2011 at 13:31 Comment(2)
Most decompilers can printout in the code (as comments) where the original lines of code were. You could parse this and rearrange the code. The problem you might find is that the decompiled code may not be in the same order because it is not exactly the same.Heligoland
Decompilers are not what I are aming at. I need this information at runtime to generate new byte code.Flock
R
5

With asm, you can use the methods visitSource and visitLineNumber to create this debugging information in the generated class.

Edit: Here is a minimal example:

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

Running this generates a class containing a main method that throws a RuntimeException just to see the line number in the stack trace. First lets see what a disassembler makes of this:

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

So this class was compiled from a txt file that does not exist :), the LineNumberTable says that the bytecode starting at offset 0 corresponds to line 123 of this imaginary file. Running this file shows that this file and linenumber is also contained in the stack trace:

$ java -cp target/classes/ test.Test
Exception in thread "main" java.lang.RuntimeException
        at test.Test.main(this/file/does/not/exist.txt:123)
Rasmussen answered 18/2, 2011 at 16:25 Comment(0)
L
2

BCEL has classes LineNumber and LineNumberTable that represent the line number information in a classfile. By the looks of it, you can create and set the table for some class that you are code generating. Presumably, the information gets written out to the class file.

Lentha answered 18/2, 2011 at 15:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.