Your cup of Byte-Code-Re-Engineering, BCEL style

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on June 17, 2007 · 6 mins read

Your cup of Byte-Code-Re-Engineering, BCEL style
In case you are wondering what this means? To put it simply, its a way to modify a class at runtime using the Jakarta's BCEL. When I say modify, I mean altering the behaviour of the class by inserting instructions dynamically. The modus operandi is very simple:

  • Load the class with BCEL
  • Modify it
  • Save it so that the original .class file is over-written with the modified one
  • Use the modified class

As annotations start getting popular and the scene is cleared for Aspect Oriented Programming and the likes, byte code re-engineering assumes significance.
But it can be very cumbersome and a bit dangerous as it might lead to very unpredictable results. Nonetheless, it is a very powerful and handy tool.
I will try to brush on how to use the BCEL to achieve our end. I, myself am a novice.
You may start at their official manual, but the examples there doesnt help much. Also, you you go through the extensive Javadocs that can be had by downloading the binaries.
The first thing to do is to definitely use the immensely handy utility BCELifier which translates Java-code we understand to the somewhat difficult to follow BCEL grammar(which is closer to the JVM's instruction set). Write a small class with a couple of methods and a few simple printlns, etc and do a:

BCELifier.main(new String[] {TestClass.class.getName()});

This class will print back the instructions in order to create the TestClass from scratch.
While working with BCEL, you will have to get used to the fact that most of these classes do not work directly with our own dear java.lang.Class. Instead you have to get a org.apache.bcel.classfile.JavaClass in order to bring home the bacon. But you will be thankful for that in a while. Confused? I will tell you the reason: when you want to modify .class files dynamically, the last thing you would want is to mess-around with Java's default class-loaders. Once a class is loaded in the JVM's memory, it is very cumbersome to dislodge it and load it afresh. So, the trick is not to load the class in the JVM until you have BCEL-ed it and saved it. And the only way to prevent it is to refrain from temptation of doing a new TestClass() or a TestClass.class or the like.
Here is how to get an instance of JavaClass:

String fullyQualifiedClassName = ...;
JavaClass clazz = Repository.lookupClass(fullyQualifiedClassName);

Before we go into specifics, a small gyan:

  • To create a class file from scratch or to modify an existing one, you use ClassGen
  • To create/modify a method, you use MethodGen
  • To create a JVM instruction, you use InstructionFactory

InstructionFactory comes with many constructors, but this one is most handy:
new InstructionFactory(classGen, constantPoolGen);
In case you are wondering what is constantPoolGen, it represents the class file's ConstantPool area. Refer to JVM Specs for details.
You can get a constantPoolGen thus:
ConstantPoolGen constantPoolGen = new ConstantPoolGen(clazz.getConstantPool());
That done, you would want to modify it. Refer to DestructiveDecorator. You can find the source code here.
Here are some excerpts:

           JavaClass clazz;
String classname = clazz.getClassName();
ClassGen classGen = new ClassGen(clazz);
ConstantPoolGen constantPoolGen = new ConstantPoolGen(clazz.getConstantPool());
InstructionFactory fact = new InstructionFactory(classGen, constantPoolGen);
Method[] methodArray = clazz.getMethods();
//get each method from the array and modify it
.....
classGen.setConstantPool(constantPoolGen);
   //get the modified class
JavaClass morphed = classGen.getJavaClass();

This is how you would modify methods:

    //create a new, empty InstructionList
InstructionList il = new InstructionList();
    //go on appending instructions to it
    //This particular instruction is equivalent to:
    //System.out.println("XXXXXXXXX");
il.append(fact.createFieldAccess("java.lang.System",
"out", new ObjectType("java.io.PrintStream"),
    Constants.GETSTATIC));
il.append(new PUSH(fact.getConstantPool(), "XXXXXXXXX"));
il.append(fact.createInvoke("java.io.PrintStream", "println", Type.VOID,
new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
    //after you are done, insert the InstructionList
    //at the top of the existing one
MethodGen mg = new MethodGen(method, classname, fact.getConstantPool());
mg.getInstructionList().insert(il);
mg.setMaxLocals();
mg.setMaxStack();
//get the modified method
Method morphedMethod = mg.getMethod();
il.dispose();
//set the modified method in the factory:
fact.getClassGen().setMethodAt(morphedMethod, position);

Now you are ready to save the modified class file:

//modify the JavaClass instance
//JavaClass clazz;
//get the path of the .class file on the hard-disk
String path = clazz .getRepository().getClassPath().
getClassFile(morphedClass.getClassName()).getPath();
//this writes the modified byte-code to the same .class
//of course this wont work in case the .class is in a jar
clazz .dump(path);

The dirtiest work is done in DestructiveDecorator.morphMethod() where I am adding a sysout and then throwing a RuntimeException. Perusal of BCELifier would ease your mind and the mists will lift clear.
I am sure, if you had the guts to stick on this far, you have ceratinly gained something. Atleast you sure could introduce a few nasty sangs in a perfectly working piece of Java code.
Here goes the rest of the gang, RuntimeDecorator:
And the leader of the gang, the DynamicClassDecorator:
And finally the test-code:

//first morph the class file before using it
new DynamicClassDecorator(new DestructiveDecorator()).saveModifiedClass("com.exp.becl.demo.TestClass");
new TestClass().dummy();

I can promise you that the results would be disastrous!
Happy JVM crash.
BTW, now you should be guessing what the blokes do when the write an EJB Container, JSP container or Aspect Oriented Programming for that matter... the stuff they do at the background stands exposed.