FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavutil/refstruct.c
Date: 2026-09-27 22:20:00
Exec Total Coverage
Lines: 159 172 92.4%
Functions: 20 20 100.0%
Branches: 58 72 80.6%

Line Branch Exec 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 #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 #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 23977634 static RefCount *get_refcount(void *obj)
72 {
73 23977634 RefCount *ref = (RefCount*)((char*)obj - REFCOUNT_OFFSET);
74 ff_assert(ref->cookie == REFSTRUCT_COOKIE);
75 23977634 return ref;
76 }
77
78 306 static const RefCount *cget_refcount(const void *obj)
79 {
80 306 const RefCount *ref = (const RefCount*)((const char*)obj - REFCOUNT_OFFSET);
81 ff_assert(ref->cookie == REFSTRUCT_COOKIE);
82 306 return ref;
83 }
84
85 13090148 static void *get_userdata(void *buf)
86 {
87 13090148 return (char*)buf + REFCOUNT_OFFSET;
88 }
89
90 4153973 static void refcount_init(RefCount *ref, AVRefStructOpaque opaque,
91 void (*free_cb)(AVRefStructOpaque opaque, void *obj))
92 {
93 4153973 atomic_init(&ref->refcount, 1);
94 4153973 ref->opaque = opaque;
95 4153973 ref->free_cb = free_cb;
96 4153973 ref->free = av_free;
97
98 #if REFSTRUCT_CHECKED
99 ref->cookie = REFSTRUCT_COOKIE;
100 #endif
101 4153973 }
102
103 4153973 void *av_refstruct_alloc_ext_c(size_t size, unsigned flags, AVRefStructOpaque opaque,
104 void (*free_cb)(AVRefStructOpaque opaque, void *obj))
105 {
106 void *buf, *obj;
107
108
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4153973 times.
4153973 if (size > SIZE_MAX - REFCOUNT_OFFSET)
109 ✗ return NULL;
110 4153973 buf = av_malloc(size + REFCOUNT_OFFSET);
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4153973 times.
4153973 if (!buf)
112 ✗ return NULL;
113 4153973 refcount_init(buf, opaque, free_cb);
114 4153973 obj = get_userdata(buf);
115
2/2
✓ Branch 0 taken 4149771 times.
✓ Branch 1 taken 4202 times.
4153973 if (!(flags & AV_REFSTRUCT_FLAG_NO_ZEROING))
116 4149771 memset(obj, 0, size);
117
118 4153973 return obj;
119 }
120
121 32760263 void av_refstruct_unref(void *objp)
122 {
123 void *obj;
124 RefCount *ref;
125
126 32760263 memcpy(&obj, objp, sizeof(obj));
127
2/2
✓ Branch 0 taken 16647077 times.
✓ Branch 1 taken 16113186 times.
32760263 if (!obj)
128 16647077 return;
129 16113186 memcpy(objp, &(void *){ NULL }, sizeof(obj));
130
131 16113186 ref = get_refcount(obj);
132
2/2
✓ Branch 0 taken 8999203 times.
✓ Branch 1 taken 7113983 times.
16113186 if (atomic_fetch_sub_explicit(&ref->refcount, 1, memory_order_acq_rel) == 1) {
133
2/2
✓ Branch 0 taken 3380505 times.
✓ Branch 1 taken 5618698 times.
8999203 if (ref->free_cb)
134 3380505 ref->free_cb(ref->opaque, obj);
135 8999203 ref->free(ref);
136 }
137
138 16113186 return;
139 }
140
141 5655727 void *av_refstruct_ref(void *obj)
142 {
143 5655727 RefCount *ref = get_refcount(obj);
144
145 5655727 atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
146
147 5655727 return obj;
148 }
149
150 1458256 const void *av_refstruct_ref_c(const void *obj)
151 {
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 1458256 RefCount *ref = get_refcount((void*)obj);
155
156 1458256 atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
157
158 1458256 return obj;
159 }
160
161 1946753 void av_refstruct_replace(void *dstp, const void *src)
162 {
163 const void *dst;
164 1946753 memcpy(&dst, dstp, sizeof(dst));
165
166
2/2
✓ Branch 0 taken 1517019 times.
✓ Branch 1 taken 429734 times.
1946753 if (src == dst)
167 1517019 return;
168 429734 av_refstruct_unref(dstp);
169
2/2
✓ Branch 0 taken 429729 times.
✓ Branch 1 taken 5 times.
429734 if (src) {
170 429729 dst = av_refstruct_ref_c(src);
171 429729 memcpy(dstp, &dst, sizeof(dst));
172 }
173 }
174
175 306 int av_refstruct_exclusive(const void *obj)
176 {
177 306 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 306 return atomic_load_explicit((atomic_uintptr_t*)&ref->refcount, memory_order_acquire) == 1;
182 }
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 48458 static void pool_free(AVRefStructPool *pool)
210 {
211 48458 ff_mutex_destroy(&pool->mutex);
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48458 times.
48458 if (pool->free_cb)
213 ✗ pool->free_cb(pool->opaque);
214 48458 av_free(get_refcount(pool));
215 48458 }
216
217 653549 static void pool_free_entry(AVRefStructPool *pool, RefCount *ref)
218 {
219
2/2
✓ Branch 0 taken 595369 times.
✓ Branch 1 taken 58180 times.
653549 if (!pool->free_entry_cb)
220 595369 FF_ASAN_UNPOISON(get_userdata(ref), pool->size);
221
2/2
✓ Branch 0 taken 58180 times.
✓ Branch 1 taken 595369 times.
653549 if (pool->free_entry_cb)
222 58180 pool->free_entry_cb(pool->opaque, get_userdata(ref));
223 653549 av_free(ref);
224 653549 }
225
226 5498779 static void pool_return_entry(void *ref_)
227 {
228 5498779 RefCount *ref = ref_;
229 5498779 AVRefStructPool *pool = ref->opaque.nc;
230
231 5498779 ff_mutex_lock(&pool->mutex);
232
2/2
✓ Branch 0 taken 5477480 times.
✓ Branch 1 taken 21299 times.
5498779 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
2/2
✓ Branch 0 taken 3388938 times.
✓ Branch 1 taken 2088542 times.
5477480 if (!pool->free_entry_cb)
237 3388938 FF_ASAN_POISON(get_userdata(ref), pool->size);
238 5477480 ref->opaque.nc = pool->available_entries;
239 5477480 pool->available_entries = ref;
240 5477480 ref = NULL;
241 }
242 5498779 ff_mutex_unlock(&pool->mutex);
243
244
2/2
✓ Branch 0 taken 21299 times.
✓ Branch 1 taken 5477480 times.
5498779 if (ref)
245 21299 pool_free_entry(pool, ref);
246
247
2/2
✓ Branch 0 taken 5533 times.
✓ Branch 1 taken 5493246 times.
5498779 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
248 5533 pool_free(pool);
249 5498779 }
250
251 2088894 static void pool_reset_entry(AVRefStructOpaque opaque, void *entry)
252 {
253 2088894 AVRefStructPool *pool = opaque.nc;
254
255 2088894 pool->reset_cb(pool->opaque, entry);
256 2088894 }
257
258 5498779 static int refstruct_pool_get_ext(void *datap, AVRefStructPool *pool)
259 {
260 5498779 void *ret = NULL;
261
262 5498779 memcpy(datap, &(void *){ NULL }, sizeof(void*));
263
264 5498779 ff_mutex_lock(&pool->mutex);
265 ff_assert(!pool->uninited);
266
2/2
✓ Branch 0 taken 4845230 times.
✓ Branch 1 taken 653549 times.
5498779 if (pool->available_entries) {
267 4845230 RefCount *ref = pool->available_entries;
268 4845230 ret = get_userdata(ref);
269 4845230 if (!pool->free_entry_cb)
270 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
2/2
✓ Branch 0 taken 41433 times.
✓ Branch 1 taken 4803797 times.
4845230 if ((pool->entry_flags & AV_REFSTRUCT_FLAG_NO_ZEROING) &&
276
2/2
✓ Branch 0 taken 145 times.
✓ Branch 1 taken 41288 times.
41433 !(pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) &&
277
3/6
✓ Branch 0 taken 145 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 145 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 145 times.
✗ Branch 5 not taken.
145 !pool->init_cb && !pool->reset_cb && !pool->free_entry_cb)
278 145 FF_MEM_UNDEFINED(ret, pool->size);
279 4845230 pool->available_entries = ref->opaque.nc;
280 4845230 ref->opaque.nc = pool;
281 4845230 atomic_init(&ref->refcount, 1);
282 }
283 5498779 ff_mutex_unlock(&pool->mutex);
284
285
2/2
✓ Branch 0 taken 653549 times.
✓ Branch 1 taken 4845230 times.
5498779 if (!ret) {
286 RefCount *ref;
287 653549 ret = av_refstruct_alloc_ext(pool->size, pool->entry_flags, pool,
288
2/2
✓ Branch 0 taken 58180 times.
✓ Branch 1 taken 595369 times.
653549 pool->reset_cb ? pool_reset_entry : NULL);
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 653549 times.
653549 if (!ret)
290 ✗ return AVERROR(ENOMEM);
291 653549 ref = get_refcount(ret);
292 653549 ref->free = pool_return_entry;
293
2/2
✓ Branch 0 taken 58180 times.
✓ Branch 1 taken 595369 times.
653549 if (pool->init_cb) {
294 58180 int err = pool->init_cb(pool->opaque, ret);
295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 58180 times.
58180 if (err < 0) {
296 ✗ if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR)
297 ✗ pool->reset_cb(pool->opaque, ret);
298 ✗ if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR)
299 ✗ pool->free_entry_cb(pool->opaque, ret);
300 ✗ av_free(ref);
301 ✗ return err;
302 }
303 }
304 }
305 5498779 atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed);
306
307
2/2
✓ Branch 0 taken 45253 times.
✓ Branch 1 taken 5453526 times.
5498779 if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME)
308 45253 memset(ret, 0, pool->size);
309
310 5498779 memcpy(datap, &ret, sizeof(ret));
311
312 5498779 return 0;
313 }
314
315 5498779 void *av_refstruct_pool_get(AVRefStructPool *pool)
316 {
317 void *ret;
318 5498779 refstruct_pool_get_ext(&ret, pool);
319 5498779 return ret;
320 }
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 48458 static void pool_unref(void *ref)
328 {
329 48458 AVRefStructPool *pool = get_userdata(ref);
330
2/2
✓ Branch 0 taken 42925 times.
✓ Branch 1 taken 5533 times.
48458 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
331 42925 pool_free(pool);
332 48458 }
333
334 48458 static void refstruct_pool_uninit(AVRefStructOpaque unused, void *obj)
335 {
336 48458 AVRefStructPool *pool = obj;
337 RefCount *entry;
338
339 48458 ff_mutex_lock(&pool->mutex);
340 ff_assert(!pool->uninited);
341 48458 pool->uninited = 1;
342 48458 entry = pool->available_entries;
343 48458 pool->available_entries = NULL;
344 48458 ff_mutex_unlock(&pool->mutex);
345
346
2/2
✓ Branch 0 taken 632250 times.
✓ Branch 1 taken 48458 times.
680708 while (entry) {
347 632250 void *next = entry->opaque.nc;
348 632250 pool_free_entry(pool, entry);
349 632250 entry = next;
350 }
351 48458 }
352
353 9379 AVRefStructPool *av_refstruct_pool_alloc(size_t size, unsigned flags)
354 {
355 9379 return av_refstruct_pool_alloc_ext(size, flags, NULL, NULL, NULL, NULL, NULL);
356 }
357
358 48458 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 {
365 48458 AVRefStructPool *pool = av_refstruct_alloc_ext(sizeof(*pool), 0, NULL,
366 refstruct_pool_uninit);
367 int err;
368
369
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48458 times.
48458 if (!pool)
370 ✗ return NULL;
371 48458 get_refcount(pool)->free = pool_unref;
372
373 48458 pool->size = size;
374 48458 pool->opaque = opaque;
375 48458 pool->init_cb = init_cb;
376 48458 pool->reset_cb = reset_cb;
377 48458 pool->free_entry_cb = free_entry_cb;
378 48458 pool->free_cb = free_cb;
379 #define COMMON_FLAGS AV_REFSTRUCT_POOL_FLAG_NO_ZEROING
380 48458 pool->entry_flags = flags & COMMON_FLAGS;
381 // Filter out nonsense combinations to avoid checks later.
382
2/2
✓ Branch 0 taken 9379 times.
✓ Branch 1 taken 39079 times.
48458 if (!pool->reset_cb)
383 9379 flags &= ~AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR;
384
2/2
✓ Branch 0 taken 9379 times.
✓ Branch 1 taken 39079 times.
48458 if (!pool->free_entry_cb)
385 9379 flags &= ~AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR;
386 48458 pool->pool_flags = flags;
387
388
2/2
✓ Branch 0 taken 1667 times.
✓ Branch 1 taken 46791 times.
48458 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 1667 pool->entry_flags |= AV_REFSTRUCT_FLAG_NO_ZEROING;
392 }
393
394 48458 atomic_init(&pool->refcount, 1);
395
396 48458 err = ff_mutex_init(&pool->mutex, NULL);
397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48458 times.
48458 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 ✗ av_free(get_refcount(pool));
401 ✗ return NULL;
402 }
403 48458 return pool;
404 }
405