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 is simply part of another application which could be doing anything. Yet when GMP fails, it gives the 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 in order 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 which 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.

4 thoughts on “GMP error handling frustrates me

  1. Chris

    I write libraries too, mine call assert which does something similar. After reading your article I am thinking of doing something like this:


    mylib_error_ptr_t mylib_error_ptr = mylib_error_default; // Point to a function that will print an error by default
    void mylib_error(const char *reason) {
    (*mylib_error_ptr)(reason);
    }

    mylib_assert_ptr_t mylib_assert_ptr = mylib_assert_default; // Point to a function that will assert by default
    void mylib_assert(const char *file, const char *function, const char *line) {
    (*mylib_assert_ptr)(file, function, line);
    }

  2. gmp

    You can use alternate error handling in GMP. This is from Gmp Manual 5.0.4 chapter 14. So you can redifine a function for example to throw an exception, and it will be ok.

    By default GMP uses malloc, realloc and free for memory allocation, and if they fail GMP prints a message to the standard error output and terminates the program.
    Alternate functions can be specified, to allocate memory in a different way or to have a different error action on running out of memory.
    This feature is available in the Berkeley compatibility library (see Chapter 13 [BSD Compatible Functions], page 84) as well as the main GMP library.
    void mp_set_memory_functions (
    void *(*alloc_func_ptr ) (size t),
    void *(*realloc_func_ptr ) (void *, size t, size t),
    void (*free_func_ptr ) (void *, size t))
    [Function]
    Replace the current allocation functions from the arguments. If an argument is NULL, the corresponding default function is used. These functions will be used for all memory allocation done by GMP, apart from temporary space from alloca if that function is available and GMP is configured to use it.

  3. Evan Teran Post author

    @gmp: that is fantastic information. I’ll definitely look into rewriting the kcalc code to use the technique you mention. It definitely seems like a nice clean approach to memory allocation errors :-).

  4. Mike

    What is the final conclusion of this problem? Did mp_set_memory_functions work well? Can you show some sample code that you used? Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *