Fri, 23 May 2025 14:00:24 +0200
add base allocator to mempool
otherwise, mempools could not be used as
new default allocators
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "cx/test.h" #include "util_allocator.h" #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 { CX_TEST_ASSERT(pool->destr == NULL); CX_TEST_ASSERT(pool->destr2 == NULL); CX_TEST_ASSERT(pool->destr2_data == NULL); CX_TEST_ASSERT(pool->allocator != NULL); CX_TEST_ASSERT(pool->allocator->cl != NULL); CX_TEST_ASSERT(pool->allocator->data == pool); CX_TEST_ASSERT(pool->allocator->cl->malloc != NULL); CX_TEST_ASSERT(pool->allocator->cl->calloc != NULL); CX_TEST_ASSERT(pool->allocator->cl->realloc != NULL); CX_TEST_ASSERT(pool->allocator->cl->free != NULL); CX_TEST_ASSERT(pool->capacity == 16); CX_TEST_ASSERT(pool->size == 0); CX_TEST_ASSERT(pool->data != NULL); } 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_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); } CX_TEST(test_mempool_calloc) { CxMempool *pool = cxMempoolCreateSimple(4); CX_TEST_DO { 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 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); // 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); CX_TEST_DO { CX_TEST_CALL_SUBROUTINE(test_mempool_realloc_verify, pool, CX_MEMPOOL_TYPE_SIMPLE); } cxMempoolFree(pool); } 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); CX_TEST_DO { CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); } cxMempoolFree(pool); } CX_TEST(test_mempool_free2) { CxMempool *pool = cxMempoolCreateAdvanced(4); CX_TEST_DO { CX_TEST_CALL_SUBROUTINE(test_mempool_free_verify, pool); } cxMempoolFree(pool); } CX_TEST(test_mempool_destroy) { 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); test_mempool_destructor_called = 0; cxFree(pool->allocator, data); CX_TEST_ASSERT(test_mempool_destructor_called == 1); data = cxMalloc(pool->allocator, sizeof(int)); cxMempoolSetDestructor(data, test_mempool_destructor); cxMempoolFree(pool); CX_TEST_ASSERT(test_mempool_destructor_called == 2); } } 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; cxMempoolRegister2(pool, &donotfree, test_mempool_destructor2, &test_mempool_destructor_called); cxMempoolFree(pool); CX_TEST_ASSERT(test_mempool_destructor_called == 2); } } CX_TEST(test_mempool_transfer) { CxMempool *src = cxMempoolCreateSimple(4); CxMempool *dest = cxMempoolCreateSimple(4); CX_TEST_DO { // allocate the first object int *c = cxMalloc(src->allocator, sizeof(int)); // allocate the second object c = cxMalloc(src->allocator, sizeof(int)); // check that the destructor functions are also transferred cxMempoolSetDestructor(c, test_mempool_destructor); // register foreign object c = malloc(sizeof(int)); cxMempoolRegister(src, c, test_mempool_destructor); // check source pool CX_TEST_ASSERT(src->size == 2); CX_TEST_ASSERT(src->registered_size == 1); const CxAllocator *old_allocator = src->allocator; CX_TEST_ASSERT(old_allocator->data == src); // perform transfer int result = cxMempoolTransfer(src, dest); CX_TEST_ASSERT(result == 0); // check transfer CX_TEST_ASSERT(src->size == 0); CX_TEST_ASSERT(dest->size == 2); CX_TEST_ASSERT(src->registered_size == 0); CX_TEST_ASSERT(dest->registered_size == 2); // 1 object + old allocator CX_TEST_ASSERT(src->allocator != old_allocator); CX_TEST_ASSERT(old_allocator->data == dest); // verify that destroying old pool does nothing test_mempool_destructor_called = 0; cxMempoolFree(src); CX_TEST_ASSERT(test_mempool_destructor_called == 0); // cover illegal arguments result = cxMempoolTransfer(dest, dest); CX_TEST_ASSERT(result != 0); // verify that destroying new pool calls the destructors // but only two times (the old allocator has a different destructor) cxMempoolFree(dest); CX_TEST_ASSERT(test_mempool_destructor_called == 2); // free the foreign object free(c); } } CX_TEST(test_mempool_transfer_object) { 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)); 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 == 2); 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); 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); // 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 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); return suite; }