Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavutil/refstruct.c
Line
Count
Source
1
/*
2
 * This file is part of FFmpeg.
3
 *
4
 * FFmpeg is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * FFmpeg is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with FFmpeg; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
 */
18
19
#include <stdatomic.h>
20
#include <stdint.h>
21
#include <string.h>
22
23
#include "refstruct.h"
24
#include "sanitizer.h"
25
26
#include "avassert.h"
27
#include "error.h"
28
#include "macros.h"
29
#include "mem.h"
30
#include "mem_internal.h"
31
#include "thread.h"
32
33
#ifndef REFSTRUCT_CHECKED
34
#ifndef ASSERT_LEVEL
35
#define ASSERT_LEVEL 0
36
#endif
37
#define REFSTRUCT_CHECKED (ASSERT_LEVEL >= 1)
38
#endif
39
40
#if REFSTRUCT_CHECKED
41
#define ff_assert(cond) av_assert0(cond)
42
#else
43
0
#define ff_assert(cond) ((void)0)
44
#endif
45
46
#define REFSTRUCT_COOKIE AV_NE((uint64_t)MKBETAG('R', 'e', 'f', 'S') << 32 | MKBETAG('t', 'r', 'u', 'c'), \
47
                               MKTAG('R', 'e', 'f', 'S') | (uint64_t)MKTAG('t', 'r', 'u', 'c') << 32)
48
49
#ifndef _MSC_VER
50
0
#define REFCOUNT_OFFSET FFALIGN(sizeof(RefCount), FFMAX(ALIGN_64, _Alignof(max_align_t)))
51
#else
52
#define REFCOUNT_OFFSET FFALIGN(sizeof(RefCount), ALIGN_64)
53
#endif
54
55
typedef struct RefCount {
56
    /**
57
     * An uintptr_t is big enough to hold the address of every reference,
58
     * so no overflow can happen when incrementing the refcount as long as
59
     * the user does not throw away references.
60
     */
61
    atomic_uintptr_t  refcount;
62
    AVRefStructOpaque opaque;
63
    void (*free_cb)(AVRefStructOpaque opaque, void *obj);
64
    void (*free)(void *ref);
65
66
#if REFSTRUCT_CHECKED
67
    uint64_t cookie;
68
#endif
69
} RefCount;
70
71
static RefCount *get_refcount(void *obj)
72
0
{
73
0
    RefCount *ref = (RefCount*)((char*)obj - REFCOUNT_OFFSET);
74
0
    ff_assert(ref->cookie == REFSTRUCT_COOKIE);
75
0
    return ref;
76
0
}
77
78
static const RefCount *cget_refcount(const void *obj)
79
0
{
80
0
    const RefCount *ref = (const RefCount*)((const char*)obj - REFCOUNT_OFFSET);
81
0
    ff_assert(ref->cookie == REFSTRUCT_COOKIE);
82
0
    return ref;
83
0
}
84
85
static void *get_userdata(void *buf)
86
0
{
87
0
    return (char*)buf + REFCOUNT_OFFSET;
88
0
}
89
90
static void refcount_init(RefCount *ref, AVRefStructOpaque opaque,
91
                          void (*free_cb)(AVRefStructOpaque opaque, void *obj))
92
0
{
93
0
    atomic_init(&ref->refcount, 1);
94
0
    ref->opaque  = opaque;
95
0
    ref->free_cb = free_cb;
96
0
    ref->free    = av_free;
97
98
#if REFSTRUCT_CHECKED
99
    ref->cookie  = REFSTRUCT_COOKIE;
100
#endif
101
0
}
102
103
void *av_refstruct_alloc_ext_c(size_t size, unsigned flags, AVRefStructOpaque opaque,
104
                               void (*free_cb)(AVRefStructOpaque opaque, void *obj))
