Java Dynamic Instrumentation #2



        Continuing from Java Dynamic Instrumentation #1, this post will cover some more advanced features of the Javassist API.

        In the previous post, we went over the ClassPool, CtClass, and CtMethod classes and how to add code to the beginning and end of a method. In this post, we will cover the dynamic creation and addition of classes, methods, and fields.         In this post, we are going to start with a fairly complex piece of code, and I will then break it down into steps.

HelloWorld.java:

public class HelloWorld {
	public void do1() {
		System.out.println("Hello World!");
	}

	public void do2(String tosay){
		System.out.println(tosay);
	}

}

Inst3.java:

import javassist.*;

public class Inst3 {
	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();	

		CtClass hw_ctc = pool.get("HelloWorld");

		CtClass nhw_ctc = pool.makeClass("NewHelloWorld");
		nhw_ctc.setSuperclass(hw_ctc);

		CtField f1 = CtField.make("private String name;", nhw_ctc);
		nhw_ctc.addField(f1);

		CtConstructor c = CtNewConstructor.make("public NewHelloWorld(){\n" + 
			"\tname = \"TestName\";" +
			"}\n", nhw_ctc);	
		nhw_ctc.addConstructor(c);

		CtMethod m1 = CtNewMethod.make("public void do3(){\n" +
			"\tSystem.out.println(name);\n" +
			"}\n", nhw_ctc);
		nhw_ctc.addMethod(m1);

		CtMethod m2 = CtNewMethod.make("public void do1(){\n" +
                	"\tSystem.out.println(\"Hello New World\");\n" +
			"\tdo3();\n" +
                	"}\n", nhw_ctc);
                nhw_ctc.addMethod(m2);

		Class hw_class = hw_ctc.toClass();
		Class nhw_class = nhw_ctc.toClass();
		HelloWorld hw = (HelloWorld)hw_class.newInstance();
		HelloWorld nhw = (HelloWorld)nhw_class.newInstance();

		hw.do1();
		hw.do2("Goodbye world");
		System.out.println("-------");
		nhw.do1();
	}
}
$ javac -classpath /path/to/javassist.jar: Inst3.java HelloWorld.java
$ java -cp /path/to/javassist.jar: Inst3
Hello World!
Goodbye world
-------
Hello New World
TestName

        Compared to the code from the previous post, only a few lines are the same/similar, while the majority of the code is new. The first new lines of code are the pair consisting of CtClass nhw_ctc = pool.makeClass("NewHelloWorld") and nhw_ctc.setSuperclass(hw_ctc). They create a new class called NewHelloWorld and set it to extend from the HelloWorld class respectively.         

The next two lines, `CtField f1 = CtField.make("private String name;", nhw_ctc)` and `nhw_ctc.addField(f1)` are used to create and add a new field, `private String name`, to the `NewHelloWorld` class.         
Next is a block of code:
CtConstructor c = CtNewConstructor.make("public NewHelloWorld(){\n" + 
	"\tname = \"TestName\";" +
	"}\n", nhw_ctc);
nhw_ctc.addConstructor(c);

        This block creates and adds a constructor for NewHelloWorld that will the field we just created for every new NewHelloWorld instance.         Next are two blocks of code:

CtMethod m1 = CtNewMethod.make("public void do3(){\n" +
	"\tSystem.out.println(name);\n" +
	"}\n", nhw_ctc);
nhw_ctc.addMethod(m1);

CtMethod m2 = CtNewMethod.make("public void do1(){\n" +
	"\tSystem.out.println(\"Hello New World\");\n" +
	"\tdo3();\n" +
	"}\n", nhw_ctc);
nhw_ctc.addMethod(m2);

        These create two methods and add them both to the NewHelloWorld class. However, one of the methods, void do1() already exists in the super class, HelloWorld. The addition of this new void do1() method causes it to override the one in the super class. Therefore any calls to this method in NewHelloWorld will use the new void do1() instead of the original one.

Now, knowing all of this, the output of the code can be easily explained.</p

Hello World!
Goodbye world
-------
Hello New World
TestName

        The first two lines are what they are because we did not modify the HelloWorld class. However, in the new class NewHelloWorld, we have overrided the void do1() method such that the new one calls System.out.println("Hello New World") and then calls the other new method that we added to NewHelloWorld, void do3(). do3() simply prints out the value of the new field, name, that was added to NewHelloWorld. If you remember, the new constructor that we added to NewHelloWorld sets name to the string “TestName”.         While this all may seem easy now (hopefully), it is important to note the ordering of the code. Because each piece of code that we added is compiled in the corresponding make(java.lang.String src, CtClass declaring) method, the referenced values in the lines of code must already exist before each of these make methods are run, or else a CannotCompileException will be thrown. Because of this, the constructor and do3() had to be created after the field was added because they referenced said field; and do1() had to be created after do3() was added because it made a call to do3().