FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavutil/refstruct.c
Date: 2026-09-28 04:46:15
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 23977757 static RefCount *get_refcount(void *obj)
72 {
73 23977757 RefCount *ref = (RefCount*)((char*)obj - REFCOUNT_OFFSET);
74 ff_assert(ref->cookie == REFSTRUCT_COOKIE);
75 23977757 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 13090284 static void *get_userdata(void *buf)
86 {
87 13090284 return (char*)buf + REFCOUNT_OFFSET;
88 }
89
90 4154034 static void refcount_init(RefCount *ref, AVRefStructOpaque opaque,
91 void (*free_cb)(AVRefStructOpaque opaque, void *obj))
92 {
93 4154034 atomic_init(&ref->refcount, 1);
94 4154034 ref->opaque = opaque;
95 4154034 ref->free_cb = free_cb;
96 4154034 ref->free = av_free;
97
98 #if REFSTRUCT_CHECKED
99 ref->cookie = REFSTRUCT_COOKIE;
100 #endif
101 4154034 }
102
103 4154034 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 4154034 times.
4154034 if (size > SIZE_MAX - REFCOUNT_OFFSET)
109 ✗ return NULL;
110 4154034 buf = av_malloc(size + REFCOUNT_OFFSET);
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4154034 times.
4154034 if (!buf)
112 ✗ return NULL;
113 4154034 refcount_init(buf, opaque, free_cb);
114 4154034 obj = get_userdata(buf);
115
2/2
✓ Branch 0 taken 4149832 times.
✓ Branch 1 taken 4202 times.
4154034 if (!(flags & AV_REFSTRUCT_FLAG_NO_ZEROING))
116 4149832 memset(obj, 0, size);
117
118 4154034 return obj;
119 }
120
121 32760578 void av_refstruct_unref(void *objp)
122 {
123 void *obj;
124 RefCount *ref;
125
126 32760578 memcpy(&obj, objp, sizeof(obj));
127
2/2
✓ Branch 0 taken 16647266 times.
✓ Branch 1 taken 16113312 times.
32760578 if (!obj)
128 16647266 return;
129 16113312 memcpy(objp, &(void *){ NULL }, sizeof(obj));
130
131 16113312 ref = get_refcount(obj);
132
2/2
✓ Branch 0 taken 8999343 times.
✓ Branch 1 taken 7113969 times.
16113312 if (atomic_fetch_sub_explicit(&ref->refcount, 1, memory_order_acq_rel) == 1) {
133
2/2
✓ Branch 0 taken 3380651 times.
✓ Branch 1 taken 5618692 times.
8999343 if (ref->free_cb)
134 3380651 ref->free_cb(ref->opaque, obj);
135 8999343 ref->free(ref);
136 }
137
138 16113312 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 1458242 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 1458242 RefCount *ref = get_refcount((void*)obj);
155
156 1458242 atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
157
158 1458242 return obj;
159 }
160
161 1946772 void av_refstruct_replace(void *dstp, const void *src)
162 {
163 const void *dst;
164 1946772 memcpy(&dst, dstp, sizeof(dst));
165
166
2/2
✓ Branch 0 taken 1517051 times.
✓ Branch 1 taken 429721 times.
1946772 if (src == dst)
167 1517051 return;
168 429721 av_refstruct_unref(dstp);
169
2/2
✓ Branch 0 taken 429716 times.
✓ Branch 1 taken 5 times.
429721 if (src) {
170 429716 dst = av_refstruct_ref_c(src);
171 429716 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 48467 static void pool_free(AVRefStructPool *pool)
210 {
211 48467 ff_mutex_destroy(&pool->mutex);
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48467 times.
48467 if (pool->free_cb)
213 ✗ pool->free_cb(pool->opaque);
214 48467 av_free(get_refcount(pool));
215 48467 }
216
217 653542 static void pool_free_entry(AVRefStructPool *pool, RefCount *ref)
218 {
219
2/2
✓ Branch 0 taken 595369 times.
✓ Branch 1 taken 58173 times.
653542 if (!pool->free_entry_cb)
220 595369 FF_ASAN_UNPOISON(get_userdata(ref), pool->size);
221
2/2
✓ Branch 0 taken 58173 times.
✓ Branch 1 taken 595369 times.
653542 if (pool->free_entry_cb)
222 58173 pool->free_entry_cb(pool->opaque, get_userdata(ref));
223 653542 av_free(ref);
224 653542 }
225
226 5498851 static void pool_return_entry(void *ref_)
227 {
228 5498851 RefCount *ref = ref_;
229 5498851 AVRefStructPool *pool = ref->opaque.nc;
230
231 5498851 ff_mutex_lock(&pool->mutex);
232
2/2
✓ Branch 0 taken 5477552 times.
✓ Branch 1 taken 21299 times.
5498851 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 3388932 times.
✓ Branch 1 taken 2088620 times.
5477552 if (!pool->free_entry_cb)
237 3388932 FF_ASAN_POISON(get_userdata(ref), pool->size);
238 5477552 ref->opaque.nc = pool->available_entries;
239 5477552 pool->available_entries = ref;
240 5477552 ref = NULL;
241 }
242 5498851 ff_mutex_unlock(&pool->mutex);
243
244
2/2
✓ Branch 0 taken 21299 times.
✓ Branch 1 taken 5477552 times.
5498851 if (ref)
245 21299 pool_free_entry(pool, ref);
246
247
2/2
✓ Branch 0 taken 5533 times.
✓ Branch 1 taken 5493318 times.
5498851 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
248 5533 pool_free(pool);
249 5498851 }
250
251 2088972 static void pool_reset_entry(AVRefStructOpaque opaque, void *entry)
252 {
253 2088972 AVRefStructPool *pool = opaque.nc;
254
255 2088972 pool->reset_cb(pool->opaque, entry);
256 2088972 }
257
258 5498851 static int refstruct_pool_get_ext(void *datap, AVRefStructPool *pool)
259 {
260 5498851 void *ret = NULL;
261
262 5498851 memcpy(datap, &(void *){ NULL }, sizeof(void*));
263
264 5498851 ff_mutex_lock(&pool->mutex);
265 ff_assert(!pool->uninited);
266
2/2
✓ Branch 0 taken 4845309 times.
✓ Branch 1 taken 653542 times.
5498851 if (pool->available_entries) {
267 4845309 RefCount *ref = pool->available_entries;
268 4845309 ret = get_userdata(ref);
269 4845309 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 4803876 times.
4845309 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 4845309 pool->available_entries = ref->opaque.nc;
280 4845309 ref->opaque.nc = pool;
281 4845309 atomic_init(&ref->refcount, 1);
282 }
283 5498851 ff_mutex_unlock(&pool->mutex);
284
285
2/2
✓ Branch 0 taken 653542 times.
✓ Branch 1 taken 4845309 times.
5498851 if (!ret) {
286 RefCount *ref;
287 653542 ret = av_refstruct_alloc_ext(pool->size, pool->entry_flags, pool,
288
2/2
✓ Branch 0 taken 58173 times.
✓ Branch 1 taken 595369 times.
653542 pool->reset_cb ? pool_reset_entry : NULL);
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 653542 times.
653542 if (!ret)
290 ✗ return AVERROR(ENOMEM);
291 653542 ref = get_refcount(ret);
292 653542 ref->free = pool_return_entry;
293
2/2
✓ Branch 0 taken 58173 times.
✓ Branch 1 taken 595369 times.
653542 if (pool->init_cb) {
294 58173 int err = pool->init_cb(pool->opaque, ret);
295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 58173 times.
58173 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 5498851 atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed);
306
307
2/2
✓ Branch 0 taken 45253 times.
✓ Branch 1 taken 5453598 times.
5498851 if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME)
308 45253 memset(ret, 0, pool->size);
309
310 5498851 memcpy(datap, &ret, sizeof(ret));
311
312 5498851 return 0;
313 }
314
315 5498851 void *av_refstruct_pool_get(AVRefStructPool *pool)
316 {
317 void *ret;
318 5498851 refstruct_pool_get_ext(&ret, pool);
319 5498851 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 48467 static void pool_unref(void *ref)
328 {
329 48467 AVRefStructPool *pool = get_userdata(ref);
330
2/2
✓ Branch 0 taken 42934 times.
✓ Branch 1 taken 5533 times.
48467 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
331 42934 pool_free(pool);
332 48467 }
333
334 48467 static void refstruct_pool_uninit(AVRefStructOpaque unused, void *obj)
335 {
336 48467 AVRefStructPool *pool = obj;
337 RefCount *entry;
338
339 48467 ff_mutex_lock(&pool->mutex);
340 ff_assert(!pool->uninited);
341 48467 pool->uninited = 1;
342 48467 entry = pool->available_entries;
343 48467 pool->available_entries = NULL;
344 48467 ff_mutex_unlock(&pool->mutex);
345
346
2/2
✓ Branch 0 taken 632243 times.
✓ Branch 1 taken 48467 times.
680710 while (entry) {
347 632243 void *next = entry->opaque.nc;
348 632243 pool_free_entry(pool, entry);
349 632243 entry = next;
350 }
351 48467 }
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 48467 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 48467 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 48467 times.
48467 if (!pool)
370 ✗ return NULL;
371 48467 get_refcount(pool)->free = pool_unref;
372
373 48467 pool->size = size;
374 48467 pool->opaque = opaque;
375 48467 pool->init_cb = init_cb;
376 48467 pool->reset_cb = reset_cb;
377 48467 pool->free_entry_cb = free_entry_cb;
378 48467 pool->free_cb = free_cb;
379 #define COMMON_FLAGS AV_REFSTRUCT_POOL_FLAG_NO_ZEROING
380 48467 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 39088 times.
48467 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 39088 times.
48467 if (!pool->free_entry_cb)
385 9379 flags &= ~AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR;
386 48467 pool->pool_flags = flags;
387
388
2/2
✓ Branch 0 taken 1667 times.
✓ Branch 1 taken 46800 times.
48467 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 48467 atomic_init(&pool->refcount, 1);
395
396 48467 err = ff_mutex_init(&pool->mutex, NULL);
397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48467 times.
48467 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 48467 return pool;
404 }
405