Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2017 the mpv developers |
2 | | * |
3 | | * Permission to use, copy, modify, and/or distribute this software for any |
4 | | * purpose with or without fee is hereby granted, provided that the above |
5 | | * copyright notice and this permission notice appear in all copies. |
6 | | * |
7 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
8 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
9 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
10 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
11 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
12 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
13 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
14 | | */ |
15 | | |
16 | | #include <assert.h> |
17 | | #include <stddef.h> |
18 | | #include <stdio.h> |
19 | | #include <stdlib.h> |
20 | | #include <string.h> |
21 | | |
22 | | #define TA_NO_WRAPPERS |
23 | | #include "ta.h" |
24 | | |
25 | | #if !defined(TA_MEMORY_DEBUGGING) |
26 | | #if !defined(NDEBUG) |
27 | | #define TA_MEMORY_DEBUGGING 1 |
28 | | #else |
29 | | #define TA_MEMORY_DEBUGGING 0 |
30 | | #endif |
31 | | #endif |
32 | | |
33 | | struct ta_header { |
34 | | size_t size; // size of the user allocation |
35 | | // Invariant: parent!=NULL => prev==NULL |
36 | | struct ta_header *prev; // siblings list (by destructor order) |
37 | | struct ta_header *next; |
38 | | // Invariant: parent==NULL || parent->child==this |
39 | | struct ta_header *child; // points to first child |
40 | | struct ta_header *parent; // set for _first_ child only, NULL otherwise |
41 | | void (*destructor)(void *); |
42 | | #if TA_MEMORY_DEBUGGING |
43 | | unsigned int canary; |
44 | | struct ta_header *leak_next; |
45 | | struct ta_header *leak_prev; |
46 | | const char *name; |
47 | | #endif |
48 | | }; |
49 | | |
50 | 2.04G | #define CANARY 0xD3ADB3EF |
51 | | |
52 | | #define MIN_ALIGN _Alignof(max_align_t) |
53 | | union aligned_header { |
54 | | struct ta_header ta; |
55 | | // Make sure to satisfy typical alignment requirements |
56 | | void *align_ptr; |
57 | | int align_int; |
58 | | double align_d; |
59 | | long long align_ll; |
60 | | char align_min[(sizeof(struct ta_header) + MIN_ALIGN - 1) & ~(MIN_ALIGN - 1)]; |
61 | | }; |
62 | | |
63 | 15.9G | #define PTR_TO_HEADER(ptr) (&((union aligned_header *)(ptr) - 1)->ta) |
64 | 3.12G | #define PTR_FROM_HEADER(h) ((void *)((union aligned_header *)(h) + 1)) |
65 | | |
66 | 3.37G | #define MAX_ALLOC (((size_t)-1) - sizeof(union aligned_header)) |
67 | | |
68 | | static void ta_dbg_add(struct ta_header *h); |
69 | | static void ta_dbg_check_header(struct ta_header *h); |
70 | | static void ta_dbg_remove(struct ta_header *h); |
71 | | |
72 | | static struct ta_header *get_header(void *ptr) |
73 | 22.9G | { |
74 | 22.9G | struct ta_header *h = ptr ? PTR_TO_HEADER(ptr) : NULL; |
75 | 22.9G | ta_dbg_check_header(h); |
76 | 22.9G | return h; |
77 | 22.9G | } |
78 | | |
79 | | /* Set the parent allocation of ptr. If parent==NULL, remove the parent. |
80 | | * Setting parent==NULL (with ptr!=NULL) unsets the parent of ptr. |
81 | | * With ptr==NULL, the function does nothing. |
82 | | * |
83 | | * Warning: if ta_parent is a direct or indirect child of ptr, things will go |
84 | | * wrong. The function will apparently succeed, but creates circular |
85 | | * parent links, which are not allowed. |
86 | | */ |
87 | | void ta_set_parent(void *ptr, void *ta_parent) |
88 | 5.18G | { |
89 | 5.18G | struct ta_header *ch = get_header(ptr); |
90 | 5.18G | if (!ch) |
91 | 1.57M | return; |
92 | 5.18G | struct ta_header *new_parent = get_header(ta_parent); |
93 | | // Unlink from previous parent |
94 | 5.18G | if (ch->prev) |
95 | 3.11M | ch->prev->next = ch->next; |
96 | 5.18G | if (ch->next) |
97 | 757M | ch->next->prev = ch->prev; |
98 | | // If ch was the first child, change child link of old parent |
99 | 5.18G | if (ch->parent) { |
100 | 1.08G | assert(ch->parent->child == ch); |
101 | 1.08G | ch->parent->child = ch->next; |
102 | 1.08G | if (ch->parent->child) { |
103 | 754M | assert(!ch->parent->child->parent); |
104 | 754M | ch->parent->child->parent = ch->parent; |
105 | 754M | } |
106 | 1.08G | } |
107 | 5.18G | ch->next = ch->prev = ch->parent = NULL; |
108 | | // Link to new parent - insert at start of list (LIFO destructor order) |
109 | 5.18G | if (new_parent) { |
110 | 1.08G | ch->next = new_parent->child; |
111 | 1.08G | if (ch->next) { |
112 | 757M | ch->next->prev = ch; |
113 | 757M | ch->next->parent = NULL; |
114 | 757M | } |
115 | 1.08G | new_parent->child = ch; |
116 | 1.08G | ch->parent = new_parent; |
117 | 1.08G | } |
118 | 5.18G | } |
119 | | |
120 | | /* Return the parent allocation, or NULL if none or if ptr==NULL. |
121 | | * |
122 | | * Warning: do not use this for program logic, or I'll be sad. |
123 | | */ |
124 | | void *ta_get_parent(void *ptr) |
125 | 0 | { |
126 | 0 | struct ta_header *ch = get_header(ptr); |
127 | 0 | return ch ? ch->parent : NULL; |
128 | 0 | } |
129 | | |
130 | | /* Allocate size bytes of memory. If ta_parent is not NULL, this is used as |
131 | | * parent allocation (if ta_parent is freed, this allocation is automatically |
132 | | * freed as well). size==0 allocates a block of size 0 (i.e. returns non-NULL). |
133 | | * Returns NULL on OOM. |
134 | | */ |
135 | | void *ta_alloc_size(void *ta_parent, size_t size) |
136 | 1.71G | { |
137 | 1.71G | if (size >= MAX_ALLOC) |
138 | 0 | return NULL; |
139 | 1.71G | struct ta_header *h = malloc(sizeof(union aligned_header) + size); |
140 | 1.71G | if (!h) |
141 | 0 | return NULL; |
142 | 1.71G | *h = (struct ta_header) {.size = size}; |
143 | 1.71G | ta_dbg_add(h); |
144 | 1.71G | void *ptr = PTR_FROM_HEADER(h); |
145 | 1.71G | ta_set_parent(ptr, ta_parent); |
146 | 1.71G | return ptr; |
147 | 1.71G | } |
148 | | |
149 | | /* Exactly the same as ta_alloc_size(), but the returned memory block is |
150 | | * initialized to 0. |
151 | | */ |
152 | | void *ta_zalloc_size(void *ta_parent, size_t size) |
153 | 259M | { |
154 | 259M | if (size >= MAX_ALLOC) |
155 | 0 | return NULL; |
156 | 259M | struct ta_header *h = calloc(1, sizeof(union aligned_header) + size); |
157 | 259M | if (!h) |
158 | 0 | return NULL; |
159 | 259M | *h = (struct ta_header) {.size = size}; |
160 | 259M | ta_dbg_add(h); |
161 | 259M | void *ptr = PTR_FROM_HEADER(h); |
162 | 259M | ta_set_parent(ptr, ta_parent); |
163 | 259M | return ptr; |
164 | 259M | } |
165 | | |
166 | | /* Reallocate the allocation given by ptr and return a new pointer. Much like |
167 | | * realloc(), the returned pointer can be different, and on OOM, NULL is |
168 | | * returned. |
169 | | * |
170 | | * size==0 is equivalent to ta_free(ptr). |
171 | | * ptr==NULL is equivalent to ta_alloc_size(ta_parent, size). |
172 | | * |
173 | | * ta_parent is used only in the ptr==NULL case. |
174 | | * |
175 | | * Returns NULL if the operation failed. |
176 | | * NULL is also returned if size==0. |
177 | | */ |
178 | | void *ta_realloc_size(void *ta_parent, void *ptr, size_t size) |
179 | 1.39G | { |
180 | 1.39G | if (size >= MAX_ALLOC) |
181 | 0 | return NULL; |
182 | 1.39G | if (!size) { |
183 | 490k | ta_free(ptr); |
184 | 490k | return NULL; |
185 | 490k | } |
186 | 1.39G | if (!ptr) |
187 | 1.32G | return ta_alloc_size(ta_parent, size); |
188 | 75.8M | struct ta_header *h = get_header(ptr); |
189 | 75.8M | struct ta_header *old_h = h; |
190 | 75.8M | if (h->size == size) |
191 | 33.1k | return ptr; |
192 | 75.8M | ta_dbg_remove(h); |
193 | 75.8M | h = realloc(h, sizeof(union aligned_header) + size); |
194 | 18.4E | ta_dbg_add(h ? h : old_h); |
195 | 75.8M | if (!h) |
196 | 0 | return NULL; |
197 | 75.8M | h->size = size; |
198 | 75.8M | if (h != old_h) { |
199 | | // Relink parent |
200 | 41.3M | if (h->parent) |
201 | 10.2M | h->parent->child = h; |
202 | | // Relink siblings |
203 | 41.3M | if (h->next) |
204 | 15.0M | h->next->prev = h; |
205 | 41.3M | if (h->prev) |
206 | 17.0M | h->prev->next = h; |
207 | | // Relink children |
208 | 41.3M | if (h->child) |
209 | 1.59M | h->child->parent = h; |
210 | 41.3M | } |
211 | 75.8M | return PTR_FROM_HEADER(h); |
212 | 75.8M | } |
213 | | |
214 | | /* Return the allocated size of ptr. This returns the size parameter of the |
215 | | * most recent ta_alloc.../ta_realloc... call. |
216 | | * If ptr==NULL, return 0. |
217 | | */ |
218 | | size_t ta_get_size(void *ptr) |
219 | 5.78G | { |
220 | 5.78G | struct ta_header *h = get_header(ptr); |
221 | 5.78G | return h ? h->size : 0; |
222 | 5.78G | } |
223 | | |
224 | | /* Free all allocations that (recursively) have ptr as parent allocation, but |
225 | | * do not free ptr itself. |
226 | | */ |
227 | | void ta_free_children(void *ptr) |
228 | 2.07G | { |
229 | 2.07G | struct ta_header *h = get_header(ptr); |
230 | 3.15G | while (h && h->child) |
231 | 1.07G | ta_free(PTR_FROM_HEADER(h->child)); |
232 | 2.07G | } |
233 | | |
234 | | /* Free the given allocation, and all of its direct and indirect children. |
235 | | */ |
236 | | void ta_free(void *ptr) |
237 | 2.32G | { |
238 | 2.32G | struct ta_header *h = get_header(ptr); |
239 | 2.32G | if (!h) |
240 | 348M | return; |
241 | 1.97G | if (h->destructor) |
242 | 254M | h->destructor(ptr); |
243 | 1.97G | ta_free_children(ptr); |
244 | 1.97G | ta_set_parent(ptr, NULL); |
245 | 1.97G | ta_dbg_remove(h); |
246 | 1.97G | free(h); |
247 | 1.97G | } |
248 | | |
249 | | /* Set a destructor that is to be called when the given allocation is freed. |
250 | | * (Whether the allocation is directly freed with ta_free() or indirectly by |
251 | | * freeing its parent does not matter.) There is only one destructor. If an |
252 | | * destructor was already set, it's overwritten. |
253 | | * |
254 | | * The destructor will be called with ptr as argument. The destructor can do |
255 | | * almost anything, but it must not attempt to free or realloc ptr. The |
256 | | * destructor is run before the allocation's children are freed (also, before |
257 | | * their destructors are run). |
258 | | */ |
259 | | void ta_set_destructor(void *ptr, void (*destructor)(void *)) |
260 | 332M | { |
261 | 332M | struct ta_header *h = get_header(ptr); |
262 | 332M | if (h) |
263 | 332M | h->destructor = destructor; |
264 | 332M | } |
265 | | |
266 | | #if TA_MEMORY_DEBUGGING |
267 | | |
268 | | #include "osdep/threads.h" |
269 | | |
270 | | static mp_static_mutex ta_dbg_mutex = MP_STATIC_MUTEX_INITIALIZER; |
271 | | static bool enable_leak_check; // pretty much constant |
272 | | static struct ta_header leak_node; |
273 | | static char allocation_is_string; |
274 | | |
275 | | static void ta_dbg_add(struct ta_header *h) |
276 | 2.04G | { |
277 | 2.04G | h->canary = CANARY; |
278 | 2.04G | if (enable_leak_check) { |
279 | 0 | mp_mutex_lock(&ta_dbg_mutex); |
280 | 0 | h->leak_next = &leak_node; |
281 | 0 | h->leak_prev = leak_node.leak_prev; |
282 | 0 | leak_node.leak_prev->leak_next = h; |
283 | 0 | leak_node.leak_prev = h; |
284 | 0 | mp_mutex_unlock(&ta_dbg_mutex); |
285 | 0 | } |
286 | 2.04G | } |
287 | | |
288 | | static void ta_dbg_check_header(struct ta_header *h) |
289 | 24.9G | { |
290 | 24.9G | if (h) { |
291 | 17.9G | assert(h->canary == CANARY); |
292 | 17.9G | if (h->parent) { |
293 | 5.65G | assert(!h->prev); |
294 | 5.65G | assert(h->parent->child == h); |
295 | 5.65G | } |
296 | 17.9G | } |
297 | 24.9G | } |
298 | | |
299 | | static void ta_dbg_remove(struct ta_header *h) |
300 | 2.04G | { |
301 | 2.04G | ta_dbg_check_header(h); |
302 | 2.04G | if (h->leak_next) { // assume checking for !=NULL invariant ok without lock |
303 | 0 | mp_mutex_lock(&ta_dbg_mutex); |
304 | 0 | h->leak_next->leak_prev = h->leak_prev; |
305 | 0 | h->leak_prev->leak_next = h->leak_next; |
306 | 0 | mp_mutex_unlock(&ta_dbg_mutex); |
307 | 0 | h->leak_next = h->leak_prev = NULL; |
308 | 0 | } |
309 | 2.04G | h->canary = 0; |
310 | 2.04G | } |
311 | | |
312 | | static size_t get_children_size(struct ta_header *h) |
313 | 0 | { |
314 | 0 | size_t size = 0; |
315 | 0 | for (struct ta_header *s = h->child; s; s = s->next) |
316 | 0 | size += s->size + get_children_size(s); |
317 | 0 | return size; |
318 | 0 | } |
319 | | |
320 | | static void print_leak_report(void) |
321 | 0 | { |
322 | 0 | mp_mutex_lock(&ta_dbg_mutex); |
323 | 0 | if (leak_node.leak_next && leak_node.leak_next != &leak_node) { |
324 | 0 | size_t size = 0; |
325 | 0 | size_t num_blocks = 0; |
326 | 0 | fprintf(stderr, "Blocks not freed:\n"); |
327 | 0 | fprintf(stderr, " %-20s %10s %10s %s\n", |
328 | 0 | "Ptr", "Bytes", "C. Bytes", "Name"); |
329 | 0 | while (leak_node.leak_next != &leak_node) { |
330 | 0 | struct ta_header *cur = leak_node.leak_next; |
331 | | // Don't list those with parent; logically, only parents are listed |
332 | 0 | if (!cur->next) { |
333 | 0 | size_t c_size = get_children_size(cur); |
334 | 0 | char name[50] = {0}; |
335 | 0 | if (cur->name) |
336 | 0 | snprintf(name, sizeof(name), "%s", cur->name); |
337 | 0 | if (cur->name == &allocation_is_string) { |
338 | 0 | snprintf(name, sizeof(name), "'%.*s'", |
339 | 0 | (int)cur->size, (char *)PTR_FROM_HEADER(cur)); |
340 | 0 | } |
341 | 0 | for (int n = 0; n < sizeof(name); n++) { |
342 | 0 | if (name[n] && name[n] < 0x20) |
343 | 0 | name[n] = '.'; |
344 | 0 | } |
345 | 0 | fprintf(stderr, " %-20p %10zu %10zu %s\n", |
346 | 0 | cur, cur->size, c_size, name); |
347 | 0 | } |
348 | 0 | size += cur->size; |
349 | 0 | num_blocks += 1; |
350 | | // Unlink, and don't confuse valgrind by leaving live pointers. |
351 | 0 | cur->leak_next->leak_prev = cur->leak_prev; |
352 | 0 | cur->leak_prev->leak_next = cur->leak_next; |
353 | 0 | cur->leak_next = cur->leak_prev = NULL; |
354 | 0 | } |
355 | 0 | fprintf(stderr, "%zu bytes in %zu blocks.\n", size, num_blocks); |
356 | 0 | } |
357 | 0 | mp_mutex_unlock(&ta_dbg_mutex); |
358 | 0 | } |
359 | | |
360 | | void ta_enable_leak_report(void) |
361 | 0 | { |
362 | 0 | mp_mutex_lock(&ta_dbg_mutex); |
363 | 0 | enable_leak_check = true; |
364 | 0 | if (!leak_node.leak_prev && !leak_node.leak_next) { |
365 | 0 | leak_node.leak_prev = &leak_node; |
366 | 0 | leak_node.leak_next = &leak_node; |
367 | 0 | atexit(print_leak_report); |
368 | 0 | } |
369 | 0 | mp_mutex_unlock(&ta_dbg_mutex); |
370 | 0 | } |
371 | | |
372 | | /* Set a (static) string that will be printed if the memory allocation in ptr |
373 | | * shows up on the leak report. The string must stay valid until ptr is freed. |
374 | | * Calling it on ptr==NULL does nothing. |
375 | | * Typically used to set location info. |
376 | | * Always returns ptr (useful for chaining function calls). |
377 | | */ |
378 | | void *ta_dbg_set_loc(void *ptr, const char *loc) |
379 | 2.02G | { |
380 | 2.02G | struct ta_header *h = get_header(ptr); |
381 | 2.02G | if (h) |
382 | 2.01G | h->name = loc; |
383 | 2.02G | return ptr; |
384 | 2.02G | } |
385 | | |
386 | | /* Mark the allocation as string. The leak report will print it literally. |
387 | | */ |
388 | | void *ta_dbg_mark_as_string(void *ptr) |
389 | 1.24G | { |
390 | | // Specially handled by leak report code. |
391 | 1.24G | return ta_dbg_set_loc(ptr, &allocation_is_string); |
392 | 1.24G | } |
393 | | |
394 | | #else |
395 | | |
396 | | static void ta_dbg_add(struct ta_header *h){} |
397 | | static void ta_dbg_check_header(struct ta_header *h){} |
398 | | static void ta_dbg_remove(struct ta_header *h){} |
399 | | |
400 | | void ta_enable_leak_report(void){} |
401 | | void *ta_dbg_set_loc(void *ptr, const char *loc){return ptr;} |
402 | | void *ta_dbg_mark_as_string(void *ptr){return ptr;} |
403 | | |
404 | | #endif |