How to analyse a NoClassDefFoundError caused by an ignored ExceptionInInitializerError?

tangens picture tangens · Feb 5, 2010 · Viewed 8.4k times · Source

Today I spent my afternoon with analysing a NoClassDefFoundError. After verifying the classpath again and again, it turned out that there was a static member of a class that threw an Exception that was ignored the first time. After that every use of the class throw a NoClassDefFoundError without a meaningful stacktrace:

Exception in thread "main" java.lang.NoClassDefFoundError: 
    Could not initialize class InitializationProblem$A
    at InitializationProblem.main(InitializationProblem.java:19)

That's all. No more lines.

Reduced to the point, this was the problem:

public class InitializationProblem {
    public static class A {
        static int foo = 1 / 0;
        static String getId() {
            return "42";
        }
    }

    public static void main( String[] args ) {
        try {
            new A();
        }
        catch( Error e ) {
            // ignore the initialization error
        }

        // here an Error is being thrown again,
        // without any hint what is going wrong.
        A.getId();
    }
}

To make it not so easy, all but the last call of A.getId() was hidden somewhere in the initialization code of a very big project.

Question:

Now that I've found this error after hours of trial and error, I'm wondering if there is a straight forward way to find this bug starting from the thrown exception. Any ideas on how to do this?


I hope this question will be a hint for anyone else analysing an inexplicable NoClassDefFoundError.

Answer

Geoff Reedy picture Geoff Reedy · Feb 6, 2010

Really, you shouldn't ever ever catch Error, but here's how you can find initializer problems wherever they might occur.

Here's an agent that will make all ExceptionInInitializerErrors print the stack trace when they are created:


import java.lang.instrument.*;
import javassist.*;
import java.io.*;
import java.security.*;

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++) {
          ctors[i].insertAfter("this.printStackTrace();");
        }
        return klass.toBytecode();
      } else {
        return null;
      }
    } catch (Throwable t) {
      return null;
    }
  }
}

It uses javassist to modify the classes. Compile and put it in a jar file with javassist classes and the following MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: InitializerLoggingAgent

Run your app with java -javaagent:agentjar.jar MainClass and every ExceptionInInitializerError will be printed even if it is caught.