1
votes

In a large number of classes derived from the common ancestor class I have the method something() whose behavior I want to modify to measure execution time. I define two additional methods in the common ancestor class

// in the common ancestor class:
// this method is defined in all derived classes
public void something() {
    System.out.println("something{");
    System.out.println("something}");
}
// the body of this method will replace the body of something()
public void measuredSomething() {
    System.out.println("measuredSomething{");
    roomForSomething();
    System.out.println("measuredSomething}");

}
// the body of this method will be replaced by the body of something()
public void roomForSomething() {
    // this code will be replaced with something else
    System.out.println("roomForSomething{");
    System.out.println("roomForSomething}");
}

And I do the replacement with Javassist:

// someClass is known
String superClassName = someClass.getName();
String className = superClassName + "__proxy";
CtClass ctSuperClass = ClassPool.getDefault().get(superClassName);
CtClass ctClass = ClassPool.getDefault().makeClass(className);
ctClass.setSuperclass(ctSuperClass);

// roomForSomething := something
CtMethod methodSomething = ctSuperClass.getMethod("something", "()V");
CtMethod methodSomething2 = CtNewMethod.copy(methodSomething, "roomForSomething", ctClass, null);

ctClass.addMethod(methodSomething2);

// something := measuredSomething
// (and measuredSomething() will call roomForSomething())
CtMethod methodMeasuredSomething = ctSuperClass.getMethod("measuredSomething", "()V");
CtMethod methodMeasuredSomething2 = CtNewMethod.copy(methodMeasuredSomething, "something", ctClass, null);

ctClass.addMethod(methodMeasuredSomething2);

someClass = ctClass.toClass();
result = someClass.newInstance();

And it works. But the debugger cannot show the code that executes.

How do I copy the debugging information along with the byte code so that the debugger would show the source that executes?

(It is not possible when the method code is modified, but should be possible in this particular case.)

((I did propose to split the method something() in two methods, in the same way as there are Thread.run() to override, and Thread.start() to invoke. I've been asked to avoid such change.))

UPDATE

The following method gives a bit better results: the debugger stops in the function generated via CtMethod.make() only when it returns from the called function; the generated function is visible in the stack trace; the source of the generated function is not available.

// something := measuredSomething
CtMethod methodSomething = CtMethod.make("public void something() {super.measuredSomething();}", ctClass);
ctClass.addMethod(methodSomething);

// roomForSomething := something
CtMethod methodRoomForSomething = CtMethod.make("public void roomForSomething() {super.something();}", ctClass);
ctClass.addMethod(methodRoomForSomething);

It would be really good if it was possible to show the source of the generated function.

1

1 Answers

0
votes

I don't think that you can debug directly the injected code with Javassist as this library can only modify the bytecode (so actually the source code are not changed). This means that your debugger cannot access the code that you injected because basically it does not exists (is only a graphical way for you to express your bytecode manuipulation).

By the way I had the same problem one and my goal was to debug the method roomForSomething. My solution was this: first declare the method that you want to debug (in your case roomForSomething()) in a class and make it static.

package p.a.c.k.a.g.e;
public class ClassName{
    public static void roomForSomething(){
        // here you can debug
    }
}

After you can just inject with Javassist a call to this static method into the modified method. Even if in the class is not imported the class where you defined the "roomForSomething" you can jsut specify the fully qualified name of the class as I have done into the example.

// roomForSomething := something
CtMethod methodRoomForSomething = CtMethod.make("public void randomName() {p.a.c.k.a.g.e.ClassName.roomForSomething()}", ctClass);
ctClass.addMethod(methodRoomForSomething);

With this solution you can set a breakpoint into the "roomForSomething" code.