1 #include "ik/memory.h"
2 #include "ik/bst_vector.h"
3 #include "ik/backtrace.h"
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <assert.h>
8 
9 #define BACKTRACE_OMIT_COUNT 2
10 
11 #ifdef IK_MEMORY_DEBUGGING
12 static uintptr_t g_allocations = 0;
13 static uintptr_t d_deg_allocations = 0;
14 static uintptr_t g_ignore_bstv_malloc = 0;
15 static bstv_t report;
16 
17 typedef struct report_info_t
18 {
19     uintptr_t location;
20     uintptr_t size;
21 #   ifdef IK_MEMORY_BACKTRACE
22     int backtrace_size;
23     char** backtrace;
24 #   endif
25 } report_info_t;
26 
27 /* ------------------------------------------------------------------------- */
28 void
ik_memory_init(void)29 ik_memory_init(void)
30 {
31     g_allocations = 0;
32     d_deg_allocations = 0;
33 
34     /*
35      * Init bst vector of report objects and force it to allocate by adding
36      * and removing one item. This fixes a bug where the number of memory leaks
37      * would be wrong in the case of MALLOC() never being called.
38      */
39     g_ignore_bstv_malloc = 1;
40         bstv_construct(&report);
41         bstv_insert(&report, 0, NULL); bstv_erase(&report, 0);
42     g_ignore_bstv_malloc = 0;
43 }
44 
45 /* ------------------------------------------------------------------------- */
46 void*
malloc_wrapper(intptr_t size)47 malloc_wrapper(intptr_t size)
48 {
49     void* p = NULL;
50     report_info_t* info = NULL;
51 
52     /* breaking from this will clean up and return NULL */
53     for (;;)
54     {
55         /* allocate */
56         p = malloc(size);
57         if (p)
58             ++g_allocations;
59         else
60             break;
61 
62         /*
63         * Record allocation info. Call to bstv may allocate memory,
64         * so set flag to ignore the call to malloc() when inserting.
65         */
66         if (!g_ignore_bstv_malloc)
67         {
68             g_ignore_bstv_malloc = 1;
69             info = (report_info_t*)malloc(sizeof(report_info_t));
70             if (!info)
71             {
72                 fprintf(stderr, "[memory] ERROR: malloc() for report_info_t failed"
73                     " -- not enough memory.\n");
74                 g_ignore_bstv_malloc = 0;
75                 break;
76             }
77 
78             /* record the location and size of the allocation */
79             info->location = (uintptr_t)p;
80             info->size = size;
81 
82             /* if (enabled, generate a backtrace so we know where memory leaks
83             * occurred */
84 #   ifdef IK_MEMORY_BACKTRACE
85             if (!(info->backtrace = get_backtrace(&info->backtrace_size)))
86                 fprintf(stderr, "[memory] WARNING: Failed to generate backtrace\n");
87 #   endif
88 
89             /* insert into bstv */
90             if (bstv_insert(&report, (uintptr_t)p, info) == 1)
91             {
92                 fprintf(stderr,
93                 "[memory] WARNING: Hash collision occurred when inserting\n"
94                 "into memory report bstv. On 64-bit systems the pointers are\n"
95                 "rounded down to 32-bit unsigned integers, so even though\n"
96                 "it's rare, collisions can happen.\n\n"
97                 "The matching call to FREE() will generate a warning saying\n"
98                 "something is being freed that was never allocated. This is to\n"
99                 "be expected and can be ignored.\n");
100 #   ifdef IK_MEMORY_BACKTRACE
101                 {
102                     char** bt;
103                     int bt_size, i;
104                     if ((bt = get_backtrace(&bt_size)))
105                     {
106                         printf("  backtrace to where malloc() was called:\n");
107                         for (i = 0; i < bt_size; ++i)
108                             printf("      %s\n", bt[i]);
109                         printf("  -----------------------------------------\n");
110                         free(bt);
111                     }
112                     else
113                         fprintf(stderr, "[memory] WARNING: Failed to generate backtrace\n");
114                 }
115 #   endif
116             }
117             g_ignore_bstv_malloc = 0;
118         }
119 
120         /* success */
121         return p;
122     }
123 
124     /* failure */
125     if (p)
126     {
127         free(p);
128         --g_allocations;
129     }
130 
131     if (info)
132     {
133 #   ifdef IK_MEMORY_BACKTRACE
134         if (info->backtrace)
135             free(info->backtrace);
136 #   endif
137         free(info);
138     }
139 
140     return NULL;
141 }
142 
143 /* ------------------------------------------------------------------------- */
144 void
free_wrapper(void * ptr)145 free_wrapper(void* ptr)
146 {
147     /* find matching allocation and remove from bstv */
148     if (!g_ignore_bstv_malloc)
149     {
150         report_info_t* info = (report_info_t*)bstv_erase(&report, (uintptr_t)ptr);
151         if (info)
152         {
153 #   ifdef IK_MEMORY_BACKTRACE
154             if (info->backtrace)
155                 free(info->backtrace);
156             else
157                 fprintf(stderr, "[memory] WARNING: free(): Allocation didn't "
158                     "have a backtrace (it was NULL)\n");
159 #   endif
160             free(info);
161         }
162         else
163         {
164 #   ifdef IK_MEMORY_BACKTRACE
165             char** bt;
166             int bt_size, i;
167             fprintf(stderr, "  -----------------------------------------\n");
168 #   endif
169             fprintf(stderr, "  WARNING: Freeing something that was never allocated\n");
170 #   ifdef IK_MEMORY_BACKTRACE
171             if ((bt = get_backtrace(&bt_size)))
172             {
173                 fprintf(stderr, "  backtrace to where free() was called:\n");
174                 for (i = 0; i < bt_size; ++i)
175                     fprintf(stderr, "      %s\n", bt[i]);
176                 fprintf(stderr, "  -----------------------------------------\n");
177                 free(bt);
178             }
179             else
180                 fprintf(stderr, "[memory] WARNING: Failed to generate backtrace\n");
181 #   endif
182         }
183     }
184 
185     if (ptr)
186     {
187         ++d_deg_allocations;
188         free(ptr);
189     }
190     else
191         fprintf(stderr, "Warning: free(NULL)\n");
192 }
193 
194 /* ------------------------------------------------------------------------- */
195 uintptr_t
ik_memory_deinit(void)196 ik_memory_deinit(void)
197 {
198     uintptr_t leaks;
199 
200     --g_allocations; /* this is the single allocation still held by the report vector */
201 
202     printf("=========================================\n");
203     printf("Inverse Kinematics Memory Report\n");
204     printf("=========================================\n");
205 
206     /* report details on any g_allocations that were not de-allocated */
207     if (report.vector.count != 0)
208     {
209         BSTV_FOR_EACH(&report, report_info_t, key, info)
210 
211             printf("  un-freed memory at %p, size %p\n", (void*)info->location, (void*)info->size);
212             mutated_string_and_hex_dump((void*)info->location, info->size);
213 
214 #   ifdef IK_MEMORY_BACKTRACE
215             printf("  Backtrace to where malloc() was called:\n");
216             {
217                 intptr_t i;
218                 for (i = BACKTRACE_OMIT_COUNT; i < info->backtrace_size; ++i)
219                     printf("      %s\n", info->backtrace[i]);
220             }
221             free(info->backtrace); /* this was allocated when malloc() was called */
222             printf("  -----------------------------------------\n");
223 #   endif
224             free(info);
225 
226         BSTV_END_EACH
227 
228         printf("=========================================\n");
229     }
230 
231     /* overall report */
232     leaks = (g_allocations > d_deg_allocations ? g_allocations - d_deg_allocations : d_deg_allocations - g_allocations);
233     printf("allocations: %lu\n", g_allocations);
234     printf("deallocations: %lu\n", d_deg_allocations);
235     printf("memory leaks: %lu\n", leaks);
236     printf("=========================================\n");
237 
238     ++g_allocations; /* this is the single allocation still held by the report vector */
239     g_ignore_bstv_malloc = 1;
240     bstv_clear_free(&report);
241 
242     return leaks;
243 }
244 
245 #else /* IK_MEMORY_DEBUGGING */
246 
ik_memory_init(void)247 void ik_memory_init(void) {}
ik_memory_deinit(void)248 uintptr_t ik_memory_deinit(void) { return 0; }
249 
250 #endif /* IK_MEMORY_DEBUGGING */
251 
252 /* ------------------------------------------------------------------------- */
253 void
mutated_string_and_hex_dump(void * data,intptr_t length_in_bytes)254 mutated_string_and_hex_dump(void* data, intptr_t length_in_bytes)
255 {
256     char* dump;
257     intptr_t i;
258 
259     /* allocate and copy data into new buffer */
260     if (!(dump = malloc(length_in_bytes + 1)))
261     {
262         fprintf(stderr, "[memory] WARNING: Failed to malloc() space for dump\n");
263         return;
264     }
265     memcpy(dump, data, length_in_bytes);
266     dump[length_in_bytes] = '\0';
267 
268     /* mutate null terminators into dots */
269     for (i = 0; i != length_in_bytes; ++i)
270         if (dump[i] == '\0')
271             dump[i] = '.';
272 
273     /* dump */
274     printf("  mutated string dump: %s\n", dump);
275     printf("  hex dump: ");
276     for (i = 0; i != length_in_bytes; ++i)
277         printf(" %02x", (unsigned char)dump[i]);
278     printf("\n");
279 
280     free(dump);
281 }
282