Coverage Report

Created: 2025-06-24 07:38

/src/mpv/ta/ta.c
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