Monday, April 4, 2016

Javassist and code instrumentation

Looking for a way to trace an Error in a static initializer block, I have stumbled upon this example of how to dynamically redefine an ExceptionInInitializerError :
import java.lang.instrument.*;
import javassist.*;

public class InitializerLoggingAgent implements ClassFileTransformer {
  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new InitializerLoggingAgent(), true);

  private final ClassPool pool = new ClassPool(true);

  public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)  {
    try {
      if (className.equals("java/lang/ExceptionInInitializerError")) {
        CtClass klass = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtConstructor[] ctors = klass.getConstructors();
        for (int i = 0; i < ctors.length; i++) {
        return klass.toBytecode();
      } else {
        return null;
    } catch (Throwable t) {
      return null;
Download javassist zip from here and explode it
Download Ant (Ant? yes, Ant!) from here
Build javassist source and put the javassist.jar file in the lib folder of your project, add it to build classpath.
Follow instructions to jar your JavaAgent with a MANIFEST.MF containing
Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent
Can-Retransform-Classes: true

run java -javaagent:agentjar.jar MainClass
It works! Your Error is intercepted and stacktrace is printed! No more silently failing static initializers!
If you forget to add "Can-Retransform-Classes: true" in the MANIFEST.MF, you get the infamous "java.lang.UnsupportedOperationException: adding retransformable transformers is not supported in this environment"

No comments: