../../../ffmpeg/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
159 / 172
92.4%
Branches:
58 / 72
80.6%
Functions:
20 / 20
100.0%

libavutil/refstruct.c
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 33955611 static RefCount *get_refcount(void *obj)
72 {
73 33955611 RefCount *ref = (RefCount*)((char*)obj - REFCOUNT_OFFSET);
74 ff_assert(ref->cookie == REFSTRUCT_COOKIE);
75 33955611 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 16223044 static void *get_userdata(void *buf)
86 {
87 16223044 return (char*)buf + REFCOUNT_OFFSET;
88 }
89
90 7044085 static void refcount_init(RefCount *ref, AVRefStructOpaque opaque,
91 void (*free_cb)(AVRefStructOpaque opaque, void *obj))
92 {
93 7044085 atomic_init(&ref->refcount, 1);
94 7044085 ref->opaque = opaque;
95 7044085 ref->free_cb = free_cb;
96 7044085 ref->free = av_free;
97
98 #if REFSTRUCT_CHECKED
99 ref->cookie = REFSTRUCT_COOKIE;
100 #endif
101 7044085 }
102
103 7044085 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 2 → 3 not taken.
✓ Branch 2 → 4 taken 7044085 times.
7044085 if (size > SIZE_MAX - REFCOUNT_OFFSET)
109 ✗ return NULL;
110 7044085 buf = av_malloc(size + REFCOUNT_OFFSET);
111
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 7044085 times.
7044085 if (!buf)
112 ✗ return NULL;
113 7044085 refcount_init(buf, opaque, free_cb);
114 7044085 obj = get_userdata(buf);
115
2/2
✓ Branch 9 → 10 taken 7039751 times.
✓ Branch 9 → 11 taken 4334 times.
7044085 if (!(flags & AV_REFSTRUCT_FLAG_NO_ZEROING))
116 7039751 memset(obj, 0, size);
117
118 7044085 return obj;
119 }
120
121 44234423 void av_refstruct_unref(void *objp)
122 {
123 void *obj;
124 RefCount *ref;
125
126 44234423 memcpy(&obj, objp, sizeof(obj));
127
2/2
✓ Branch 2 → 3 taken 21599692 times.
✓ Branch 2 → 4 taken 22634731 times.
44234423 if (!obj)
128 21599692 return;
129 22634731 memcpy(objp, &(void *){ NULL }, sizeof(obj));
130
131 22634731 ref = get_refcount(obj);
132
2/2
✓ Branch 5 → 6 taken 12071184 times.
✓ Branch 5 → 9 taken 10563547 times.
22634731 if (atomic_fetch_sub_explicit(&ref->refcount, 1, memory_order_acq_rel) == 1) {
133
2/2
✓ Branch 6 → 7 taken 4379447 times.
✓ Branch 6 → 8 taken 7691737 times.
12071184 if (ref->free_cb)
134 4379447 ref->free_cb(ref->opaque, obj);
135 12071184 ref->free(ref);
136 }
137
138 22634731 return;
139 }
140
141 8233048 void *av_refstruct_ref(void *obj)
142 {
143 8233048 RefCount *ref = get_refcount(obj);
144
145 8233048 atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
146
147 8233048 return obj;
148 }
149
150 2330499 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 2330499 RefCount *ref = get_refcount((void*)obj);
155
156 2330499 atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed);
157
158 2330499 return obj;
159 }
160
161 2357000 void av_refstruct_replace(void *dstp, const void *src)
162 {
163 const void *dst;
164 2357000 memcpy(&dst, dstp, sizeof(dst));
165
166
2/2
✓ Branch 2 → 3 taken 1886907 times.
✓ Branch 2 → 4 taken 470093 times.
2357000 if (src == dst)
167 1886907 return;
168 470093 av_refstruct_unref(dstp);
169
2/2
✓ Branch 5 → 6 taken 470088 times.
✓ Branch 5 → 8 taken 5 times.
470093 if (src) {
170 470088 dst = av_refstruct_ref_c(src);
171 470088 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 50309 static void pool_free(AVRefStructPool *pool)
210 {
211 50309 ff_mutex_destroy(&pool->mutex);
212
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 50309 times.
50309 if (pool->free_cb)
213 ✗ pool->free_cb(pool->opaque);
214 50309 av_free(get_refcount(pool));
215 50309 }
216
217 656715 static void pool_free_entry(AVRefStructPool *pool, RefCount *ref)
218 {
219
2/2
✓ Branch 2 → 3 taken 596233 times.
✓ Branch 2 → 4 taken 60482 times.
656715 if (!pool->free_entry_cb)
220 596233 FF_ASAN_UNPOISON(get_userdata(ref), pool->size);
221
2/2
✓ Branch 4 → 5 taken 60482 times.
✓ Branch 4 → 7 taken 596233 times.
656715 if (pool->free_entry_cb)
222 60482 pool->free_entry_cb(pool->opaque, get_userdata(ref));
223 656715 av_free(ref);
224 656715 }
225
226 5683814 static void pool_return_entry(void *ref_)
227 {
228 5683814 RefCount *ref = ref_;
229 5683814 AVRefStructPool *pool = ref->opaque.nc;
230
231 5683814 ff_mutex_lock(&pool->mutex);
232
2/2
✓ Branch 3 → 4 taken 5661894 times.
✓ Branch 3 → 7 taken 21920 times.
5683814 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 4 → 5 taken 3444836 times.
✓ Branch 4 → 6 taken 2217058 times.
5661894 if (!pool->free_entry_cb)
237 3444836 FF_ASAN_POISON(get_userdata(ref), pool->size);
238 5661894 ref->opaque.nc = pool->available_entries;
239 5661894 pool->available_entries = ref;
240 5661894 ref = NULL;
241 }
242 5683814 ff_mutex_unlock(&pool->mutex);
243
244
2/2
✓ Branch 8 → 9 taken 21920 times.
✓ Branch 8 → 10 taken 5661894 times.
5683814 if (ref)
245 21920 pool_free_entry(pool, ref);
246
247
2/2
✓ Branch 10 → 11 taken 5766 times.
✓ Branch 10 → 12 taken 5678048 times.
5683814 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
248 5766 pool_free(pool);
249 5683814 }
250
251 2217414 static void pool_reset_entry(AVRefStructOpaque opaque, void *entry)
252 {
253 2217414 AVRefStructPool *pool = opaque.nc;
254
255 2217414 pool->reset_cb(pool->opaque, entry);
256 2217414 }
257
258 5683814 static int refstruct_pool_get_ext(void *datap, AVRefStructPool *pool)
259 {
260 5683814 void *ret = NULL;
261
262 5683814 memcpy(datap, &(void *){ NULL }, sizeof(void*));
263
264 5683814 ff_mutex_lock(&pool->mutex);
265 ff_assert(!pool->uninited);
266
2/2
✓ Branch 3 → 4 taken 5027099 times.
✓ Branch 3 → 13 taken 656715 times.
5683814 if (pool->available_entries) {
267 5027099 RefCount *ref = pool->available_entries;
268 5027099 ret = get_userdata(ref);
269 5027099 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 5 → 6 taken 59337 times.
✓ Branch 5 → 12 taken 4967762 times.
5027099 if ((pool->entry_flags & AV_REFSTRUCT_FLAG_NO_ZEROING) &&
276
2/2
✓ Branch 6 → 7 taken 145 times.
✓ Branch 6 → 12 taken 59192 times.
59337 !(pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) &&
277
3/6
✓ Branch 7 → 8 taken 145 times.
✗ Branch 7 → 12 not taken.
✓ Branch 8 → 9 taken 145 times.
✗ Branch 8 → 12 not taken.
✓ Branch 9 → 10 taken 145 times.
✗ Branch 9 → 12 not taken.
145 !pool->init_cb && !pool->reset_cb && !pool->free_entry_cb)
278 145 FF_MEM_UNDEFINED(ret, pool->size);
279 5027099 pool->available_entries = ref->opaque.nc;
280 5027099 ref->opaque.nc = pool;
281 5027099 atomic_init(&ref->refcount, 1);
282 }
283 5683814 ff_mutex_unlock(&pool->mutex);
284
285
2/2
✓ Branch 14 → 15 taken 656715 times.
✓ Branch 14 → 31 taken 5027099 times.
5683814 if (!ret) {
286 RefCount *ref;
287 656715 ret = av_refstruct_alloc_ext(pool->size, pool->entry_flags, pool,
288
2/2
✓ Branch 15 → 16 taken 60482 times.
✓ Branch 15 → 17 taken 596233 times.
656715 pool->reset_cb ? pool_reset_entry : NULL);
289
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 656715 times.
656715 if (!ret)
290 ✗ return AVERROR(ENOMEM);
291 656715 ref = get_refcount(ret);
292 656715 ref->free = pool_return_entry;
293
2/2
✓ Branch 22 → 23 taken 60482 times.
✓ Branch 22 → 31 taken 596233 times.
656715 if (pool->init_cb) {
294 60482 int err = pool->init_cb(pool->opaque, ret);
295
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 31 taken 60482 times.
60482 if (err < 0) {
296
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR)
297 ✗ pool->reset_cb(pool->opaque, ret);
298
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✗ 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 5683814 atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed);
306
307
2/2
✓ Branch 31 → 32 taken 63288 times.
✓ Branch 31 → 33 taken 5620526 times.
5683814 if (pool->pool_flags & AV_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME)
308 63288 memset(ret, 0, pool->size);
309
310 5683814 memcpy(datap, &ret, sizeof(ret));
311
312 5683814 return 0;
313 }
314
315 5683814 void *av_refstruct_pool_get(AVRefStructPool *pool)
316 {
317 void *ret;
318 5683814 refstruct_pool_get_ext(&ret, pool);
319 5683814 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 50309 static void pool_unref(void *ref)
328 {
329 50309 AVRefStructPool *pool = get_userdata(ref);
330
2/2
✓ Branch 3 → 4 taken 44543 times.
✓ Branch 3 → 5 taken 5766 times.
50309 if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
331 44543 pool_free(pool);
332 50309 }
333
334 50309 static void refstruct_pool_uninit(AVRefStructOpaque unused, void *obj)
335 {
336 50309 AVRefStructPool *pool = obj;
337 RefCount *entry;
338
339 50309 ff_mutex_lock(&pool->mutex);
340 ff_assert(!pool->uninited);
341 50309 pool->uninited = 1;
342 50309 entry = pool->available_entries;
343 50309 pool->available_entries = NULL;
344 50309 ff_mutex_unlock(&pool->mutex);
345
346
2/2
✓ Branch 7 → 5 taken 634795 times.
✓ Branch 7 → 8 taken 50309 times.
685104 while (entry) {
347 634795 void *next = entry->opaque.nc;
348 634795 pool_free_entry(pool, entry);
349 634795 entry = next;
350 }
351 50309 }
352
353 9777 AVRefStructPool *av_refstruct_pool_alloc(size_t size, unsigned flags)
354 {
355 9777 return av_refstruct_pool_alloc_ext(size, flags, NULL, NULL, NULL, NULL, NULL);
356 }
357
358 50309 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 50309 AVRefStructPool *pool = av_refstruct_alloc_ext(sizeof(*pool), 0, NULL,
366 refstruct_pool_uninit);
367 int err;
368
369
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 50309 times.
50309 if (!pool)
370 ✗ return NULL;
371 50309 get_refcount(pool)->free = pool_unref;
372
373 50309 pool->size = size;
374 50309 pool->opaque = opaque;
375 50309 pool->init_cb = init_cb;
376 50309 pool->reset_cb = reset_cb;
377 50309 pool->free_entry_cb = free_entry_cb;
378 50309 pool->free_cb = free_cb;
379 #define COMMON_FLAGS AV_REFSTRUCT_POOL_FLAG_NO_ZEROING
380 50309 pool->entry_flags = flags & COMMON_FLAGS;
381 // Filter out nonsense combinations to avoid checks later.
382
2/2
✓ Branch 6 → 7 taken 9777 times.
✓ Branch 6 → 8 taken 40532 times.
50309 if (!pool->reset_cb)
383 9777 flags &= ~AV_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR;
384
2/2
✓ Branch 8 → 9 taken 9777 times.
✓ Branch 8 → 10 taken 40532 times.
50309 if (!pool->free_entry_cb)
385 9777 flags &= ~AV_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR;
386 50309 pool->pool_flags = flags;
387
388
2/2
✓ Branch 10 → 11 taken 1728 times.
✓ Branch 10 → 12 taken 48581 times.
50309 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 1728 pool->entry_flags |= AV_REFSTRUCT_FLAG_NO_ZEROING;
392 }
393
394 50309 atomic_init(&pool->refcount, 1);
395
396 50309 err = ff_mutex_init(&pool->mutex, NULL);
397
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 17 taken 50309 times.
50309 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 50309 return pool;
404 }
405