Wednesday, January 11, 2006

Exceptions!

You're a software developer. You've been given a piece of software that throws exceptions. You want to be a good developer and help out. What do you do?

int main() {
    try {
        [... do some stuff ...]
    } catch (ourExceptionType &e) {
        DBG("Ouch, we've got an unhandled exception!");
        return -1;
    } catch (...) {
        DBG("Ouch, not only is it an unhandled exception, but I don't know
what it is!");
        return -1;
    }
}

You would think that you're being helpful. You've just stopped a core dump when the program manages to get an exception all the way back out to main. Of course you would be wrong, very, very wrong.

When a program crashes, it should be very loud. It should scream it's death all the way down. It should give developers enough information to track down the cause and fix it. You've just managed to do several things with this "helpful" piece of code that makes an already difficult job impossible.

First, when exiting all you provide is a debug message. These are either turned off or compiled out. It isn't going to be available. Thanks man.

Second, you catch the exceptions! You aren't going to do anything about it, you're not going to clean up and continue! So, why catch it? Catching it completely destroys the stack information from the exception! Congratulations! You have no idea whatsoever where the exception originally came from.

Fine, you say, I'll re-throw the exception. Nope, doesn't help. Let's see what GCC does with that:


#include <memory>
#include <stdio.h>
#include <stdexcept>

 class MyException : public std::runtime_error {
 public:
   MyException() : std::runtime_error("MyException") { }
 };
 
int myalpha() {

    throw MyException();
    return -1;
}

int myjunk() {
    return myalpha();
}

int main(int argc, char *argv[]) {
    try {
        myjunk();
    } catch (MyException &e) {
        fprintf(stderr, "Boom!\n");
        throw;
    }
}

Looking at the stack trace this produces we see:

#0  0xff21e2f4 in _libc_kill () from /usr/lib/libc.so.1
#1  0xff1b57b8 in abort () from /usr/lib/libc.so.1
#2  0xff353100 in __cxxabiv1::__terminate(void (*)()) (
    handler=0xff1b56b0 ) at eh_terminate.cc:47
#3  0xff353150 in std::terminate() () at eh_terminate.cc:57
#4  0xff353314 in __cxa_rethrow () at eh_throw.cc:101
#5  0x00010d80 in main (argc=1, argv=0xffbff834) at test.cc:26

Nothing at all about where the exception was originally thrown from. We only get information on where the exception that killed the application came from. So, do not be a good samaritan. Don't catch those exceptions unless you mean to do something about it!