tangens
tangens

Reputation: 39733

How to analyse a NoClassDefFoundError caused by an ignored ExceptionInInitializerError?

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.

Upvotes: 12

Views: 10865

Answers (7)

vorburger
vorburger

Reputation: 3928

If you can reproduce the problem (even occasionally), and it's possible to run the app under debug, then you may be able to set a break point in your debugger for (all 3 constructors of) ExceptionInInitializerError, and see when they git hit.

Upvotes: 0

Stephen C
Stephen C

Reputation: 718768

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.


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.

No there isn't. There are complicated/heroic ways ... like using doing clever things with a java agent to hack the runtime system on the fly ... but not the sort of thing that a typical Java developer is likely to have in their "toolbox".

Which is why the advice above is so important.

Upvotes: 1

Bill K
Bill K

Reputation: 62769

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.

Upvotes: 4

Geoff Reedy
Geoff Reedy

Reputation: 36011

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.

Upvotes: 17

skaffman
skaffman

Reputation: 403461

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.

Upvotes: 15

Yoni
Yoni

Reputation: 10321

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 ...

Upvotes: 0

Andreas Dolk
Andreas Dolk

Reputation: 114767

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.

Upvotes: 1

Related Questions