105
0
{
106
0
    void *buf, *obj;
107
108
0
    if (size > SIZE_MAX - REFCOUNT_OFFSET)
109
0
        return NULL;
110
0
    buf = av_malloc(size + REFCOUNT_OFFSET);
111
0
    if (!buf)
112
0
        return NULL;
113
0
    refcount_init(buf, opaque, free_cb);
114
0
    obj = get_userdata(buf);
115
0
    if (!(flags & AV_REFSTRUCT_FLAG_NO_ZEROING))
116
0
        memset(obj, 0, size);
117
118
0
    return obj;
119
0
}
120
121
void av_refstruct_unref(void *objp)
122
0
{
123
0
    void *obj;
124
0
    RefCount *ref;
125
126
0
    memcpy(&obj, objp, sizeof(obj));
127
0
    if (!obj)
128
0
        return;
129
0
    memcpy(objp, &(void *){ NULL }, sizeof(obj));
130
131
0
    ref = get_refcount(obj);
132
0
    if (atomic_fetch_sub_explicit(&ref->refcount, 1, memory_order_acq_rel) == 1) {
133
0
        if (ref->free_cb)
134
0
            ref->free_cb(ref->opaque, obj);
135
0
        ref->free(ref);
136
0
    }
137
138
0
    return;
139
0
}
140
141
void *av_refstruct_ref(void *obj)
142
0
{
143
0
    RefCount *ref = get_refcount(obj);
144
145
0
    atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
146
147
0
    return obj;
148
0
}
149
150
const void *av_refstruct_ref_c(const void *obj)
151
0
{
152
    /* Casting const away here is fine, as it is only supposed
153
     * to apply to the user's data and not our bookkeeping data. */
154
0
    RefCount *ref = get_refcount((void*)obj);
155
156
0
    atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
157
158
0
    return obj;
159
0
}
160
161
void av_refstruct_replace(void *dstp, const void *src)
162
0
{
163
0
    const void *dst;
164
0
    memcpy(&dst, dstp, sizeof(dst));
165
166
0
    if (src == dst)
167
0
        return;
168
0
    av_refstruct_unref(dstp);
169
0
    if (src) {
170
0
        dst = av_refstruct_ref_c(src);
171
0
        memcpy(dstp, &dst, sizeof(dst));
172
0
    }
173
0
}
174
175
int av_refstruct_exclusive(const void *obj)
176
0
{
177
0
    const RefCount *ref = cget_refcount(obj);
178
    /* Casting const away here is safe, because it is a load.
179
     * It is necessary because atomic_load_explicit() does not
180
     * accept const atomics in C11 (see also N1807). */
181
0
    return atomic_load_explicit((atomic_uintptr_t*)&ref->refcount, memory_order_acquire) == 1;
182
0
}
183
184
struct AVRefStructPool {
185
    size_t size;
186
    AVRefStructOpaque opaque;
187
    int  (*init_cb)(AVRefStructOpaque opaque, void *obj);
188
    void (*reset_cb)(AVRefStructOpaque opaque, void *obj);
189
    void (*free_entry_cb)(AVRefStructOpaque opaque, void *obj);
190
    void (*free_cb)(AVRefStructOpaque opaque);
191
192
    int uninited;
193
    unsigned entry_flags;
194
    unsigned pool_flags;
195
196
    /** The number of outstanding entries not in available_entries. */
197
    atomic_uintptr_t refcount;
198
    /**
199
     * This is a linked list of available entries;
200
     * the RefCount's opaque pointer is used as next pointer
201
     * for available entries.
202
     * While the entries are in use, the opaque is a pointer
203
     * to the corresponding AVRefStructPool.
204
     */
205
    RefCount *available_entries;
206
    AVMutex mutex;
207
};
208
209
static void pool_free(AVRefStructPool *pool)
210
0
{
211
0
    ff_mutex_destroy(&pool->mutex);
212
0
    if (pool->free_cb)
213
0
        pool->free_cb(pool->opaque);
214
0
    av_free(get_refcount(pool));
215
0
}
216
217
static void pool_free_entry(AVRefStructPool *pool, RefCount *ref)
218
0
{
219
0
    if (!pool->free_entry_cb)
220
0
        FF_ASAN_UNPOISON(get_userdata(ref), pool->size);
221
0
    if (pool->free_entry_cb)
222
0
        pool->free_entry_cb(pool->opaque, get_userdata(ref));
223
0
    av_free(ref);
224
0
}
225
226
static void pool_return_entry(void *ref_)
227
0
{
228
0
    RefCount *ref = ref_;
229
0
    AVRefStructPool *pool = ref->opaque.nc;
230
231
0
    ff_mutex_lock(&pool->mutex);
232
0
    if (!pool->uninited) {
233
        /* An entry with an entry free callback owns allocations while it
234
         * rests in the pool. LeakSanitizer does not follow pointers stored
235
         * in poisoned memory, so such entries stay addressable. */
236
0
        if (!pool->free_entry_cb)
237
0
            FF_ASAN_POISON(get_userdata(ref), pool->size);
238
0
        ref->opaque.nc = pool->available_entries;
239
0
        pool->available_entries = ref;
240
0
        ref = NULL;
241
0
    }
242
0
    ff_mutex_unlock(&pool->mutex);
243
244
0
    if (ref)
245
0
        pool_free_entry(pool, ref);
246
247
0
    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
248
0
        pool_free(pool);
249
0
}
250
251
static void pool_reset_entry(AVRefStructOpaque opaque, void *entry)
252
0
{
253
0
    AVRefStructPool *pool = opaque.nc;
254
255
0
    pool->reset_cb(pool->opaque, entry);
256
0
}
257
258
static int refstruct_pool_get_ext(void *datap, AVRefStructPool *pool)
259
0
{
260
0
    void *ret = NULL;
261
262
0
    memcpy(datap, &(void *){ NULL }, sizeof(void*));
263
264
0
    ff_mutex_lock(&pool->mutex);
265
0
    ff_assert(!pool->uninited);
266
0
    if (pool->available_entries) {
267
0
        RefCount *ref = pool->available_entries;
268
0
        ret = get_userdata(ref);
269
0
        if (!pool->free_entry_cb)
270
0
            FF_ASAN_UNPOISON(ret, pool->size);
271
        /* Entries are zeroed once at allocation and a user may rely on never
272
         * written parts staying zero, so only a pool that never zeroes and
273
         * keeps no state through callbacks gets its reused entries marked
274
         * undefined. */
275
0
        if ((pool->entry_flags & AV_REFSTRUCT_FLAG_NO_ZEROING) &&
276
0
            !(pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) &&
277
0
            !pool->init_cb && !pool->reset_cb && !pool->free_entry_cb)
278
0
            FF_MEM_UNDEFINED(ret, pool->size);
279
0
        pool->available_entries = ref->opaque.nc;
280
0
        ref->opaque.nc = pool;
281
0
        atomic_init(&ref->refcount, 1);
282
0
    }
283
0
    ff_mutex_unlock(&pool->mutex);
284
285
0
    if (!ret) {
286
0
        RefCount *ref;
287
0
        ret = av_refstruct_alloc_ext(pool->size, pool->entry_flags, pool,
288
0
                                     pool->reset_cb ? pool_reset_entry : NULL);
289
0
        if (!ret)
290
0
            return AVERROR(ENOMEM);
291
0
        ref = get_refcount(ret);
292
0
        ref->free = pool_return_entry;
293
0
        if (pool->init_cb) {
294
0
            int err = pool->init_cb(pool->opaque, ret);
295
0
            if (err < 0) {
296
0
                if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR)
297
0
                    pool->reset_cb(pool->opaque, ret);
298
0
                if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR)
299
0
                    pool->free_entry_cb(pool->opaque, ret);
300
0
                av_free(ref);
301
0
                return err;
302
0
            }
303
0
        }
