1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include "util_allocator.h"
30 #include "cx/test.h"
31
32 #if !defined(__clang__) &&
__GNUC__ >
11
33
34 #pragma GCC diagnostic ignored
"-Wuse-after-free"
35 #endif
36
37 static void cx_testing_allocator_track(CxTestingAllocator *alloc,
void *ptr) {
38 for (
size_t i =
0; i < alloc->tracked_count; i++) {
39 if (alloc->tracked[i] == ptr)
return;
40 }
41
42 if (alloc->tracked_count == alloc->tracked_capacity) {
43 size_t newcapa = alloc->tracked_capacity +
64;
44 void *newarr = realloc(alloc->tracked, newcapa *
sizeof(
void *));
45 if (newarr ==
NULL) abort();
46 alloc->tracked = newarr;
47 alloc->tracked_capacity = newcapa;
48 }
49
50 alloc->tracked[alloc->tracked_count] = ptr;
51 alloc->tracked_count++;
52 }
53
54 static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc,
void *ptr) {
55 for (
size_t i =
0; i < alloc->tracked_count; i++) {
56 if (alloc->tracked[i] == ptr) {
57 size_t last = alloc->tracked_count -
1;
58 if (i < last) {
59 alloc->tracked[i] = alloc->tracked[last];
60 }
61 alloc->tracked_count--;
62 return true;
63 }
64 }
65 return false;
66 }
67
68 static void *cx_malloc_testing(
void *d,
size_t n) {
69 CxTestingAllocator *data = d;
70 void *ptr = malloc(n);
71 data->alloc_total++;
72 if (ptr ==
NULL) {
73 data->alloc_failed++;
74 }
else {
75 cx_testing_allocator_track(data, ptr);
76 }
77 return ptr;
78 }
79
80 static void *cx_realloc_testing(
void *d,
void *mem,
size_t n) {
81 CxTestingAllocator *data = d;
82 void *ptr = realloc(mem, n);
83 if (mem !=
NULL && ptr == mem) {
84 return ptr;
85 }
else {
86 data->alloc_total++;
87 if (ptr ==
NULL) {
88 data->alloc_failed++;
89 }
else {
90 if (mem !=
NULL) {
91 data->free_total++;
92 if (!cx_testing_allocator_untrack(data, mem)) {
93 data->free_failed++;
94 }
95 }
96 cx_testing_allocator_track(data, ptr);
97 }
98 return ptr;
99 }
100 }
101
102 static void *cx_calloc_testing(
void *d,
size_t nelem,
size_t n) {
103 CxTestingAllocator *data = d;
104 void *ptr = calloc(nelem, n);
105 data->alloc_total++;
106 if (ptr ==
NULL) {
107 data->alloc_failed++;
108 }
else {
109 cx_testing_allocator_track(data, ptr);
110 }
111 return ptr;
112 }
113
114 static void cx_free_testing(
void *d,
void *mem) {
115 if (mem ==
NULL)
return;
116 CxTestingAllocator *data = d;
117 data->free_total++;
118 if (cx_testing_allocator_untrack(data, mem)) {
119 free(mem);
120 }
else {
121 data->free_failed++;
122
123 }
124 }
125
126 cx_allocator_class cx_testing_allocator_class = {
127 cx_malloc_testing,
128 cx_realloc_testing,
129 cx_calloc_testing,
130 cx_free_testing
131 };
132
133
134 void cx_testing_allocator_init(CxTestingAllocator *alloc) {
135 alloc->base.cl = &cx_testing_allocator_class;
136 alloc->base.data = alloc;
137 alloc->alloc_failed =
0;
138 alloc->alloc_total =
0;
139 alloc->free_failed =
0;
140 alloc->free_total =
0;
141 size_t initial_capa =
16;
142 alloc->tracked_capacity = initial_capa;
143 alloc->tracked_count =
0;
144 alloc->tracked = calloc(initial_capa,
sizeof(
void *));
145 }
146
147 void cx_testing_allocator_destroy(CxTestingAllocator *alloc) {
148 free(alloc->tracked);
149 }
150
151 bool cx_testing_allocator_used(
const CxTestingAllocator *alloc) {
152 return alloc->alloc_total >
0;
153 }
154
155 bool cx_testing_allocator_verify(
const CxTestingAllocator *alloc) {
156 return alloc->tracked_count ==
0 && alloc->alloc_failed ==
0 && alloc->free_failed ==
0
157 && alloc->alloc_total == alloc->free_total;
158 }
159
160
161
162 CX_TEST(test_util_allocator_expect_free) {
163 CxTestingAllocator talloc;
164 cx_testing_allocator_init(&talloc);
165 CxAllocator *alloc = &talloc.base;
166 CX_TEST_DO {
167 CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
168 "Fresh testing allocator fails to verify.");
169 CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc),
170 "Fresh testing allocator already used.");
171 void *ptr = cxMalloc(alloc,
16);
172 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
173 "Testing allocator verifies with unfreed memory.");
174 CX_TEST_ASSERT(cx_testing_allocator_used(&talloc));
175 CX_TEST_ASSERT(ptr !=
NULL);
176
177 cxFree(alloc, ptr);
178 CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc),
179 "Testing allocator fails to verify after everything freed.");
180 }
181 cx_testing_allocator_destroy(&talloc);
182 }
183
184 CX_TEST(test_util_allocator_detect_double_free) {
185 CxTestingAllocator talloc;
186 cx_testing_allocator_init(&talloc);
187 CxAllocator *alloc = &talloc.base;
188 CX_TEST_DO {
189 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
190 void *ptr = cxMalloc(alloc,
16);
191 CX_TEST_ASSERT(ptr !=
NULL);
192 cxFree(alloc, ptr);
193 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
194 cxFree(alloc, ptr);
195 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
196 "Testing allocator does not detect double-free.");
197 }
198 cx_testing_allocator_destroy(&talloc);
199 }
200
201 CX_TEST(test_util_allocator_free_untracked) {
202 CxTestingAllocator talloc;
203 cx_testing_allocator_init(&talloc);
204 CxAllocator *alloc = &talloc.base;
205 void *ptr = malloc(
16);
206 CX_TEST_DO {
207 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
208 cxFree(alloc, ptr);
209 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc),
210 "Testing allocator does not detect free of untracked memory.");
211 }
212 free(ptr);
213 cx_testing_allocator_destroy(&talloc);
214 }
215
216 CX_TEST(test_util_allocator_full_lifecycle_with_realloc) {
217 CxTestingAllocator talloc;
218 cx_testing_allocator_init(&talloc);
219 CxAllocator *alloc = &talloc.base;
220 CX_TEST_DO {
221 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
222 void *ptr = cxMalloc(alloc,
16);
223 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
224 CX_TEST_ASSERT(ptr !=
NULL);
225 CX_TEST_ASSERT(talloc.tracked_count ==
1);
226 ptr = cxRealloc(alloc, ptr,
256);
227 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
228 CX_TEST_ASSERT(ptr !=
NULL);
229 CX_TEST_ASSERT(talloc.tracked_count ==
1);
230 cxFree(alloc, ptr);
231 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
232 }
233 cx_testing_allocator_destroy(&talloc);
234 }
235
236 CX_TEST(test_util_allocator_calloc_initializes) {
237 CxTestingAllocator talloc;
238 cx_testing_allocator_init(&talloc);
239 CxAllocator *alloc = &talloc.base;
240 CX_TEST_DO {
241 const char zeros[
16] = {
0};
242 void *ptr = cxCalloc(alloc,
16,
1);
243 CX_TEST_ASSERT(memcmp(ptr, zeros,
16) ==
0);
244 cxFree(alloc, ptr);
245 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
246 }
247 cx_testing_allocator_destroy(&talloc);
248 }
249
250 CxTestSuite *cx_test_suite_testing_allocator(
void) {
251 CxTestSuite *suite = cx_test_suite_new(
"testing allocator self-test");
252
253 cx_test_register(suite, test_util_allocator_expect_free);
254 cx_test_register(suite, test_util_allocator_detect_double_free);
255 cx_test_register(suite, test_util_allocator_free_untracked);
256 cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc);
257 cx_test_register(suite, test_util_allocator_calloc_initializes);
258
259 return suite;
260 }
261
262