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