#include "util_allocator.h"
#include "cx/test.h"
#if !defined(__clang__) &&
__GNUC__ >
11
#pragma GCC diagnostic ignored
"-Wuse-after-free"
#endif
static void cx_testing_allocator_track(CxTestingAllocator *alloc,
void *ptr) {
for (
size_t i =
0; i < alloc->tracked_count; i++) {
if (alloc->tracked[i] == ptr)
return;
}
if (alloc->tracked_count == alloc->tracked_capacity) {
size_t newcapa = alloc->tracked_capacity +
64;
void *newarr = realloc(alloc->tracked, newcapa *
sizeof(
void *));
if (newarr ==
NULL) abort();
alloc->tracked = newarr;
alloc->tracked_capacity = newcapa;
}
alloc->tracked[alloc->tracked_count] = ptr;
alloc->tracked_count++;
}
static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc,
void *ptr) {
for (
size_t i =
0; i < alloc->tracked_count; i++) {
if (alloc->tracked[i] == ptr) {
size_t last = alloc->tracked_count -
1;
if (i < last) {
alloc->tracked[i] = alloc->tracked[last];
}
alloc->tracked_count--;
return true;
}
}
return false;
}
static void *cx_malloc_testing(
void *d,
size_t n) {
CxTestingAllocator *data = d;
void *ptr = malloc(n);
data->alloc_total++;
if (ptr ==
NULL) {
data->alloc_failed++;
}
else {
cx_testing_allocator_track(data, ptr);
}
return ptr;
}
static void *cx_realloc_testing(
void *d,
void *mem,
size_t n) {
CxTestingAllocator *data = d;
void *ptr = realloc(mem, n);
if (mem !=
NULL && ptr == mem) {
return ptr;
}
else {
data->alloc_total++;
if (ptr ==
NULL) {
data->alloc_failed++;
}
else {
if (mem !=
NULL) {
data->free_total++;
if (!cx_testing_allocator_untrack(data, mem)) {
data->free_failed++;
}
}
cx_testing_allocator_track(data, ptr);
}
return ptr;
}
}
static void *cx_calloc_testing(
void *d,
size_t nelem,
size_t n) {
CxTestingAllocator *data = d;
void *ptr = calloc(nelem, n);
data->alloc_total++;
if (ptr ==
NULL) {
data->alloc_failed++;
}
else {
cx_testing_allocator_track(data, ptr);
}
return ptr;
}
static void cx_free_testing(
void *d,
void *mem) {
if (mem ==
NULL)
return;
CxTestingAllocator *data = d;
data->free_total++;
if (cx_testing_allocator_untrack(data, mem)) {
free(mem);
}
else {
data->free_failed++;
}
}
cx_allocator_class cx_testing_allocator_class = {
cx_malloc_testing,
cx_realloc_testing,
cx_calloc_testing,
cx_free_testing
};
void cx_testing_allocator_init(CxTestingAllocator *alloc) {
alloc->base.cl = &cx_testing_allocator_class;
alloc->base.data = alloc;
alloc->alloc_failed =
0;
alloc->alloc_total =
0;
alloc->free_failed =
0;
alloc->free_total =
0;
size_t initial_capa =
16;
alloc->tracked_capacity = initial_capa;
alloc->tracked_count =
0;
alloc->tracked = calloc(initial_capa,
sizeof(
void *));
}
void cx_testing_allocator_destroy(CxTestingAllocator *alloc) {
free(alloc->tracked);
}
bool cx_testing_allocator_used(
const CxTestingAllocator *alloc) {
return alloc->alloc_total >
0;
}
bool cx_testing_allocator_verify(
const CxTestingAllocator *alloc) {
return alloc->tracked_count ==
0 && alloc->alloc_failed ==
0 && alloc->free_failed ==
0
&& alloc->alloc_total == alloc->free_total;
}
CX_TEST(test_util_allocator_expect_free) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
"Fresh testing allocator fails to verify.");
CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc),
"Fresh testing allocator already used.");
void *ptr = cxMalloc(alloc,
16);
CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
"Testing allocator verifies with unfreed memory.");
CX_TEST_ASSERT(cx_testing_allocator_used(&talloc));
CX_TEST_ASSERT(ptr !=
NULL);
cxFree(alloc, ptr);
CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
"Testing allocator fails to verify after everything freed.");
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_util_allocator_detect_double_free) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
void *ptr = cxMalloc(alloc,
16);
CX_TEST_ASSERT(ptr !=
NULL);
cxFree(alloc, ptr);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
cxFree(alloc, ptr);
CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
"Testing allocator does not detect double-free.");
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_util_allocator_free_untracked) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *alloc = &talloc.base;
void *ptr = malloc(
16);
CX_TEST_DO {
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
cxFree(alloc, ptr);
CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
"Testing allocator does not detect free of untracked memory.");
}
free(ptr);
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_util_allocator_full_lifecycle_with_realloc) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
void *ptr = cxMalloc(alloc,
16);
CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
CX_TEST_ASSERT(ptr !=
NULL);
CX_TEST_ASSERT(talloc.tracked_count ==
1);
ptr = cxRealloc(alloc, ptr,
256);
CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
CX_TEST_ASSERT(ptr !=
NULL);
CX_TEST_ASSERT(talloc.tracked_count ==
1);
cxFree(alloc, ptr);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CX_TEST(test_util_allocator_calloc_initializes) {
CxTestingAllocator talloc;
cx_testing_allocator_init(&talloc);
CxAllocator *alloc = &talloc.base;
CX_TEST_DO {
const char zeros[
16] = {
0};
void *ptr = cxCalloc(alloc,
16,
1);
CX_TEST_ASSERT(memcmp(ptr, zeros,
16) ==
0);
cxFree(alloc, ptr);
CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
}
cx_testing_allocator_destroy(&talloc);
}
CxTestSuite *cx_test_suite_testing_allocator(
void) {
CxTestSuite *suite = cx_test_suite_new(
"testing allocator self-test");
cx_test_register(suite, test_util_allocator_expect_free);
cx_test_register(suite, test_util_allocator_detect_double_free);
cx_test_register(suite, test_util_allocator_free_untracked);
cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc);
cx_test_register(suite, test_util_allocator_calloc_initializes);
return suite;
}