]> git.draconx.ca Git - cdecl99.git/blob - t/memwrap.c
8b54433afbeeeb793b6d8090f5afc2f61d347bfe
[cdecl99.git] / t / memwrap.c
1 /*
2  * Allocation wrapper for test purposes.
3  *
4  * Copyright © 2024 Nick Bowler
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <assert.h>
24 #include "cdecl-internal.h"
25
26 static union test_alloc {
27         union test_alloc *p;
28         max_align_t a;
29         size_t v;
30 } *alloc_head;
31
32 enum {
33         ALLOC_NEXT_PTR,
34         ALLOC_STATE,
35         ALLOC_SIZE,
36         ALLOC_PAYLOAD
37 };
38
39 #define ALLOC_STATE_ALLOCATED 0x7143u
40 #define ALLOC_STATE_FREED     0xdeadu
41
42 /*
43  * Hook for testing allocation behaviour of the library, enabling a basic
44  * verification in test cases that the library does not leak memory.
45  *
46  * The library sources must be recompiled with -DTEST_MALLOC_HOOK to
47  * make use of this functionality.
48  *
49  * In this implementation, allocated memory is never freed, instead just
50  * marked with an indication that the memory is no longer live.
51  */
52 void *test_realloc_hook(void *p, size_t n)
53 {
54         union test_alloc *old_alloc = p, *alloc;
55
56         if (old_alloc) {
57                 old_alloc = &old_alloc[-ALLOC_PAYLOAD];
58                 if (old_alloc[ALLOC_STATE].v != ALLOC_STATE_ALLOCATED) {
59                         printf("Bail out! %p is not a live allocation!\n", p);
60                         exit(99);
61                 }
62
63                 old_alloc[ALLOC_STATE].v = ALLOC_STATE_FREED;
64                 printf("# %p freed\n", p);
65         }
66
67         if (!n)
68                 return NULL;
69
70         n = (n + sizeof *alloc - 1) / sizeof *alloc;
71         alloc = (malloc)((n + ALLOC_PAYLOAD) * sizeof *alloc);
72         if (!alloc)
73                 return NULL;
74
75         alloc[ALLOC_NEXT_PTR].p = alloc_head;
76         alloc_head = alloc;
77
78         alloc[ALLOC_STATE].v = ALLOC_STATE_ALLOCATED;
79         alloc[ALLOC_SIZE].v  = n * sizeof *alloc;
80
81         if (old_alloc) {
82                 n = old_alloc[ALLOC_SIZE].v;
83                 memcpy(&alloc[ALLOC_PAYLOAD], &old_alloc[ALLOC_PAYLOAD], n);
84         }
85
86         p = &alloc[ALLOC_PAYLOAD];
87         printf("# %p allocated\n", p);
88         return p;
89 }
90
91 /*
92  * Returns the total number of allocations that have not yet been freed.
93  */
94 size_t test_live_allocations(void)
95 {
96         union test_alloc *a;
97         size_t ret = 0;
98
99         for (a = alloc_head; a; a = a[ALLOC_NEXT_PTR].p) {
100                 void *p = &a[ALLOC_PAYLOAD];
101
102                 switch (a[ALLOC_STATE].v) {
103                 case ALLOC_STATE_ALLOCATED:
104                         printf("# %p still live\n", p);
105                         ret++;
106                         break;
107                 case ALLOC_STATE_FREED:
108                         break;
109                 default:
110                         printf("Bail out! detected %p state corruption\n", p);
111                         exit(99);
112                 }
113         }
114
115         return ret;
116 }