1#include "alloc.h"
2
3#include <stdint.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8
9#define CHECK(cond, msg) \
10 do { \
11 if (!(cond)) { \
12 fprintf(stderr, "FAILED: %s\n at %s:%d\n", (msg), \
13 __FILE__, __LINE__); \
14 exit(1); \
15 } \
16 } while (0)
17
18
19
20
21
22static uint64_t rng_state;
23
24static void rng_seed(uint64_t s) { rng_state = s ? s : 0x9e3779b97f4a7c15ull; }
25
26static uint64_t rng_next(void) {
27 uint64_t x = rng_state;
28 x ^= x << 13;
29 x ^= x >> 7;
30 x ^= x << 17;
31 rng_state = x;
32 return x;
33}
34
35static uint32_t rng_range(uint32_t n) { return (uint32_t)(rng_next() % n); }
36
37
38
39
40
41
42static void fill_pattern(unsigned char *p, size_t n, uint8_t seed) {
43 for (size_t i = 0; i < n; i++) p[i] = (uint8_t)(seed + (uint8_t)i);
44}
45
46static int verify_pattern(const unsigned char *p, size_t n, uint8_t seed) {
47 for (size_t i = 0; i < n; i++)
48 if (p[i] != (uint8_t)(seed + (uint8_t)i)) return 0;
49 return 1;
50}
51
52
53
54
55
56static void test_basics(void) {
57 CHECK(my_malloc(0) == NULL, "malloc(0) must return NULL");
58 CHECK(my_realloc(NULL, 0) == NULL, "realloc(NULL,0) must return NULL");
59
60
61 unsigned char *a = my_realloc(NULL, 100);
62 CHECK(a != NULL, "realloc(NULL,100) should allocate");
63 fill_pattern(a, 100, 0x11);
64
65
66 unsigned char *b = my_realloc(a, 5000);
67 CHECK(b != NULL, "grow realloc should succeed");
68 CHECK(verify_pattern(b, 100, 0x11), "grow must preserve bytes");
69
70
71 unsigned char *c = my_realloc(b, 40);
72 CHECK(c != NULL, "shrink realloc should succeed");
73 CHECK(verify_pattern(c, 40, 0x11), "shrink must preserve bytes");
74 CHECK(my_realloc(c, 0) == NULL, "realloc(p,0) frees and returns NULL");
75
76
77 size_t n = 64;
78 unsigned char *z = my_calloc(n, 4);
79 CHECK(z != NULL, "calloc should succeed");
80 for (size_t i = 0; i < n * 4; i++) CHECK(z[i] == 0, "calloc must zero memory");
81 my_free(z);
82 CHECK(my_calloc((size_t)-1, 2) == NULL, "calloc overflow must return NULL");
83
84 my_free(NULL);
85
86 CHECK(my_heap_check() == HEAP_OK, "heap consistent after basics");
87 heap_stats_t s;
88 my_stats(&s);
89 CHECK(s.bytes_in_use == 0, "everything freed after basics");
90 CHECK(s.free_blocks == 1, "fully coalesced after basics");
91 CHECK(s.largest_free_block == my_heap_capacity(), "full capacity recovered");
92
93 printf(" [ok] basics: malloc/free/calloc/realloc semantics\n");
94}
95
96
97
98
99
100static void test_fragmentation(void) {
101 heap_stats_t s;
102 my_stats(&s);
103 const size_t initial_largest = s.largest_free_block;
104 CHECK(initial_largest == my_heap_capacity(), "start from an empty heap");
105
106
107 enum { CAP = 512 };
108 void *blk[CAP];
109 int n = 0;
110 while (n < CAP) {
111 void *p = my_malloc(4096);
112 if (!p) break;
113 blk[n++] = p;
114 }
115 CHECK(n > 8, "should place many blocks before the arena fills");
116 CHECK(my_heap_check() == HEAP_OK, "heap consistent when full");
117
118 my_stats(&s);
119 CHECK(s.largest_free_block < 4096 + 4096, "arena is essentially full");
120
121
122
123 for (int i = 0; i < n; i += 2) {
124 my_free(blk[i]);
125 blk[i] = NULL;
126 }
127 CHECK(my_heap_check() == HEAP_OK, "heap consistent while fragmented");
128
129 my_stats(&s);
130 const size_t fragmented_largest = s.largest_free_block;
131 CHECK(fragmented_largest < initial_largest / 4,
132 "fragmented heap has no large contiguous block");
133 CHECK(s.free_blocks > 4, "fragmentation produced many free blocks");
134
135
136 for (int i = 1; i < n; i += 2) {
137 if (blk[i]) my_free(blk[i]);
138 blk[i] = NULL;
139 }
140 CHECK(my_heap_check() == HEAP_OK, "heap consistent after full free");
141
142 my_stats(&s);
143 CHECK(s.free_blocks == 1, "coalescing must collapse to a single free block");
144 CHECK(s.largest_free_block == initial_largest,
145 "coalescing must recover the full-size largest block");
146
147 printf(" [ok] fragmentation: fragmented to %zu bytes, coalesced back to %zu\n",
148 fragmented_largest, s.largest_free_block);
149}
150
151
152
153
154
155static void test_canary(void) {
156 CHECK(my_heap_check() == HEAP_OK, "heap clean before canary test");
157
158
159
160 const size_t n = 64;
161 unsigned char *p = my_malloc(n);
162 CHECK(p != NULL, "canary test allocation");
163 fill_pattern(p, n, 0x5a);
164 CHECK(my_heap_check() == HEAP_OK, "heap clean with a valid canary");
165
166
167
168
169 unsigned char *overflow = p + n;
170 unsigned char saved[8];
171 memcpy(saved, overflow, sizeof saved);
172 overflow[0] ^= 0xFF;
173
174 int rc = my_heap_check();
175 CHECK(rc == HEAP_ERR_FOOT_CANARY, "overflow must be reported as a footer-canary violation");
176
177
178 memcpy(overflow, saved, sizeof saved);
179 CHECK(my_heap_check() == HEAP_OK, "heap clean after repairing the canary");
180 CHECK(verify_pattern(p, n, 0x5a), "user bytes intact after repair");
181
182 my_free(p);
183 CHECK(my_heap_check() == HEAP_OK, "heap clean after freeing canary block");
184
185 printf(" [ok] canary: footer overflow detected (code %d) and validated clean after repair\n", rc);
186}
187
188
189
190
191
192static void test_stress(void) {
193 enum { NSLOTS = 256, MAXSZ = 4096, NOPS = 100000, CHECK_EVERY = 1000 };
194
195 typedef struct {
196 unsigned char *ptr;
197 size_t size;
198 uint8_t seed;
199 } slot_t;
200
201 static slot_t slot[NSLOTS];
202 memset(slot, 0, sizeof slot);
203
204 rng_seed(0xC0FFEEull);
205
206 unsigned long n_malloc = 0, n_calloc = 0, n_free = 0;
207 unsigned long n_realloc = 0, n_oom = 0, n_verify = 0;
208
209 for (int op = 0; op < NOPS; op++) {
210 uint32_t i = rng_range(NSLOTS);
211 slot_t *sp = &slot[i];
212
213 if (sp->ptr == NULL) {
214
215 size_t sz = 1 + rng_range(MAXSZ);
216 unsigned char *p;
217 if (rng_range(2) == 0) {
218 p = my_malloc(sz);
219 n_malloc++;
220 } else {
221 p = my_calloc(sz, 1);
222 n_calloc++;
223 if (p)
224 for (size_t k = 0; k < sz; k++)
225 CHECK(p[k] == 0, "calloc memory must be zeroed");
226 }
227 if (!p) { n_oom++; continue; }
228
229 uint8_t seed = (uint8_t)rng_next();
230 fill_pattern(p, sz, seed);
231 sp->ptr = p;
232 sp->size = sz;
233 sp->seed = seed;
234 } else {
235
236 CHECK(verify_pattern(sp->ptr, sp->size, sp->seed),
237 "shadow pattern must match before touching a block");
238 n_verify++;
239
240 uint32_t what = rng_range(3);
241 if (what == 0) {
242 my_free(sp->ptr);
243 n_free++;
244 sp->ptr = NULL;
245 sp->size = 0;
246 } else if (what == 1) {
247 size_t nsz = 1 + rng_range(MAXSZ);
248 unsigned char *q = my_realloc(sp->ptr, nsz);
249 n_realloc++;
250 if (q) {
251 size_t keep = sp->size < nsz ? sp->size : nsz;
252 CHECK(verify_pattern(q, keep, sp->seed),
253 "realloc must preserve the overlapping prefix");
254 uint8_t ns = (uint8_t)rng_next();
255 fill_pattern(q, nsz, ns);
256 sp->ptr = q;
257 sp->size = nsz;
258 sp->seed = ns;
259 } else {
260
261 n_oom++;
262 CHECK(verify_pattern(sp->ptr, sp->size, sp->seed),
263 "failed realloc must leave the original intact");
264 }
265 }
266
267 }
268
269 if (op % CHECK_EVERY == 0)
270 CHECK(my_heap_check() == HEAP_OK, "heap must stay consistent under stress");
271 }
272
273
274 for (int i = 0; i < NSLOTS; i++) {
275 if (slot[i].ptr) {
276 CHECK(verify_pattern(slot[i].ptr, slot[i].size, slot[i].seed),
277 "final shadow pattern must match");
278 my_free(slot[i].ptr);
279 slot[i].ptr = NULL;
280 }
281 }
282
283 CHECK(my_heap_check() == HEAP_OK, "heap consistent after draining");
284 heap_stats_t s;
285 my_stats(&s);
286 CHECK(s.bytes_in_use == 0, "nothing should remain in use");
287 CHECK(s.free_blocks == 1, "heap must coalesce to a single free block");
288 CHECK(s.largest_free_block == my_heap_capacity(), "full capacity recovered");
289
290 printf(" [ok] stress: %d ops (malloc %lu, calloc %lu, realloc %lu, free %lu, "
291 "verify %lu, oom %lu)\n",
292 NOPS, n_malloc, n_calloc, n_realloc, n_free, n_verify, n_oom);
293}
294
295int main(void) {
296 printf("running my_alloc tests...\n");
297 test_basics();
298 test_fragmentation();
299 test_canary();
300 test_stress();
301 printf("ALL TESTS PASSED\n");
302 return 0;
303}
304
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.