Search code examples
c++unique-ptrgmp

Why does gmp crash with "invalid next size" to realloc here?


I have a simple function using the gmp C++ bindings:

#include <inttypes.h>
#include <memory>
#include <gmpxx.h>



mpz_class f(uint64_t n){
    std::unique_ptr<mpz_class[]> m = std::make_unique<mpz_class[]>(n + 1);
    m[0] = 0;
    m[1] = 1;
    for(uint64_t i = 2; i <= n; ++i){
        m[i] = m[i-1] + m[i-2];
    }
    return m[n];
}

int main(){
    mpz_class fn;
    for(uint64_t n = 0;; n += 1){
        fn = f(n);
    }
}

Presumably make_unique should allocate a fresh array and free it when the function returns since the unique pointer owning it has its lifetime end. Presumably the mpz_class object returned should be a copy and not affected by this array getting deleted. The program crashes with the error:

realloc(): invalid next size

and if I look at the core dump in gdb I get the stack trace:

#0 raise()
#1 abort()
#2 __libc_message()
#3 malloc_printerr()
#4 _int_realloc()
#5 realloc()
#6 __gmp_default_reallocate()
#7 __gmpz_realloc()
#8 __gmpz_add()
#9 __gmp_binary_plus::eval(v, w, z)
#10 __gmp_expr<...>::eval(this, this, p)
#11 __gmp_set_expr<...>(expr, z)
#12 __gmp_expr<...>::operator=<...>(expr, this)
#13 f(n)
#14 main(argc, argv)

This isn't helpful to me, except that it suggests maybe the problem is coming from gmpxx using expression templates (stack frames 9-12 indicate this, valgrind and stack frame 12 put the last line of my code executed before the error at m[1] = 1;). Valgrind says there is an invalid read of size 8 at this line but lists stack entries corresponding to the rest of the trace after it, and then says there is an invalid write at the next instruction. The invalid read is 8 bytes after "a block of size 24 alloc'd [by make_unique]" while the invalid write is to null. Obviously this line should not cause either though as it should only be reading a pointer and then writing to part of the buffer it points to which definitely does not have address 0x0. I decided to use the C++ bindings even though I always use gmp from C because I thought it would be faster to write but this error ensured that was not the case. Is this a problem with gmp or am I allocating the array wrong? I get similar errors if I used new and delete directly or if I manually inline the function call. I feel like the problem may have to do with mpz_class actually storing an expression template and not a proper concretized value.

I'm using GCC 9.2.0 with g++ -std=c++17 -O2 -g -Wall ... and GMP 6.1.2-3. Neither Clang nor GCC report any errors.


Solution

  • If we run under Valgrind, we see:

    ==1948514== Invalid read of size 8
    ==1948514==    at 0x489B0F0: __gmpz_set_si (in /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2)
    ==1948514==    by 0x10945E: __gmp_expr<__mpz_struct [1], __mpz_struct [1]>::assign_si(long) (gmpxx.h:1453)
    ==1948514==    by 0x1094E3: __gmp_expr<__mpz_struct [1], __mpz_struct [1]>::operator=(int) (gmpxx.h:1538)
    ==1948514==    by 0x109248: f(unsigned long) (59678712.cpp:8)
    ==1948514==    by 0x109351: main (59678712.cpp:18)
    ==1948514==  Address 0x4e08ca0 is 8 bytes after a block of size 24 alloc'd
    ==1948514==    at 0x483650F: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==1948514==    by 0x10953F: std::_MakeUniq<__gmp_expr<__mpz_struct [1], __mpz_struct [1]> []>::__array std::make_unique<__gmp_expr<__mpz_struct [1], __mpz_struct [1]> []>(unsigned long) (unique_ptr.h:855)
    ==1948514==    by 0x10920C: f(unsigned long) (59678712.cpp:6)
    ==1948514==    by 0x109351: main (59678712.cpp:18)
    

    This demonstrates that when we call f(0), we write to m[1], which is out of bounds. That's undefined behaviour, so anything could happen. Luckily you got a crash, rather than something more subtle.

    Simple fix:

    mpz_class f(uint64_t n) {
        if (!n) return 0;
    

    BTW, prefer <cstdint> to <inttypes.h>, and write as std::uint64_t etc.