Thu, 25 Jan 2024 22:01:12 +0100
add cx_array_add() + fix type of cx_array_default_reallocator
universe@422 | 1 | /* |
universe@422 | 2 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
universe@422 | 3 | * |
universe@422 | 4 | * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. |
universe@422 | 5 | * |
universe@422 | 6 | * Redistribution and use in source and binary forms, with or without |
universe@422 | 7 | * modification, are permitted provided that the following conditions are met: |
universe@422 | 8 | * |
universe@422 | 9 | * 1. Redistributions of source code must retain the above copyright |
universe@422 | 10 | * notice, this list of conditions and the following disclaimer. |
universe@422 | 11 | * |
universe@422 | 12 | * 2. Redistributions in binary form must reproduce the above copyright |
universe@422 | 13 | * notice, this list of conditions and the following disclaimer in the |
universe@422 | 14 | * documentation and/or other materials provided with the distribution. |
universe@422 | 15 | * |
universe@422 | 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
universe@422 | 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
universe@422 | 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
universe@422 | 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
universe@422 | 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
universe@422 | 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
universe@422 | 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
universe@422 | 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
universe@422 | 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
universe@422 | 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
universe@422 | 26 | * POSSIBILITY OF SUCH DAMAGE. |
universe@422 | 27 | */ |
universe@422 | 28 | |
universe@422 | 29 | #include "util_allocator.h" |
universe@770 | 30 | #include "cx/test.h" |
universe@422 | 31 | |
universe@770 | 32 | static void cx_testing_allocator_track(CxTestingAllocator *alloc, void *ptr) { |
universe@770 | 33 | for (size_t i = 0; i < alloc->tracked_count; i++) { |
universe@770 | 34 | if (alloc->tracked[i] == ptr) return; // is already tracked |
universe@770 | 35 | } |
universe@770 | 36 | |
universe@770 | 37 | if (alloc->tracked_count == alloc->tracked_capacity) { |
universe@770 | 38 | size_t newcapa = alloc->tracked_capacity + 64; |
universe@770 | 39 | void *newarr = realloc(alloc->tracked, newcapa * sizeof(void *)); |
universe@770 | 40 | if (newarr == NULL) abort(); |
universe@770 | 41 | alloc->tracked = newarr; |
universe@770 | 42 | alloc->tracked_capacity = newcapa; |
universe@770 | 43 | } |
universe@770 | 44 | |
universe@770 | 45 | alloc->tracked[alloc->tracked_count] = ptr; |
universe@770 | 46 | alloc->tracked_count++; |
universe@770 | 47 | } |
universe@770 | 48 | |
universe@770 | 49 | static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc, void *ptr) { |
universe@770 | 50 | for (size_t i = 0; i < alloc->tracked_count; i++) { |
universe@770 | 51 | if (alloc->tracked[i] == ptr) { |
universe@770 | 52 | size_t last = alloc->tracked_count - 1; |
universe@770 | 53 | if (i < last) { |
universe@770 | 54 | alloc->tracked[i] = alloc->tracked[last]; |
universe@770 | 55 | } |
universe@770 | 56 | alloc->tracked_count--; |
universe@770 | 57 | return true; |
universe@770 | 58 | } |
universe@770 | 59 | } |
universe@770 | 60 | return false; |
universe@770 | 61 | } |
universe@770 | 62 | |
universe@770 | 63 | static void *cx_malloc_testing(void *d, size_t n) { |
universe@770 | 64 | CxTestingAllocator *data = d; |
universe@422 | 65 | void *ptr = malloc(n); |
universe@422 | 66 | data->alloc_total++; |
universe@770 | 67 | if (ptr == NULL) { |
universe@422 | 68 | data->alloc_failed++; |
universe@422 | 69 | } else { |
universe@770 | 70 | cx_testing_allocator_track(data, ptr); |
universe@422 | 71 | } |
universe@422 | 72 | return ptr; |
universe@422 | 73 | } |
universe@422 | 74 | |
universe@770 | 75 | static void *cx_realloc_testing(void *d, void *mem, size_t n) { |
universe@770 | 76 | CxTestingAllocator *data = d; |
universe@422 | 77 | void *ptr = realloc(mem, n); |
universe@422 | 78 | if (ptr == mem) { |
universe@422 | 79 | return ptr; |
universe@422 | 80 | } else { |
universe@422 | 81 | data->alloc_total++; |
universe@770 | 82 | if (ptr == NULL) { |
universe@422 | 83 | data->alloc_failed++; |
universe@422 | 84 | } else { |
universe@422 | 85 | data->free_total++; |
universe@814 | 86 | #if !defined(__clang__) && __GNUC__ > 11 |
universe@775 | 87 | #pragma GCC diagnostic push |
universe@775 | 88 | #pragma GCC diagnostic ignored "-Wuse-after-free" |
universe@814 | 89 | #endif |
universe@770 | 90 | if (!cx_testing_allocator_untrack(data, mem)) { |
universe@422 | 91 | data->free_failed++; |
universe@422 | 92 | } |
universe@814 | 93 | #if !defined(__clang__) && __GNUC__ > 11 |
universe@775 | 94 | #pragma GCC diagnostic pop |
universe@814 | 95 | #endif |
universe@770 | 96 | cx_testing_allocator_track(data, ptr); |
universe@422 | 97 | } |
universe@422 | 98 | return ptr; |
universe@422 | 99 | } |
universe@422 | 100 | } |
universe@422 | 101 | |
universe@770 | 102 | static void *cx_calloc_testing(void *d, size_t nelem, size_t n) { |
universe@770 | 103 | CxTestingAllocator *data = d; |
universe@422 | 104 | void *ptr = calloc(nelem, n); |
universe@422 | 105 | data->alloc_total++; |
universe@770 | 106 | if (ptr == NULL) { |
universe@422 | 107 | data->alloc_failed++; |
universe@422 | 108 | } else { |
universe@770 | 109 | cx_testing_allocator_track(data, ptr); |
universe@422 | 110 | } |
universe@422 | 111 | return ptr; |
universe@422 | 112 | } |
universe@422 | 113 | |
universe@770 | 114 | static void cx_free_testing(void *d, void *mem) { |
universe@770 | 115 | CxTestingAllocator *data = d; |
universe@422 | 116 | data->free_total++; |
universe@770 | 117 | if (cx_testing_allocator_untrack(data, mem)) { |
universe@770 | 118 | free(mem); |
universe@770 | 119 | } else { |
universe@422 | 120 | data->free_failed++; |
universe@422 | 121 | // do not even attempt to free mem, because it is likely to segfault |
universe@422 | 122 | } |
universe@422 | 123 | } |
universe@422 | 124 | |
universe@422 | 125 | cx_allocator_class cx_testing_allocator_class = { |
universe@422 | 126 | cx_malloc_testing, |
universe@422 | 127 | cx_realloc_testing, |
universe@422 | 128 | cx_calloc_testing, |
universe@422 | 129 | cx_free_testing |
universe@422 | 130 | }; |
universe@422 | 131 | |
universe@770 | 132 | |
universe@770 | 133 | void cx_testing_allocator_init(CxTestingAllocator *alloc) { |
universe@770 | 134 | alloc->base.cl = &cx_testing_allocator_class; |
universe@770 | 135 | alloc->base.data = alloc; |
universe@770 | 136 | alloc->alloc_failed = 0; |
universe@770 | 137 | alloc->alloc_total = 0; |
universe@770 | 138 | alloc->free_failed = 0; |
universe@770 | 139 | alloc->free_total = 0; |
universe@770 | 140 | size_t initial_capa = 16; |
universe@770 | 141 | alloc->tracked_capacity = initial_capa; |
universe@770 | 142 | alloc->tracked_count = 0; |
universe@770 | 143 | alloc->tracked = calloc(sizeof(void *), initial_capa); |
universe@422 | 144 | } |
universe@422 | 145 | |
universe@770 | 146 | void cx_testing_allocator_destroy(CxTestingAllocator *alloc) { |
universe@770 | 147 | free(alloc->tracked); |
universe@571 | 148 | } |
universe@571 | 149 | |
universe@770 | 150 | bool cx_testing_allocator_used(CxTestingAllocator const *alloc) { |
universe@770 | 151 | return alloc->alloc_total > 0; |
universe@770 | 152 | } |
universe@770 | 153 | |
universe@770 | 154 | bool cx_testing_allocator_verify(CxTestingAllocator const *alloc) { |
universe@770 | 155 | return alloc->tracked_count == 0 && alloc->alloc_failed == 0 && alloc->free_failed == 0 |
universe@770 | 156 | && alloc->alloc_total == alloc->free_total; |
universe@422 | 157 | } |
universe@518 | 158 | |
universe@518 | 159 | // SELF-TEST |
universe@518 | 160 | |
universe@770 | 161 | CX_TEST(test_util_allocator_expect_free) { |
universe@770 | 162 | CxTestingAllocator talloc; |
universe@770 | 163 | cx_testing_allocator_init(&talloc); |
universe@770 | 164 | CxAllocator *alloc = &talloc.base; |
universe@770 | 165 | CX_TEST_DO { |
universe@770 | 166 | CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), |
universe@770 | 167 | "Fresh testing allocator fails to verify."); |
universe@770 | 168 | CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc), |
universe@770 | 169 | "Fresh testing allocator already used."); |
universe@770 | 170 | void *ptr = cxMalloc(alloc, 16); |
universe@770 | 171 | CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), |
universe@770 | 172 | "Testing allocator verifies with unfreed memory."); |
universe@770 | 173 | CX_TEST_ASSERT(cx_testing_allocator_used(&talloc)); |
universe@770 | 174 | CX_TEST_ASSERT(ptr != NULL); |
universe@518 | 175 | |
universe@770 | 176 | cxFree(alloc, ptr); |
universe@770 | 177 | CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), |
universe@770 | 178 | "Testing allocator fails to verify after everything freed."); |
universe@770 | 179 | } |
universe@770 | 180 | cx_testing_allocator_destroy(&talloc); |
universe@518 | 181 | } |
universe@518 | 182 | |
universe@770 | 183 | CX_TEST(test_util_allocator_detect_double_free) { |
universe@770 | 184 | CxTestingAllocator talloc; |
universe@770 | 185 | cx_testing_allocator_init(&talloc); |
universe@770 | 186 | CxAllocator *alloc = &talloc.base; |
universe@770 | 187 | CX_TEST_DO { |
universe@770 | 188 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 189 | void *ptr = cxMalloc(alloc, 16); |
universe@770 | 190 | CX_TEST_ASSERT(ptr != NULL); |
universe@770 | 191 | cxFree(alloc, ptr); |
universe@770 | 192 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 193 | cxFree(alloc, ptr); |
universe@770 | 194 | CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), |
universe@770 | 195 | "Testing allocator does not detect double-free."); |
universe@770 | 196 | } |
universe@770 | 197 | cx_testing_allocator_destroy(&talloc); |
universe@518 | 198 | } |
universe@518 | 199 | |
universe@770 | 200 | CX_TEST(test_util_allocator_free_untracked) { |
universe@770 | 201 | CxTestingAllocator talloc; |
universe@770 | 202 | cx_testing_allocator_init(&talloc); |
universe@770 | 203 | CxAllocator *alloc = &talloc.base; |
universe@770 | 204 | void *ptr = malloc(16); |
universe@770 | 205 | CX_TEST_DO { |
universe@770 | 206 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 207 | cxFree(alloc, ptr); |
universe@770 | 208 | CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), |
universe@770 | 209 | "Testing allocator does not detect free of untracked memory."); |
universe@770 | 210 | } |
universe@770 | 211 | free(ptr); |
universe@770 | 212 | cx_testing_allocator_destroy(&talloc); |
universe@518 | 213 | } |
universe@518 | 214 | |
universe@770 | 215 | CX_TEST(test_util_allocator_full_lifecycle_with_realloc) { |
universe@770 | 216 | CxTestingAllocator talloc; |
universe@770 | 217 | cx_testing_allocator_init(&talloc); |
universe@770 | 218 | CxAllocator *alloc = &talloc.base; |
universe@770 | 219 | CX_TEST_DO { |
universe@770 | 220 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 221 | void *ptr = cxMalloc(alloc, 16); |
universe@770 | 222 | CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); |
universe@770 | 223 | CX_TEST_ASSERT(ptr != NULL); |
universe@770 | 224 | CX_TEST_ASSERT(talloc.tracked_count == 1); |
universe@770 | 225 | ptr = cxRealloc(alloc, ptr, 256); |
universe@770 | 226 | CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); |
universe@770 | 227 | CX_TEST_ASSERT(ptr != NULL); |
universe@770 | 228 | CX_TEST_ASSERT(talloc.tracked_count == 1); |
universe@770 | 229 | cxFree(alloc, ptr); |
universe@770 | 230 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 231 | } |
universe@770 | 232 | cx_testing_allocator_destroy(&talloc); |
universe@518 | 233 | } |
universe@518 | 234 | |
universe@770 | 235 | CX_TEST(test_util_allocator_calloc_initializes) { |
universe@770 | 236 | CxTestingAllocator talloc; |
universe@770 | 237 | cx_testing_allocator_init(&talloc); |
universe@770 | 238 | CxAllocator *alloc = &talloc.base; |
universe@770 | 239 | CX_TEST_DO { |
universe@770 | 240 | const char zeros[16] = {0}; |
universe@770 | 241 | void *ptr = cxCalloc(alloc, 16, 1); |
universe@770 | 242 | CX_TEST_ASSERT(memcmp(ptr, zeros, 16) == 0); |
universe@770 | 243 | cxFree(alloc, ptr); |
universe@770 | 244 | CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); |
universe@770 | 245 | } |
universe@770 | 246 | cx_testing_allocator_destroy(&talloc); |
universe@518 | 247 | } |
universe@770 | 248 | |
universe@770 | 249 | CxTestSuite *cx_test_suite_testing_allocator(void) { |
universe@770 | 250 | CxTestSuite *suite = cx_test_suite_new("testing allocator self-test"); |
universe@770 | 251 | |
universe@770 | 252 | cx_test_register(suite, test_util_allocator_expect_free); |
universe@770 | 253 | cx_test_register(suite, test_util_allocator_detect_double_free); |
universe@770 | 254 | cx_test_register(suite, test_util_allocator_free_untracked); |
universe@770 | 255 | cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc); |
universe@770 | 256 | cx_test_register(suite, test_util_allocator_calloc_initializes); |
universe@770 | 257 | |
universe@770 | 258 | return suite; |
universe@770 | 259 | } |
universe@770 | 260 |