GMP error handling frustrates me

The GNU Multiple Precision Arithmetic Library (GMP) is a wonderful library. It offers arbitrary precision math in a relatively simple, easy-to-use package. It is currently used in kcalc as the core for all basic math operations. The only problem is, the error handling it offers, is broken by design.


What could possibly be so broken in such a useful library?

abort(). That’s right, this simple C library function is the thorn in my side that makes using GMP annoying. So what’s so bad about this function?

Here’s the problem, when allowing the user to enter arbitrary values to calculate with (like kcalc), it is trivial to create numbers that are so big, that it would take more RAM than you have to represent it in memory. For example: 549,755,813,888!, that’s right, the factorial of that number. As you can imagine, that is a ridiculously large number. So big in fact that GMP gives up before it even starts.

It’s actually good that GMP gives up before wasting the user’s time, the problem is how it gives up. It does so by printing a message to stderr and calling abort(). Keep in mind, this is a library, not an application. This means that is simply part of another application that could be doing anything. Yet when GMP fails, it gives the application, (you know that thing that the user cares about) absolutely no easy opportunity to deal with the issue. All the user sees is a crash.

This is what I’ve had to resort to, to work around this:

static jmp_buf abort_factorial;
static void factorial_abort_handler(int)
{
    longjmp(abort_factorial, 1);
}

detail::knumber *detail::knuminteger::factorial() const
{
    // ----snip----
    // some basic sanity checking here ...
    // ----snip----

    struct sigaction new_sa;
    struct sigaction old_sa;

    sigemptyset(&new_sa.sa_mask);
    new_sa.sa_handler = factorial_abort_handler;
    sigaction(SIGABRT, &new_sa, &old_sa);

    if(setjmp(abort_factorial)) {
        sigaction(SIGABRT, &old_sa, 0);
        return new knumerror(UndefinedNumber);
    }

    // ----snip----
    // attempt to do interesting work here ...
    // ----snip----

    sigaction(SIGABRT, &old_sa, 0);


    return tmp_num;
}

I have to resort to setting up a signal handler before doing the work and using longjmp() to clean up the mess neatly if an abort happens. I can’t even use c++ exceptions to handle this because SIGABRT isn’t trapped by them. I suppose I could do something “clever” and replace the libc’s abort() function with my own, but that’s just another hack, I want a clean approach.

Fortunately, I don’t just bring problems, I also bring solutions :-). What GMP should do is offer a “fatal error” handler. Something like this would be great:

void gmp_fatal(const char *reason) {
    (*gmp_fatal_ptr)(reason);
}

where gmp_fatal_ptr is a function pointer that can be set by the application.

Heck by default, it can point to a function that prints the reason to stderr and calls abort, this would mean that most users and application writers wouldn’t even see the difference. But at least give me the option to deal with it in a reasonable fashion that isn’t a crash.