304
0
    }
305
0
    atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed);
306
307
0
    if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME)
308
0
        memset(ret, 0, pool->size);
309
310
0
    memcpy(datap, &ret, sizeof(ret));
311
312
0
    return 0;
313
0
}
314
315
void *av_refstruct_pool_get(AVRefStructPool *pool)
316
0
{
317
0
    void *ret;
318
0
    refstruct_pool_get_ext(&ret, pool);
319
0
    return ret;
320
0
}
321
322
/**
323
 * Hint: The content of pool_unref() and refstruct_pool_uninit()
324
 * could currently be merged; they are only separate functions
325
 * in case we would ever introduce weak references.
326
 */
327
static void pool_unref(void *ref)
328
0
{
329
0
    AVRefStructPool *pool = get_userdata(ref);
330
0
    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
331
0
        pool_free(pool);
332
0
}
333
334
static void refstruct_pool_uninit(AVRefStructOpaque unused, void *obj)
335
0
{
336
0
    AVRefStructPool *pool = obj;
337
0
    RefCount *entry;
338
339
0
    ff_mutex_lock(&pool->mutex);
340
0
    ff_assert(!pool->uninited);
341
0
    pool->uninited = 1;
342
0
    entry = pool->available_entries;
343
0
    pool->available_entries = NULL;
344
0
    ff_mutex_unlock(&pool->mutex);
345
346
0
    while (entry) {
347
0
        void *next = entry->opaque.nc;
348
0
        pool_free_entry(pool, entry);
349
0
        entry = next;
350
0
    }
351
0
}
352
353
AVRefStructPool *av_refstruct_pool_alloc(size_t size, unsigned flags)
354
0
{
355
0
    return av_refstruct_pool_alloc_ext(size, flags, NULL, NULL, NULL, NULL, NULL);
356
0
}
357
358
AVRefStructPool *av_refstruct_pool_alloc_ext_c(size_t size, unsigned flags,
359
                                               AVRefStructOpaque opaque,
360
                                               int  (*init_cb)(AVRefStructOpaque opaque, void *obj),
361
                                               void (*reset_cb)(AVRefStructOpaque opaque, void *obj),
362
                                               void (*free_entry_cb)(AVRefStructOpaque opaque, void *obj),
363
                                               void (*free_cb)(AVRefStructOpaque opaque))
