views:

278

answers:

6

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.

+1  A: 

The only hints the error gives are the name of the class and that something went terribly wrong during initialization of that class. So either in one of those static initializers, field initialization or maybe in a called constructor.

The second error has been thrown because the class has not been initialized at the time A.getId() was called. The first initialization was aborted. Catching that error was a nice test for the engineering team ;-)

A promising approach to locate such an error is to initialize the class in a test environment and debug the initialization (single steps) code. Then one should be able to find the cause of the problem.

Andreas_D
A: 

I really don't understand your reasoning. You ask about "find this bug starting from the thrown exception" and yet you catch that error and ignore it ...

Yoni
+2  A: 

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.

There is your problem! Don't ever catch and ignore Error (or Throwable). NOT EVER.

And if you've inherited some dodgy code that might do this, use your favourite code search tool / IDE to seek and destroy the offending catch clauses.

Stephen C
+4  A: 

My advice would be to avoid this problem by avoiding static initializers as much as you can. Because these initializers get executed during the classloading process, many frameworks don't handle them very well, and in fact older VMs don't handle them very well either.

Most (if not all) static initializers can be refactored into other forms, and in general it makes the problems easier to handle and diagnose. As you've discovered, static initializers are forbidden from throwing checked exceptions, so you've got to either log-and-ignore, or log-and-rethrow-as-unchecked, none of which make the job of diagnosis any easier.

Also, most classloaders make one-and-only-one attempt to load a given class, and if it fails the first time, and isn't handled properly, the problem gets effectively squashed, and you end up with generic Errors being thrown, with little or no context.

skaffman
+3  A: 

If you ever see code with this pattern:

} catch(...) {
// no code
}

Find out who wrote it and BEAT THE CRAP OUT OF THEM. I'm serious. Try to get them fired--they do not understand the debugging portion of programming in any way, shape or form.

I guess if they are an apprentice programmer you might just beat the crap out of them and then let them have ONE second chance.

Even for temporary code--it's never worth the possibility that it will somehow be brought forward into production code.

This kind of code is caused by checked exceptions, an otherwise reasonable idea made into a huge language pitfall by the fact that at some point we'll all see code like that above.

It can take DAYS if not WEEKS to solve this problem. So you've got to understand that by coding that, you are potentially costing the company tens of thousands of dollars. (There's another good solution, fine them for all the salary spent because of that stupidity--I bet they never do THAT again).

If you do expect (catch) a given error and handle it, make sure that:

  1. You know that the error you handled is the ONLY POSSIBLE source of that exception.
  2. Any other exceptions/causes incidentally caught are either rethrown or logged.
  3. You aren't catching to broad an exception (Exception or Throwable)

If I sound aggressive and angry, it's because I've gotten screwed spending weeks finding hidden bugs like this and, as a consultant, haven't found anyone to take it out on. Sorry.

Bill K
+1  A: 

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.

Geoff Reedy