Fri, 23 May 2025 13:36:11 +0200
stabilizes mempool implementation
resolves #676
fixes #677
fixes #678
CHANGELOG | file | annotate | diff | comparison | revisions | |
docs/Writerside/topics/about.md | file | annotate | diff | comparison | revisions | |
src/cx/mempool.h | file | annotate | diff | comparison | revisions | |
src/mempool.c | file | annotate | diff | comparison | revisions | |
tests/test_mempool.c | file | annotate | diff | comparison | revisions | |
tests/ucxtest.c | file | annotate | diff | comparison | revisions |
--- a/CHANGELOG Fri May 23 12:44:24 2025 +0200 +++ b/CHANGELOG Fri May 23 13:36:11 2025 +0200 @@ -21,6 +21,8 @@ * changes all cxListIterator() without index to also accept NULL as list argument * changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element * fixes critical memory overflow in the stack-based array reallocator (this unfortunately breaks the function signature) + * fixes mempool implementation not supporting NULL as argument for realloc + * fixes mempool implementation not supporting zero as size for realloc * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node * fixes unnecessary allocations in cx_strcat() family of functions * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
--- a/docs/Writerside/topics/about.md Fri May 23 12:44:24 2025 +0200 +++ b/docs/Writerside/topics/about.md Fri May 23 13:36:11 2025 +0200 @@ -48,6 +48,8 @@ * changes all cxListIterator() without index to also accept NULL as list argument * changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element * fixes critical memory overflow in the stack-based array reallocator (this unfortunately breaks the function signature) +* fixes mempool implementation not supporting NULL as argument for realloc +* fixes mempool implementation not supporting zero as size for realloc * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node * fixes unnecessary allocations in cx_strcat() family of functions * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
--- a/src/cx/mempool.h Fri May 23 12:44:24 2025 +0200 +++ b/src/cx/mempool.h Fri May 23 13:36:11 2025 +0200 @@ -43,6 +43,7 @@ extern "C" { #endif +/** A memory block in a simple memory pool. */ struct cx_mempool_memory_s { /** The destructor. */ cx_destructor_func destructor; @@ -50,6 +51,7 @@ char c[]; }; +/** A memory block in an advanced memory pool. */ struct cx_mempool_memory2_s { /** The destructor. */ cx_destructor_func2 destructor; @@ -59,6 +61,7 @@ char c[]; }; +/** Represents memory that is not allocated by, but registered with a pool. */ struct cx_mempool_foreign_memory_s { /** The foreign memory. */ void* mem; @@ -170,6 +173,36 @@ CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); /** + * Creates a basic array-based memory pool. + * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_SIMPLE. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE) + +/** + * Creates a basic array-based memory pool. + * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_ADVANCED. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxMempoolCreateAdvanced(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_ADVANCED) + +/** + * Creates a basic array-based memory pool. + * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_PURE. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxMempoolCreatePure(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_PURE) + +/** * Sets the global destructor for all memory blocks within the specified pool. * * @param pool the memory pool @@ -191,14 +224,6 @@ void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); /** - * Creates a basic array-based memory pool. - * - * @param capacity (@c size_t) the initial capacity of the pool - * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed - */ -#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE) - -/** * Sets the destructor function for a specific allocated memory object. * * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined.
--- a/src/mempool.c Fri May 23 12:44:24 2025 +0200 +++ b/src/mempool.c Fri May 23 13:36:11 2025 +0200 @@ -39,11 +39,12 @@ size_t newcap = pool->capacity >= 1000 ? pool->capacity + 1000 : pool->capacity * 2; size_t newmsize; + // LCOV_EXCL_START if (pool->capacity > newcap || cx_szmul(newcap, sizeof(void*), &newmsize)) { errno = EOVERFLOW; return 1; - } + } // LCOV_EXCL_STOP void **newdata = cxReallocDefault(pool->data, newmsize); if (newdata == NULL) return 1; pool->data = newdata; @@ -59,11 +60,12 @@ // we do not expect so many registrations size_t newcap = pool->registered_capacity + 8; size_t newmsize; + // LCOV_EXCL_START if (pool->registered_capacity > newcap || cx_szmul(newcap, sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) { errno = EOVERFLOW; return 1; - } + } // LCOV_EXCL_STOP void *newdata = cxReallocDefault(pool->registered, newmsize); if (newdata == NULL) return 1; pool->registered = newdata; @@ -78,7 +80,7 @@ struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { - return NULL; + return NULL; // LCOV_EXCL_LINE } struct cx_mempool_memory_s *mem = @@ -107,33 +109,6 @@ return ptr; } -static void *cx_mempool_realloc_simple( - void *p, - void *ptr, - size_t n -) { - struct cx_mempool_s *pool = p; - - const unsigned overhead = sizeof(struct cx_mempool_memory_s); - struct cx_mempool_memory_s *mem = - (void *) (((char *) ptr) - overhead); - struct cx_mempool_memory_s *newm = - cxReallocDefault(mem, n + overhead); - - if (newm == NULL) return NULL; - if (mem != newm) { - for (size_t i = 0; i < pool->size; i++) { - if (pool->data[i] == mem) { - pool->data[i] = newm; - return ((char*)newm) + overhead; - } - } - abort(); // LCOV_EXCL_LINE - } else { - return ptr; - } -} - static void cx_mempool_free_simple( void *p, void *ptr @@ -168,6 +143,41 @@ abort(); // LCOV_EXCL_LINE } +static void *cx_mempool_realloc_simple( + void *p, + void *ptr, + size_t n +) { + if (ptr == NULL) { + return cx_mempool_malloc_simple(p, n); + } + if (n == 0) { + cx_mempool_free_simple(p, ptr); + return NULL; + } + struct cx_mempool_s *pool = p; + + const unsigned overhead = sizeof(struct cx_mempool_memory_s); + struct cx_mempool_memory_s *mem = + (void *) (((char *) ptr) - overhead); + struct cx_mempool_memory_s *newm = + cxReallocDefault(mem, n + overhead); + + if (newm == NULL) return NULL; + if (mem != newm) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == mem) { + pool->data[i] = newm; + return ((char*)newm) + overhead; + } + } + abort(); // LCOV_EXCL_LINE + } else { + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; @@ -200,11 +210,11 @@ struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { - return NULL; + return NULL; // LCOV_EXCL_LINE } struct cx_mempool_memory2_s *mem = - cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n); + cxMallocDefault(sizeof(struct cx_mempool_memory2_s) + n); if (mem == NULL) return NULL; mem->destructor = NULL; mem->data = NULL; @@ -230,33 +240,6 @@ return ptr; } -static void *cx_mempool_realloc_advanced( - void *p, - void *ptr, - size_t n -) { - struct cx_mempool_s *pool = p; - - const unsigned overhead = sizeof(struct cx_mempool_memory2_s); - struct cx_mempool_memory2_s *mem = - (void *) (((char *) ptr) - overhead); - struct cx_mempool_memory2_s *newm = - cxReallocDefault(mem, n + overhead); - - if (newm == NULL) return NULL; - if (mem != newm) { - for (size_t i = 0; i < pool->size; i++) { - if (pool->data[i] == mem) { - pool->data[i] = newm; - return ((char*)newm) + overhead; - } - } - abort(); // LCOV_EXCL_LINE - } else { - return ptr; - } -} - static void cx_mempool_free_advanced( void *p, void *ptr @@ -291,6 +274,41 @@ abort(); // LCOV_EXCL_LINE } +static void *cx_mempool_realloc_advanced( + void *p, + void *ptr, + size_t n +) { + if (ptr == NULL) { + return cx_mempool_malloc_advanced(p, n); + } + if (n == 0) { + cx_mempool_free_advanced(p, ptr); + return NULL; + } + struct cx_mempool_s *pool = p; + + const unsigned overhead = sizeof(struct cx_mempool_memory2_s); + struct cx_mempool_memory2_s *mem = + (void *) (((char *) ptr) - overhead); + struct cx_mempool_memory2_s *newm = + cxReallocDefault(mem, n + overhead); + + if (newm == NULL) return NULL; + if (mem != newm) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == mem) { + pool->data[i] = newm; + return ((char*)newm) + overhead; + } + } + abort(); // LCOV_EXCL_LINE + } else { + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; @@ -324,7 +342,7 @@ struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { - return NULL; + return NULL; // LCOV_EXCL_LINE } void *mem = cxMallocDefault(n); @@ -351,27 +369,6 @@ return ptr; } -static void *cx_mempool_realloc_pure( - void *p, - void *ptr, - size_t n -) { - struct cx_mempool_s *pool = p; - void *newm = cxReallocDefault(ptr, n); - if (newm == NULL) return NULL; - if (ptr != newm) { - for (size_t i = 0; i < pool->size; i++) { - if (pool->data[i] == ptr) { - pool->data[i] = newm; - return newm; - } - } - abort(); // LCOV_EXCL_LINE - } else { - return ptr; - } -} - static void cx_mempool_free_pure( void *p, void *ptr @@ -400,6 +397,35 @@ abort(); // LCOV_EXCL_LINE } +static void *cx_mempool_realloc_pure( + void *p, + void *ptr, + size_t n +) { + if (ptr == NULL) { + return cx_mempool_malloc_pure(p, n); + } + if (n == 0) { + cx_mempool_free_pure(p, ptr); + return NULL; + } + struct cx_mempool_s *pool = p; + void *newm = cxReallocDefault(ptr, n); + if (newm == NULL) return NULL; + if (ptr != newm) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == ptr) { + pool->data[i] = newm; + return newm; + } + } + abort(); // LCOV_EXCL_LINE + } else { + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; @@ -526,9 +552,10 @@ if (capacity == 0) capacity = 16; size_t poolsize; if (cx_szmul(capacity, sizeof(void*), &poolsize)) { + // LCOV_EXCL_START errno = EOVERFLOW; return NULL; - } + } // LCOV_EXCL_STOP CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); if (provided_allocator == NULL) { // LCOV_EXCL_START @@ -536,10 +563,10 @@ } // LCOV_EXCL_STOP CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool)); - if (pool == NULL) { + if (pool == NULL) { // LCOV_EXCL_START cxFreeDefault(provided_allocator); return NULL; - } + } // LCOV_EXCL_STOP provided_allocator->data = pool; pool->allocator = provided_allocator;
--- a/tests/test_mempool.c Fri May 23 12:44:24 2025 +0200 +++ b/tests/test_mempool.c Fri May 23 13:36:11 2025 +0200 @@ -31,6 +31,19 @@ #include "cx/mempool.h" +#include <errno.h> + +static unsigned test_mempool_destructor_called; + +static void test_mempool_destructor(cx_attr_unused void *mem) { + test_mempool_destructor_called++; +} + +static void test_mempool_destructor2(void *data, cx_attr_unused void *mem) { + int *ctr = data; + *ctr = *ctr + 1; +} + CX_TEST(test_mempool_create) { CxMempool *pool = cxMempoolCreateSimple(16); CX_TEST_DO { @@ -51,21 +64,69 @@ cxMempoolFree(pool); } +static CX_TEST_SUBROUTINE(test_mempool_malloc_verify, CxMempool *pool) { + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(pool->size == 2); + CX_TEST_ASSERT(pool->capacity == 4); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + CX_TEST_ASSERT(pool->size == 4); + CX_TEST_ASSERT(pool->capacity == 4); + CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); + int *i = cxMalloc(pool->allocator, sizeof(int)); + CX_TEST_ASSERT(i != NULL); + *i = 4083914; // let asan / valgrind check + CX_TEST_ASSERT(pool->size == 6); + CX_TEST_ASSERT(pool->capacity >= 6); +} + +CX_TEST(test_mempool_malloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + CX_TEST(test_mempool_malloc) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 2); - CX_TEST_ASSERT(pool->capacity == 4); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 4); - CX_TEST_ASSERT(pool->capacity == 4); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(cxMalloc(pool->allocator, sizeof(int)) != NULL); - CX_TEST_ASSERT(pool->size == 6); - CX_TEST_ASSERT(pool->capacity >= 6); + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + +CX_TEST(test_mempool_malloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_malloc_verify, pool); + } + cxMempoolFree(pool); +} + +static CX_TEST_SUBROUTINE(test_mempool_calloc_verify, CxMempool *pool) { + int *test = cxCalloc(pool->allocator, 2, sizeof(int)); + CX_TEST_ASSERT(test != NULL); + CX_TEST_ASSERT(test[0] == 0); + CX_TEST_ASSERT(test[1] == 0); +#if __GNUC__ > 11 +// we want to explicitly test the overflow +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Walloc-size-larger-than=" +#endif + errno = 0; + CX_TEST_ASSERT(NULL == cxCalloc(pool->allocator, SIZE_MAX / 2, sizeof(int))); + CX_TEST_ASSERT(errno == EOVERFLOW); +#if __GNUC__ > 11 +#pragma GCC diagnostic pop +#endif +} + +CX_TEST(test_mempool_calloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); } cxMempoolFree(pool); } @@ -73,66 +134,121 @@ CX_TEST(test_mempool_calloc) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { - int *test = cxCalloc(pool->allocator, 2, sizeof(int)); - CX_TEST_ASSERT(test != NULL); - CX_TEST_ASSERT(test[0] == 0); - CX_TEST_ASSERT(test[1] == 0); + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); + } + cxMempoolFree(pool); +} + +CX_TEST(test_mempool_calloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_calloc_verify, pool); } cxMempoolFree(pool); } -static unsigned test_mempool_destructor_called; +static CX_TEST_SUBROUTINE(test_mempool_realloc_verify, CxMempool *pool, enum cx_mempool_type type) { + // use realloc with NULL which shall behave as a malloc + int *data = cxRealloc(pool->allocator, NULL, 2*sizeof(int)); + if (type == CX_MEMPOOL_TYPE_SIMPLE) { + cxMempoolSetDestructor(data, test_mempool_destructor); + } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { + cxMempoolSetDestructor2(data, test_mempool_destructor2, &test_mempool_destructor_called); + } + *data = 13; + + // shrink to actual sizeof(int) + data = cxRealloc(pool->allocator, data, sizeof(int)); + CX_TEST_ASSERT(*data == 13); + + // realloc with the same size (should not do anything) + data = cxRealloc(pool->allocator, data, sizeof(int)); + CX_TEST_ASSERT(*data == 13); -static void test_mempool_destructor(cx_attr_unused void *mem) { - test_mempool_destructor_called++; + // now try hard to trigger a memmove + int *rdata = data; + unsigned n = 1; + while (rdata == data) { + n <<= 1; + // eventually the memory should be moved elsewhere + CX_TEST_ASSERTM(n < 65536, "Reallocation attempt failed - test not executable"); + rdata = cxRealloc(pool->allocator, data, n * sizeof(intptr_t)); + } + + CX_TEST_ASSERT(*rdata == 13); + // test if destructor is still intact + if (type != CX_MEMPOOL_TYPE_PURE) { + test_mempool_destructor_called = 0; + cxFree(pool->allocator, rdata); + CX_TEST_ASSERT(test_mempool_destructor_called == 1); + } +} + +CX_TEST(test_mempool_realloc0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_PURE); + } + cxMempoolFree(pool); } CX_TEST(test_mempool_realloc) { CxMempool *pool = cxMempoolCreateSimple(4); - cxMempoolGlobalDestructor(pool, test_mempool_destructor); CX_TEST_DO { - CX_TEST_ASSERT(pool->destr == test_mempool_destructor); - int *data = cxMalloc(pool->allocator, sizeof(int)); - *data = 13; + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_SIMPLE); + } + cxMempoolFree(pool); +} - int *rdata = data; - unsigned n = 1; - while (rdata == data) { - n <<= 1; - // eventually the memory should be moved elsewhere - CX_TEST_ASSERTM(n < 65536, "Reallocation attempt failed - test not executable"); - rdata = cxRealloc(pool->allocator, data, n * sizeof(intptr_t)); - } - - CX_TEST_ASSERT(*rdata == 13); - // test if destructor is still intact - test_mempool_destructor_called = 0; - cxFree(pool->allocator, rdata); - CX_TEST_ASSERT(test_mempool_destructor_called == 1); +CX_TEST(test_mempool_realloc2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_ADVANCED); } cxMempoolFree(pool); } +static CX_TEST_SUBROUTINE(test_mempool_free_verify, CxMempool *pool) { + void *mem1, *mem2; + mem1 = cxMalloc(pool->allocator, 16); + cxFree(pool->allocator, mem1); + CX_TEST_ASSERT(pool->size == 0); + + mem1 = cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); + mem2 = cxMalloc(pool->allocator, 16); + mem2 = cxMalloc(pool->allocator, 16); + + CX_TEST_ASSERT(pool->size == 5); + // a realloc with size zero shall behave like a free + void *freed = cxRealloc(pool->allocator, mem1, 0); + CX_TEST_ASSERT(freed == NULL); + CX_TEST_ASSERT(pool->size == 4); + cxFree(pool->allocator, mem2); + CX_TEST_ASSERT(pool->size == 3); +} + +CX_TEST(test_mempool_free0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); + } + cxMempoolFree(pool); +} CX_TEST(test_mempool_free) { CxMempool *pool = cxMempoolCreateSimple(4); - void *mem1, *mem2; CX_TEST_DO { - mem1 = cxMalloc(pool->allocator, 16); - cxFree(pool->allocator, mem1); - CX_TEST_ASSERT(pool->size == 0); + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); + } + cxMempoolFree(pool); +} - mem1 = cxMalloc(pool->allocator, 16); - mem1 = cxMalloc(pool->allocator, 16); - mem1 = cxMalloc(pool->allocator, 16); - mem2 = cxMalloc(pool->allocator, 16); - mem2 = cxMalloc(pool->allocator, 16); - - CX_TEST_ASSERT(pool->size == 5); - cxFree(pool->allocator, mem1); - CX_TEST_ASSERT(pool->size == 4); - cxFree(pool->allocator, mem2); - CX_TEST_ASSERT(pool->size == 3); +CX_TEST(test_mempool_free2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); } cxMempoolFree(pool); } @@ -154,14 +270,124 @@ } } +CX_TEST(test_mempool_destroy2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + *data = 47; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + CX_TEST_ASSERT(*data == 47); + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 1); + data = cxMalloc(pool->allocator, sizeof(int)); + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 2); + } +} + +CX_TEST(test_mempool_remove_destructor) { + CxMempool *pool = cxMempoolCreateSimple(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + *data = 13; + cxMempoolSetDestructor(data, test_mempool_destructor); + CX_TEST_ASSERT(*data == 13); + cxMempoolRemoveDestructor(data); + CX_TEST_ASSERT(*data == 13); + test_mempool_destructor_called = 0; + cxFree(pool->allocator, data); + CX_TEST_ASSERT(test_mempool_destructor_called == 0); + data = cxMalloc(pool->allocator, sizeof(int)); + *data = 99; + cxMempoolSetDestructor(data, test_mempool_destructor); + cxMempoolRemoveDestructor(data); + CX_TEST_ASSERT(*data == 99); + cxMempoolFree(pool); + CX_TEST_ASSERT(test_mempool_destructor_called == 0); + } +} + +CX_TEST(test_mempool_remove_destructor2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + *data = 47; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + CX_TEST_ASSERT(*data == 47); + cxMempoolRemoveDestructor2(data); + CX_TEST_ASSERT(*data == 47); + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 0); + data = cxMalloc(pool->allocator, sizeof(int)); + *data = 99; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &ctr); + cxMempoolRemoveDestructor2(data); + CX_TEST_ASSERT(*data == 99); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 0); + } +} + +static CX_TEST_SUBROUTINE(test_mempool_global_destructors_verify, CxMempool *pool) { + int *data = cxMalloc(pool->allocator, sizeof(int)); + int ctr = 0; + cxMempoolGlobalDestructor(pool, test_mempool_destructor); + cxMempoolGlobalDestructor2(pool, test_mempool_destructor2, &ctr); + test_mempool_destructor_called = 0; + cxFree(pool->allocator, data); + CX_TEST_ASSERT(ctr == 1); + CX_TEST_ASSERT(test_mempool_destructor_called == 1); + data = cxMalloc(pool->allocator, sizeof(int)); + cxMempoolFree(pool); + CX_TEST_ASSERT(ctr == 2); + CX_TEST_ASSERT(test_mempool_destructor_called == 2); +} + +CX_TEST(test_mempool_global_destructors0) { + CxMempool *pool = cxMempoolCreatePure(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + +CX_TEST(test_mempool_global_destructors) { + CxMempool *pool = cxMempoolCreateSimple(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + +CX_TEST(test_mempool_global_destructors2) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + CX_TEST_CALL_SUBROUTINE(test_mempool_global_destructors_verify, pool); + } +} + CX_TEST(test_mempool_register) { + CxMempool *pool = cxMempoolCreateAdvanced(4); + CX_TEST_DO { + int *data = cxMalloc(pool->allocator, sizeof(int)); + test_mempool_destructor_called = 0; + cxMempoolSetDestructor2(data, test_mempool_destructor2, &test_mempool_destructor_called); + int donotfree = 0; + cxMempoolRegister(pool, &donotfree, test_mempool_destructor); + cxMempoolFree(pool); + CX_TEST_ASSERT(test_mempool_destructor_called == 2); + } +} + +CX_TEST(test_mempool_register2) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { int *data = cxMalloc(pool->allocator, sizeof(int)); test_mempool_destructor_called = 0; cxMempoolSetDestructor(data, test_mempool_destructor); int donotfree = 0; - cxMempoolRegister(pool, &donotfree, test_mempool_destructor); + cxMempoolRegister2(pool, &donotfree, test_mempool_destructor2, &test_mempool_destructor_called); cxMempoolFree(pool); CX_TEST_ASSERT(test_mempool_destructor_called == 2); } @@ -222,50 +448,75 @@ CxMempool *src = cxMempoolCreateSimple(4); CxMempool *dest = cxMempoolCreateSimple(4); CX_TEST_DO { + int *a = cxMalloc(src->allocator, sizeof(int)); int *b = cxMalloc(src->allocator, sizeof(int)); - b = cxMalloc(src->allocator, sizeof(int)); int *c = malloc(sizeof(int)); cxMempoolRegister(src, c, free); + int *d = malloc(sizeof(int)); + cxMempoolRegister(src, d, free); CX_TEST_ASSERT(src->size == 2); - CX_TEST_ASSERT(src->registered_size == 1); + CX_TEST_ASSERT(src->registered_size == 2); - int result = cxMempoolTransferObject(src, dest, b); + int result = cxMempoolTransferObject(src, dest, a); CX_TEST_ASSERT(result == 0); CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 2); CX_TEST_ASSERT(dest->size == 1); - result = cxMempoolTransferObject(src, dest, b); + CX_TEST_ASSERT(dest->registered_size == 0); + result = cxMempoolTransferObject(src, dest, a); + CX_TEST_ASSERT(result != 0); + CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 2); + CX_TEST_ASSERT(dest->size == 1); + CX_TEST_ASSERT(dest->registered_size == 0); + + // can also transfer foreign memory this way + result = cxMempoolTransferObject(src, dest, c); + CX_TEST_ASSERT(result == 0); + CX_TEST_ASSERT(src->size == 1); + CX_TEST_ASSERT(src->registered_size == 1); + CX_TEST_ASSERT(dest->size == 1); + CX_TEST_ASSERT(dest->registered_size == 1); + + // src==dest is an error + result = cxMempoolTransferObject(dest, dest, b); CX_TEST_ASSERT(result != 0); CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); - // can also transfer foreign memory this way - CX_TEST_ASSERT(src->registered_size == 1); - CX_TEST_ASSERT(dest->registered_size == 0); - result = cxMempoolTransferObject(src, dest, c); - CX_TEST_ASSERT(result == 0); - CX_TEST_ASSERT(src->registered_size == 0); - CX_TEST_ASSERT(dest->registered_size == 1); - - result = cxMempoolTransferObject(dest, dest, b); - CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 1); - CX_TEST_ASSERT(dest->size == 1); + // check that we don't die when we free memory that's still in the source pool + cxFree(src->allocator, b); } cxMempoolFree(src); cxMempoolFree(dest); - // let valgrind check that everything worked + // let valgrind check that everything else worked } CxTestSuite *cx_test_suite_mempool(void) { CxTestSuite *suite = cx_test_suite_new("mempool"); cx_test_register(suite, test_mempool_create); + cx_test_register(suite, test_mempool_malloc0); + cx_test_register(suite, test_mempool_calloc0); + cx_test_register(suite, test_mempool_realloc0); + cx_test_register(suite, test_mempool_free0); cx_test_register(suite, test_mempool_malloc); cx_test_register(suite, test_mempool_calloc); cx_test_register(suite, test_mempool_realloc); cx_test_register(suite, test_mempool_free); cx_test_register(suite, test_mempool_destroy); + cx_test_register(suite, test_mempool_malloc2); + cx_test_register(suite, test_mempool_calloc2); + cx_test_register(suite, test_mempool_realloc2); + cx_test_register(suite, test_mempool_free2); + cx_test_register(suite, test_mempool_destroy2); + cx_test_register(suite, test_mempool_remove_destructor); + cx_test_register(suite, test_mempool_remove_destructor2); + cx_test_register(suite, test_mempool_global_destructors0); + cx_test_register(suite, test_mempool_global_destructors); + cx_test_register(suite, test_mempool_global_destructors2); cx_test_register(suite, test_mempool_register); + cx_test_register(suite, test_mempool_register2); cx_test_register(suite, test_mempool_transfer); cx_test_register(suite, test_mempool_transfer_object);
--- a/tests/ucxtest.c Fri May 23 12:44:24 2025 +0200 +++ b/tests/ucxtest.c Fri May 23 13:36:11 2025 +0200 @@ -59,7 +59,7 @@ for (size_t i = 0; i < cx_nmemb(test_suites) ; i++) {run_tests(test_suites[i]);} (void)0 #define free_test_suites for (size_t i = 0 ; i < cx_nmemb(test_suites) ; i++) {cx_test_suite_free(test_suites[i]);} (void)0 -static int verify_testing_allocator() { +static int verify_testing_allocator(void) { printf("Verify Testing Allocator\n"); CxTestSuite *suite_ta = cx_test_suite_testing_allocator(); int result = suite_ta->failure > 0;