364
0
{
365
0
    AVRefStructPool *pool = av_refstruct_alloc_ext(sizeof(*pool), 0, NULL,
366
0
                                                   refstruct_pool_uninit);
367
0
    int err;
368
369
0
    if (!pool)
370
0
        return NULL;
371
0
    get_refcount(pool)->free = pool_unref;
372
373
0
    pool->size          = size;
374
0
    pool->opaque        = opaque;
375
0
    pool->init_cb       = init_cb;
376
0
    pool->reset_cb      = reset_cb;
377
0
    pool->free_entry_cb = free_entry_cb;
378
0
    pool->free_cb       = free_cb;
379
0
#define COMMON_FLAGS AV_REFSTRUCT_POOL_FLAG_NO_ZEROING
380
0
    pool->entry_flags   = flags & COMMON_FLAGS;
381
    // Filter out nonsense combinations to avoid checks later.
382
0
    if (!pool->reset_cb)
383
0
        flags &= ~AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR;
384
0
    if (!pool->free_entry_cb)
385
0
        flags &= ~AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR;
386
0
    pool->pool_flags    = flags;
387
388
0
    if (flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) {
389
        // We will zero the buffer before every use, so zeroing
390
        // upon allocating the buffer is unnecessary.
391
0
        pool->entry_flags |= AV_REFSTRUCT_FLAG_NO_ZEROING;
392
0
    }
393
394
0
    atomic_init(&pool->refcount, 1);
395
396
0
    err = ff_mutex_init(&pool->mutex, NULL);
397
0
    if (err) {
398
        // Don't call av_refstruct_uninit() on pool, as it hasn't been properly
399
        // set up and is just a POD right now.
400
0
        av_free(get_refcount(pool));
401
0
        return NULL;
402
0
    }
403
0
    return pool;
404
0
}