diff options
Diffstat (limited to 'ta')
-rw-r--r-- | ta/README | 39 | ||||
-rw-r--r-- | ta/ta.c | 404 | ||||
-rw-r--r-- | ta/ta.h | 157 | ||||
-rw-r--r-- | ta/ta_talloc.c | 77 | ||||
-rw-r--r-- | ta/ta_talloc.h | 157 | ||||
-rw-r--r-- | ta/ta_utils.c | 315 |
6 files changed, 1149 insertions, 0 deletions
diff --git a/ta/README b/ta/README new file mode 100644 index 0000000..4e2fd16 --- /dev/null +++ b/ta/README @@ -0,0 +1,39 @@ +TA ("Tree Allocator") is a wrapper around malloc() and related functions, +adding features like automatically freeing sub-trees of memory allocations if +a parent allocation is freed. + +Generally, the idea is that every TA allocation can have a parent (indicated +by the ta_parent argument in allocation function calls). If a parent is freed, +its child allocations are automatically freed as well. It is also allowed to +free a child before the parent, or to move a child to another parent with +ta_set_parent(). + +It also provides a bunch of convenience macros and debugging facilities. + +The TA functions are documented in the implementation files (ta.c, ta_utils.c). + +TA is intended to be useable as library independent from mpv. It doesn't +depend on anything mpv specific. + +Note: +----- + +mpv doesn't use the TA API yet for two reasons: first, the TA API is not +necessarily finalized yet. Second, it should be easily possible to revert +the commit adding TA, and changing all the code would not allow this. + +Especially the naming schema for some TA functions is still somewhat +undecided. (The talloc naming is a bit verbose at times.) + +For now, mpv goes through a talloc wrapper, which maps the talloc API to TA. +New code should still use talloc as well. At one point, all talloc calls +will be replaced with TA calls, and the talloc wrapper will be removed. + +Documentation for the talloc API is here: + + https://talloc.samba.org/talloc/doc/html/group__talloc.html + +There are some minor differences with mpv's talloc bridge. mpv calls abort() +on allocation failures, and the talloc_set_destructor() signature is slightly +different. libtalloc also has a weird 256MB limit per allocation. The talloc +wrapper supports only a strict subset of libtalloc functionality used by mpv. @@ -0,0 +1,404 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define TA_NO_WRAPPERS +#include "ta.h" + +#if !defined(TA_MEMORY_DEBUGGING) + #if !defined(NDEBUG) + #define TA_MEMORY_DEBUGGING 1 + #else + #define TA_MEMORY_DEBUGGING 0 + #endif +#endif + +struct ta_header { + size_t size; // size of the user allocation + // Invariant: parent!=NULL => prev==NULL + struct ta_header *prev; // siblings list (by destructor order) + struct ta_header *next; + // Invariant: parent==NULL || parent->child==this + struct ta_header *child; // points to first child + struct ta_header *parent; // set for _first_ child only, NULL otherwise + void (*destructor)(void *); +#if TA_MEMORY_DEBUGGING + unsigned int canary; + struct ta_header *leak_next; + struct ta_header *leak_prev; + const char *name; +#endif +}; + +#define CANARY 0xD3ADB3EF + +#define MIN_ALIGN _Alignof(max_align_t) +union aligned_header { + struct ta_header ta; + // Make sure to satisfy typical alignment requirements + void *align_ptr; + int align_int; + double align_d; + long long align_ll; + char align_min[(sizeof(struct ta_header) + MIN_ALIGN - 1) & ~(MIN_ALIGN - 1)]; +}; + +#define PTR_TO_HEADER(ptr) (&((union aligned_header *)(ptr) - 1)->ta) +#define PTR_FROM_HEADER(h) ((void *)((union aligned_header *)(h) + 1)) + +#define MAX_ALLOC (((size_t)-1) - sizeof(union aligned_header)) + +static void ta_dbg_add(struct ta_header *h); +static void ta_dbg_check_header(struct ta_header *h); +static void ta_dbg_remove(struct ta_header *h); + +static struct ta_header *get_header(void *ptr) +{ + struct ta_header *h = ptr ? PTR_TO_HEADER(ptr) : NULL; + ta_dbg_check_header(h); + return h; +} + +/* Set the parent allocation of ptr. If parent==NULL, remove the parent. + * Setting parent==NULL (with ptr!=NULL) unsets the parent of ptr. + * With ptr==NULL, the function does nothing. + * + * Warning: if ta_parent is a direct or indirect child of ptr, things will go + * wrong. The function will apparently succeed, but creates circular + * parent links, which are not allowed. + */ +void ta_set_parent(void *ptr, void *ta_parent) +{ + struct ta_header *ch = get_header(ptr); + if (!ch) + return; + struct ta_header *new_parent = get_header(ta_parent); + // Unlink from previous parent + if (ch->prev) + ch->prev->next = ch->next; + if (ch->next) + ch->next->prev = ch->prev; + // If ch was the first child, change child link of old parent + if (ch->parent) { + assert(ch->parent->child == ch); + ch->parent->child = ch->next; + if (ch->parent->child) { + assert(!ch->parent->child->parent); + ch->parent->child->parent = ch->parent; + } + } + ch->next = ch->prev = ch->parent = NULL; + // Link to new parent - insert at start of list (LIFO destructor order) + if (new_parent) { + ch->next = new_parent->child; + if (ch->next) { + ch->next->prev = ch; + ch->next->parent = NULL; + } + new_parent->child = ch; + ch->parent = new_parent; + } +} + +/* Return the parent allocation, or NULL if none or if ptr==NULL. + * + * Warning: do not use this for program logic, or I'll be sad. + */ +void *ta_get_parent(void *ptr) +{ + struct ta_header *ch = get_header(ptr); + return ch ? ch->parent : NULL; +} + +/* Allocate size bytes of memory. If ta_parent is not NULL, this is used as + * parent allocation (if ta_parent is freed, this allocation is automatically + * freed as well). size==0 allocates a block of size 0 (i.e. returns non-NULL). + * Returns NULL on OOM. + */ +void *ta_alloc_size(void *ta_parent, size_t size) +{ + if (size >= MAX_ALLOC) + return NULL; + struct ta_header *h = malloc(sizeof(union aligned_header) + size); + if (!h) + return NULL; + *h = (struct ta_header) {.size = size}; + ta_dbg_add(h); + void *ptr = PTR_FROM_HEADER(h); + ta_set_parent(ptr, ta_parent); + return ptr; +} + +/* Exactly the same as ta_alloc_size(), but the returned memory block is + * initialized to 0. + */ +void *ta_zalloc_size(void *ta_parent, size_t size) +{ + if (size >= MAX_ALLOC) + return NULL; + struct ta_header *h = calloc(1, sizeof(union aligned_header) + size); + if (!h) + return NULL; + *h = (struct ta_header) {.size = size}; + ta_dbg_add(h); + void *ptr = PTR_FROM_HEADER(h); + ta_set_parent(ptr, ta_parent); + return ptr; +} + +/* Reallocate the allocation given by ptr and return a new pointer. Much like + * realloc(), the returned pointer can be different, and on OOM, NULL is + * returned. + * + * size==0 is equivalent to ta_free(ptr). + * ptr==NULL is equivalent to ta_alloc_size(ta_parent, size). + * + * ta_parent is used only in the ptr==NULL case. + * + * Returns NULL if the operation failed. + * NULL is also returned if size==0. + */ +void *ta_realloc_size(void *ta_parent, void *ptr, size_t size) +{ + if (size >= MAX_ALLOC) + return NULL; + if (!size) { + ta_free(ptr); + return NULL; + } + if (!ptr) + return ta_alloc_size(ta_parent, size); + struct ta_header *h = get_header(ptr); + struct ta_header *old_h = h; + if (h->size == size) + return ptr; + ta_dbg_remove(h); + h = realloc(h, sizeof(union aligned_header) + size); + ta_dbg_add(h ? h : old_h); + if (!h) + return NULL; + h->size = size; + if (h != old_h) { + // Relink parent + if (h->parent) + h->parent->child = h; + // Relink siblings + if (h->next) + h->next->prev = h; + if (h->prev) + h->prev->next = h; + // Relink children + if (h->child) + h->child->parent = h; + } + return PTR_FROM_HEADER(h); +} + +/* Return the allocated size of ptr. This returns the size parameter of the + * most recent ta_alloc.../ta_realloc... call. + * If ptr==NULL, return 0. + */ +size_t ta_get_size(void *ptr) +{ + struct ta_header *h = get_header(ptr); + return h ? h->size : 0; +} + +/* Free all allocations that (recursively) have ptr as parent allocation, but + * do not free ptr itself. + */ +void ta_free_children(void *ptr) +{ + struct ta_header *h = get_header(ptr); + while (h && h->child) + ta_free(PTR_FROM_HEADER(h->child)); +} + +/* Free the given allocation, and all of its direct and indirect children. + */ +void ta_free(void *ptr) +{ + struct ta_header *h = get_header(ptr); + if (!h) + return; + if (h->destructor) + h->destructor(ptr); + ta_free_children(ptr); + ta_set_parent(ptr, NULL); + ta_dbg_remove(h); + free(h); +} + +/* Set a destructor that is to be called when the given allocation is freed. + * (Whether the allocation is directly freed with ta_free() or indirectly by + * freeing its parent does not matter.) There is only one destructor. If an + * destructor was already set, it's overwritten. + * + * The destructor will be called with ptr as argument. The destructor can do + * almost anything, but it must not attempt to free or realloc ptr. The + * destructor is run before the allocation's children are freed (also, before + * their destructors are run). + */ +void ta_set_destructor(void *ptr, void (*destructor)(void *)) +{ + struct ta_header *h = get_header(ptr); + if (h) + h->destructor = destructor; +} + +#if TA_MEMORY_DEBUGGING + +#include "osdep/threads.h" + +static mp_static_mutex ta_dbg_mutex = MP_STATIC_MUTEX_INITIALIZER; +static bool enable_leak_check; // pretty much constant +static struct ta_header leak_node; +static char allocation_is_string; + +static void ta_dbg_add(struct ta_header *h) +{ + h->canary = CANARY; + if (enable_leak_check) { + mp_mutex_lock(&ta_dbg_mutex); + h->leak_next = &leak_node; + h->leak_prev = leak_node.leak_prev; + leak_node.leak_prev->leak_next = h; + leak_node.leak_prev = h; + mp_mutex_unlock(&ta_dbg_mutex); + } +} + +static void ta_dbg_check_header(struct ta_header *h) +{ + if (h) { + assert(h->canary == CANARY); + if (h->parent) { + assert(!h->prev); + assert(h->parent->child == h); + } + } +} + +static void ta_dbg_remove(struct ta_header *h) +{ + ta_dbg_check_header(h); + if (h->leak_next) { // assume checking for !=NULL invariant ok without lock + mp_mutex_lock(&ta_dbg_mutex); + h->leak_next->leak_prev = h->leak_prev; + h->leak_prev->leak_next = h->leak_next; + mp_mutex_unlock(&ta_dbg_mutex); + h->leak_next = h->leak_prev = NULL; + } + h->canary = 0; +} + +static size_t get_children_size(struct ta_header *h) +{ + size_t size = 0; + for (struct ta_header *s = h->child; s; s = s->next) + size += s->size + get_children_size(s); + return size; +} + +static void print_leak_report(void) +{ + mp_mutex_lock(&ta_dbg_mutex); + if (leak_node.leak_next && leak_node.leak_next != &leak_node) { + size_t size = 0; + size_t num_blocks = 0; + fprintf(stderr, "Blocks not freed:\n"); + fprintf(stderr, " %-20s %10s %10s %s\n", + "Ptr", "Bytes", "C. Bytes", "Name"); + while (leak_node.leak_next != &leak_node) { + struct ta_header *cur = leak_node.leak_next; + // Don't list those with parent; logically, only parents are listed + if (!cur->next) { + size_t c_size = get_children_size(cur); + char name[50] = {0}; + if (cur->name) + snprintf(name, sizeof(name), "%s", cur->name); + if (cur->name == &allocation_is_string) { + snprintf(name, sizeof(name), "'%.*s'", + (int)cur->size, (char *)PTR_FROM_HEADER(cur)); + } + for (int n = 0; n < sizeof(name); n++) { + if (name[n] && name[n] < 0x20) + name[n] = '.'; + } + fprintf(stderr, " %-20p %10zu %10zu %s\n", + cur, cur->size, c_size, name); + } + size += cur->size; + num_blocks += 1; + // Unlink, and don't confuse valgrind by leaving live pointers. + cur->leak_next->leak_prev = cur->leak_prev; + cur->leak_prev->leak_next = cur->leak_next; + cur->leak_next = cur->leak_prev = NULL; + } + fprintf(stderr, "%zu bytes in %zu blocks.\n", size, num_blocks); + } + mp_mutex_unlock(&ta_dbg_mutex); +} + +void ta_enable_leak_report(void) +{ + mp_mutex_lock(&ta_dbg_mutex); + enable_leak_check = true; + if (!leak_node.leak_prev && !leak_node.leak_next) { + leak_node.leak_prev = &leak_node; + leak_node.leak_next = &leak_node; + atexit(print_leak_report); + } + mp_mutex_unlock(&ta_dbg_mutex); +} + +/* Set a (static) string that will be printed if the memory allocation in ptr + * shows up on the leak report. The string must stay valid until ptr is freed. + * Calling it on ptr==NULL does nothing. + * Typically used to set location info. + * Always returns ptr (useful for chaining function calls). + */ +void *ta_dbg_set_loc(void *ptr, const char *loc) +{ + struct ta_header *h = get_header(ptr); + if (h) + h->name = loc; + return ptr; +} + +/* Mark the allocation as string. The leak report will print it literally. + */ +void *ta_dbg_mark_as_string(void *ptr) +{ + // Specially handled by leak report code. + return ta_dbg_set_loc(ptr, &allocation_is_string); +} + +#else + +static void ta_dbg_add(struct ta_header *h){} +static void ta_dbg_check_header(struct ta_header *h){} +static void ta_dbg_remove(struct ta_header *h){} + +void ta_enable_leak_report(void){} +void *ta_dbg_set_loc(void *ptr, const char *loc){return ptr;} +void *ta_dbg_mark_as_string(void *ptr){return ptr;} + +#endif @@ -0,0 +1,157 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef TA_H_ +#define TA_H_ + +#include <stddef.h> +#include <stdbool.h> +#include <stdarg.h> + +#ifdef __GNUC__ +#define TA_PRF(a1, a2) __attribute__ ((format(printf, a1, a2))) +#define TA_TYPEOF(t) __typeof__(t) +#else +#define TA_PRF(a1, a2) +#define TA_TYPEOF(t) void * +#endif + +// Broken crap with __USE_MINGW_ANSI_STDIO +#if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__) +#undef TA_PRF +#define TA_PRF(a1, a2) __attribute__ ((format (gnu_printf, a1, a2))) +#endif + +#define TA_STRINGIFY_(x) # x +#define TA_STRINGIFY(x) TA_STRINGIFY_(x) + +#ifdef NDEBUG +#define TA_LOC "" +#else +#define TA_LOC __FILE__ ":" TA_STRINGIFY(__LINE__) +#endif + +// Core functions +void *ta_alloc_size(void *ta_parent, size_t size); +void *ta_zalloc_size(void *ta_parent, size_t size); +void *ta_realloc_size(void *ta_parent, void *ptr, size_t size); +size_t ta_get_size(void *ptr); +void ta_free(void *ptr); +void ta_free_children(void *ptr); +void ta_set_destructor(void *ptr, void (*destructor)(void *)); +void ta_set_parent(void *ptr, void *ta_parent); +void *ta_get_parent(void *ptr); + +// Utility functions +size_t ta_calc_array_size(size_t element_size, size_t count); +size_t ta_calc_prealloc_elems(size_t nextidx); +void *ta_new_context(void *ta_parent); +void *ta_steal_(void *ta_parent, void *ptr); +void *ta_memdup(void *ta_parent, void *ptr, size_t size); +char *ta_strdup(void *ta_parent, const char *str); +bool ta_strdup_append(char **str, const char *a); +bool ta_strdup_append_buffer(char **str, const char *a); +char *ta_strndup(void *ta_parent, const char *str, size_t n); +bool ta_strndup_append(char **str, const char *a, size_t n); +bool ta_strndup_append_buffer(char **str, const char *a, size_t n); +char *ta_asprintf(void *ta_parent, const char *fmt, ...) TA_PRF(2, 3); +char *ta_vasprintf(void *ta_parent, const char *fmt, va_list ap) TA_PRF(2, 0); +bool ta_asprintf_append(char **str, const char *fmt, ...) TA_PRF(2, 3); +bool ta_vasprintf_append(char **str, const char *fmt, va_list ap) TA_PRF(2, 0); +bool ta_asprintf_append_buffer(char **str, const char *fmt, ...) TA_PRF(2, 3); +bool ta_vasprintf_append_buffer(char **str, const char *fmt, va_list ap) TA_PRF(2, 0); + +#define ta_new(ta_parent, type) (type *)ta_alloc_size(ta_parent, sizeof(type)) +#define ta_znew(ta_parent, type) (type *)ta_zalloc_size(ta_parent, sizeof(type)) + +#define ta_new_array(ta_parent, type, count) \ + (type *)ta_alloc_size(ta_parent, ta_calc_array_size(sizeof(type), count)) + +#define ta_znew_array(ta_parent, type, count) \ + (type *)ta_zalloc_size(ta_parent, ta_calc_array_size(sizeof(type), count)) + +#define ta_new_array_size(ta_parent, element_size, count) \ + ta_alloc_size(ta_parent, ta_calc_array_size(element_size, count)) + +#define ta_realloc(ta_parent, ptr, type, count) \ + (type *)ta_realloc_size(ta_parent, ptr, ta_calc_array_size(sizeof(type), count)) + +#define ta_new_ptrtype(ta_parent, ptr) \ + (TA_TYPEOF(ptr))ta_alloc_size(ta_parent, sizeof(*ptr)) + +#define ta_new_array_ptrtype(ta_parent, ptr, count) \ + (TA_TYPEOF(ptr))ta_new_array_size(ta_parent, sizeof(*(ptr)), count) + +#define ta_steal(ta_parent, ptr) (TA_TYPEOF(ptr))ta_steal_(ta_parent, ptr) + +#define ta_dup(ta_parent, ptr) \ + (TA_TYPEOF(ptr))ta_memdup(ta_parent, ptr, sizeof(*(ptr))) + +// Ugly macros that crash on OOM. +// All of these mirror real functions (with a 'x' added after the 'ta_' +// prefix), and the only difference is that they will call abort() on allocation +// failures (such as out of memory conditions), instead of returning an error +// code. +#define ta_xalloc_size(...) ta_oom_p(ta_alloc_size(__VA_ARGS__)) +#define ta_xzalloc_size(...) ta_oom_p(ta_zalloc_size(__VA_ARGS__)) +#define ta_xnew_context(...) ta_oom_p(ta_new_context(__VA_ARGS__)) +#define ta_xstrdup_append(...) ta_oom_b(ta_strdup_append(__VA_ARGS__)) +#define ta_xstrdup_append_buffer(...) ta_oom_b(ta_strdup_append_buffer(__VA_ARGS__)) +#define ta_xstrndup_append(...) ta_oom_b(ta_strndup_append(__VA_ARGS__)) +#define ta_xstrndup_append_buffer(...) ta_oom_b(ta_strndup_append_buffer(__VA_ARGS__)) +#define ta_xasprintf(...) ta_oom_s(ta_asprintf(__VA_ARGS__)) +#define ta_xvasprintf(...) ta_oom_s(ta_vasprintf(__VA_ARGS__)) +#define ta_xasprintf_append(...) ta_oom_b(ta_asprintf_append(__VA_ARGS__)) +#define ta_xvasprintf_append(...) ta_oom_b(ta_vasprintf_append(__VA_ARGS__)) +#define ta_xasprintf_append_buffer(...) ta_oom_b(ta_asprintf_append_buffer(__VA_ARGS__)) +#define ta_xvasprintf_append_buffer(...) ta_oom_b(ta_vasprintf_append_buffer(__VA_ARGS__)) +#define ta_xnew(...) ta_oom_g(ta_new(__VA_ARGS__)) +#define ta_xznew(...) ta_oom_g(ta_znew(__VA_ARGS__)) +#define ta_xnew_array(...) ta_oom_g(ta_new_array(__VA_ARGS__)) +#define ta_xznew_array(...) ta_oom_g(ta_znew_array(__VA_ARGS__)) +#define ta_xnew_array_size(...) ta_oom_p(ta_new_array_size(__VA_ARGS__)) +#define ta_xnew_ptrtype(...) ta_oom_g(ta_new_ptrtype(__VA_ARGS__)) +#define ta_xnew_array_ptrtype(...) ta_oom_g(ta_new_array_ptrtype(__VA_ARGS__)) +#define ta_xdup(...) ta_oom_g(ta_dup(__VA_ARGS__)) + +#define ta_xrealloc(ta_parent, ptr, type, count) \ + (type *)ta_xrealloc_size(ta_parent, ptr, ta_calc_array_size(sizeof(type), count)) + +// Can't be macros, because the OOM logic is slightly less trivial. +char *ta_xstrdup(void *ta_parent, const char *str); +char *ta_xstrndup(void *ta_parent, const char *str, size_t n); +void *ta_xmemdup(void *ta_parent, void *ptr, size_t size); +void *ta_xrealloc_size(void *ta_parent, void *ptr, size_t size); + +#ifndef TA_NO_WRAPPERS +#define ta_alloc_size(...) ta_dbg_set_loc(ta_alloc_size(__VA_ARGS__), TA_LOC) +#define ta_zalloc_size(...) ta_dbg_set_loc(ta_zalloc_size(__VA_ARGS__), TA_LOC) +#define ta_realloc_size(...) ta_dbg_set_loc(ta_realloc_size(__VA_ARGS__), TA_LOC) +#define ta_memdup(...) ta_dbg_set_loc(ta_memdup(__VA_ARGS__), TA_LOC) +#define ta_xmemdup(...) ta_dbg_set_loc(ta_xmemdup(__VA_ARGS__), TA_LOC) +#define ta_xrealloc_size(...) ta_dbg_set_loc(ta_xrealloc_size(__VA_ARGS__), TA_LOC) +#endif + +void ta_oom_b(bool b); +char *ta_oom_s(char *s); +void *ta_oom_p(void *p); +// Generic pointer +#define ta_oom_g(ptr) (TA_TYPEOF(ptr))ta_oom_p(ptr) + +void ta_enable_leak_report(void); +void *ta_dbg_set_loc(void *ptr, const char *name); +void *ta_dbg_mark_as_string(void *ptr); + +#endif diff --git a/ta/ta_talloc.c b/ta/ta_talloc.c new file mode 100644 index 0000000..27dca22 --- /dev/null +++ b/ta/ta_talloc.c @@ -0,0 +1,77 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#include "ta_talloc.h" + +char *ta_talloc_strdup_append(char *s, const char *a) +{ + ta_xstrdup_append(&s, a); + return s; +} + +char *ta_talloc_strdup_append_buffer(char *s, const char *a) +{ + ta_xstrdup_append_buffer(&s, a); + return s; +} + +char *ta_talloc_strndup_append(char *s, const char *a, size_t n) +{ + ta_xstrndup_append(&s, a, n); + return s; +} + +char *ta_talloc_strndup_append_buffer(char *s, const char *a, size_t n) +{ + ta_xstrndup_append_buffer(&s, a, n); + return s; +} + +char *ta_talloc_vasprintf_append(char *s, const char *fmt, va_list ap) +{ + ta_xvasprintf_append(&s, fmt, ap); + return s; +} + +char *ta_talloc_vasprintf_append_buffer(char *s, const char *fmt, va_list ap) +{ + ta_xvasprintf_append_buffer(&s, fmt, ap); + return s; +} + +char *ta_talloc_asprintf_append(char *s, const char *fmt, ...) +{ + char *res; + va_list ap; + va_start(ap, fmt); + res = talloc_vasprintf_append(s, fmt, ap); + va_end(ap); + return res; +} + +char *ta_talloc_asprintf_append_buffer(char *s, const char *fmt, ...) +{ + char *res; + va_list ap; + va_start(ap, fmt); + res = talloc_vasprintf_append_buffer(s, fmt, ap); + va_end(ap); + return res; +} diff --git a/ta/ta_talloc.h b/ta/ta_talloc.h new file mode 100644 index 0000000..cacc72e --- /dev/null +++ b/ta/ta_talloc.h @@ -0,0 +1,157 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef TA_TALLOC_H_ +#define TA_TALLOC_H_ + +#include <string.h> + +#include "ta.h" + +// Note: all talloc wrappers are wired to the "x" functions, which abort on OOM. +// libtalloc doesn't do that, but the mplayer2 internal copy of it did. + +#define talloc ta_xnew +#define talloc_zero ta_xznew + +#define talloc_array ta_xnew_array +#define talloc_zero_array ta_xznew_array + +#define talloc_array_size ta_xnew_array_size +#define talloc_realloc ta_xrealloc +#define talloc_ptrtype ta_xnew_ptrtype +#define talloc_array_ptrtype ta_xnew_array_ptrtype + +#define talloc_steal ta_steal +#define talloc_realloc_size ta_xrealloc_size +#define talloc_new ta_xnew_context +#define talloc_set_destructor ta_set_destructor +#define talloc_enable_leak_report ta_enable_leak_report +#define talloc_size ta_xalloc_size +#define talloc_zero_size ta_xzalloc_size +#define talloc_get_size ta_get_size +#define talloc_free_children ta_free_children +#define talloc_free ta_free +#define talloc_dup ta_xdup +#define talloc_memdup ta_xmemdup +#define talloc_strdup ta_xstrdup +#define talloc_strndup ta_xstrndup +#define talloc_asprintf ta_xasprintf +#define talloc_vasprintf ta_xvasprintf + +// Don't define linker-level symbols, as that would clash with real libtalloc. +#define talloc_strdup_append ta_talloc_strdup_append +#define talloc_strdup_append_buffer ta_talloc_strdup_append_buffer +#define talloc_strndup_append ta_talloc_strndup_append +#define talloc_strndup_append_buffer ta_talloc_strndup_append_buffer +#define talloc_vasprintf_append ta_talloc_vasprintf_append +#define talloc_vasprintf_append_buffer ta_talloc_vasprintf_append_buffer +#define talloc_asprintf_append ta_talloc_asprintf_append +#define talloc_asprintf_append_buffer ta_talloc_asprintf_append_buffer + +char *ta_talloc_strdup(void *t, const char *p); +char *ta_talloc_strdup_append(char *s, const char *a); +char *ta_talloc_strdup_append_buffer(char *s, const char *a); + +char *ta_talloc_strndup(void *t, const char *p, size_t n); +char *ta_talloc_strndup_append(char *s, const char *a, size_t n); +char *ta_talloc_strndup_append_buffer(char *s, const char *a, size_t n); + +char *ta_talloc_vasprintf_append(char *s, const char *fmt, va_list ap) TA_PRF(2, 0); +char *ta_talloc_vasprintf_append_buffer(char *s, const char *fmt, va_list ap) TA_PRF(2, 0); + +char *ta_talloc_asprintf_append(char *s, const char *fmt, ...) TA_PRF(2, 3); +char *ta_talloc_asprintf_append_buffer(char *s, const char *fmt, ...) TA_PRF(2, 3); + +// mpv specific stuff - should be made part of proper TA API + +#define TA_FREEP(pctx) do {talloc_free(*(pctx)); *(pctx) = NULL;} while(0) + +// Return number of allocated entries in typed array p[]. +#define MP_TALLOC_AVAIL(p) (talloc_get_size(p) / sizeof((p)[0])) + +// Resize array p so that p[count-1] is the last valid entry. ctx as ta parent. +#define MP_RESIZE_ARRAY(ctx, p, count) \ + do { \ + (p) = ta_xrealloc_size(ctx, p, \ + ta_calc_array_size(sizeof((p)[0]), count)); \ + } while (0) + +// Resize array p so that p[nextidx] is accessible. Preallocate additional +// space to make appending more efficient, never shrink. ctx as ta parent. +#define MP_TARRAY_GROW(ctx, p, nextidx) \ + do { \ + size_t nextidx_ = (nextidx); \ + if (nextidx_ >= MP_TALLOC_AVAIL(p)) \ + MP_RESIZE_ARRAY(ctx, p, ta_calc_prealloc_elems(nextidx_)); \ + } while (0) + +// Append the last argument to array p (with count idxvar), basically: +// p[idxvar++] = ...; ctx as ta parent. +#define MP_TARRAY_APPEND(ctx, p, idxvar, ...) \ + do { \ + MP_TARRAY_GROW(ctx, p, idxvar); \ + (p)[(idxvar)] = (__VA_ARGS__); \ + (idxvar)++; \ + } while (0) + +// Insert the last argument at p[at] (array p with count idxvar), basically: +// for(idxvar-1 down to at) p[n+1] = p[n]; p[at] = ...; idxvar++; +// ctx as ta parent. Required: at >= 0 && at <= idxvar. +#define MP_TARRAY_INSERT_AT(ctx, p, idxvar, at, ...)\ + do { \ + size_t at_ = (at); \ + assert(at_ <= (idxvar)); \ + MP_TARRAY_GROW(ctx, p, idxvar); \ + memmove((p) + at_ + 1, (p) + at_, \ + ((idxvar) - at_) * sizeof((p)[0])); \ + (idxvar)++; \ + (p)[at_] = (__VA_ARGS__); \ + } while (0) + +// Given an array p with count idxvar, insert c elements at p[at], so that +// p[at] to p[at+c-1] can be accessed. The elements at p[at] and following +// are shifted up by c before insertion. The new entries are uninitialized. +// ctx as ta parent. Required: at >= 0 && at <= idxvar. +#define MP_TARRAY_INSERT_N_AT(ctx, p, idxvar, at, c)\ + do { \ + size_t at_ = (at); \ + assert(at_ <= (idxvar)); \ + size_t c_ = (c); \ + MP_TARRAY_GROW(ctx, p, (idxvar) + c_); \ + memmove((p) + at_ + c_, (p) + at_, \ + ((idxvar) - at_) * sizeof((p)[0])); \ + (idxvar) += c_; \ + } while (0) + +// Remove p[at] from array p with count idxvar (inverse of MP_TARRAY_INSERT_AT()). +// Doesn't actually free any memory, or do any other talloc calls. +#define MP_TARRAY_REMOVE_AT(p, idxvar, at) \ + do { \ + size_t at_ = (at); \ + assert(at_ <= (idxvar)); \ + memmove((p) + at_, (p) + at_ + 1, \ + ((idxvar) - at_ - 1) * sizeof((p)[0])); \ + (idxvar)--; \ + } while (0) + +// Returns whether or not there was any element to pop. +#define MP_TARRAY_POP(p, idxvar, out) \ + ((idxvar) > 0 \ + ? (*(out) = (p)[--(idxvar)], true) \ + : false \ + ) + +#endif diff --git a/ta/ta_utils.c b/ta/ta_utils.c new file mode 100644 index 0000000..6246968 --- /dev/null +++ b/ta/ta_utils.c @@ -0,0 +1,315 @@ +/* Copyright (C) 2017 the mpv developers + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include "osdep/strnlen.h" + +#define TA_NO_WRAPPERS +#include "ta.h" + +// Return element_size * count. If it overflows, return (size_t)-1 (SIZE_MAX). +// I.e. this returns the equivalent of: MIN(element_size * count, SIZE_MAX). +// The idea is that every real memory allocator will reject (size_t)-1, thus +// this is a valid way to handle too large array allocation requests. +size_t ta_calc_array_size(size_t element_size, size_t count) +{ + if (count > (((size_t)-1) / element_size)) + return (size_t)-1; + return element_size * count; +} + +// This is used when an array has to be enlarged for appending new elements. +// Return a "good" size for the new array (in number of elements). This returns +// a value > nextidx, unless the calculation overflows, in which case SIZE_MAX +// is returned. +size_t ta_calc_prealloc_elems(size_t nextidx) +{ + if (nextidx >= ((size_t)-1) / 2 - 1) + return (size_t)-1; + return (nextidx + 1) * 2; +} + +/* Create an empty (size 0) TA allocation. + */ +void *ta_new_context(void *ta_parent) +{ + return ta_alloc_size(ta_parent, 0); +} + +/* Set parent of ptr to ta_parent, return the ptr. + * Note that ta_parent==NULL will simply unset the current parent of ptr. + */ +void *ta_steal_(void *ta_parent, void *ptr) +{ + ta_set_parent(ptr, ta_parent); + return ptr; +} + +/* Duplicate the memory at ptr with the given size. + */ +void *ta_memdup(void *ta_parent, void *ptr, size_t size) +{ + if (!ptr) { + assert(!size); + return NULL; + } + void *res = ta_alloc_size(ta_parent, size); + if (!res) + return NULL; + memcpy(res, ptr, size); + return res; +} + +// *str = *str[0..at] + append[0..append_len] +// (append_len being a maximum length; shorter if embedded \0s are encountered) +static bool strndup_append_at(char **str, size_t at, const char *append, + size_t append_len) +{ + assert(ta_get_size(*str) >= at); + + if (!*str && !append) + return true; // stays NULL, but not an OOM condition + + size_t real_len = append ? strnlen(append, append_len) : 0; + if (append_len > real_len) + append_len = real_len; + + if (ta_get_size(*str) < at + append_len + 1) { + char *t = ta_realloc_size(NULL, *str, at + append_len + 1); + if (!t) + return false; + *str = t; + } + + if (append_len) + memcpy(*str + at, append, append_len); + + (*str)[at + append_len] = '\0'; + + ta_dbg_mark_as_string(*str); + + return true; +} + +/* Return a copy of str. + * Returns NULL on OOM. + */ +char *ta_strdup(void *ta_parent, const char *str) +{ + return ta_strndup(ta_parent, str, str ? strlen(str) : 0); +} + +/* Return a copy of str. If the string is longer than n, copy only n characters + * (the returned allocation will be n+1 bytes and contain a terminating '\0'). + * The returned string will have the length MIN(strlen(str), n) + * If str==NULL, return NULL. Returns NULL on OOM as well. + */ +char *ta_strndup(void *ta_parent, const char *str, size_t n) +{ + if (!str) + return NULL; + char *new = NULL; + strndup_append_at(&new, 0, str, n); + ta_set_parent(new, ta_parent); + return new; +} + +/* Append a to *str. If *str is NULL, the string is newly allocated, otherwise + * ta_realloc() is used on *str as needed. + * Return success or failure (it can fail due to OOM only). + */ +bool ta_strdup_append(char **str, const char *a) +{ + return strndup_append_at(str, *str ? strlen(*str) : 0, a, (size_t)-1); +} + +/* Like ta_strdup_append(), but use ta_get_size(*str)-1 instead of strlen(*str). + * (See also: ta_asprintf_append_buffer()) + */ +bool ta_strdup_append_buffer(char **str, const char *a) +{ + size_t size = ta_get_size(*str); + if (size > 0) + size -= 1; + return strndup_append_at(str, size, a, (size_t)-1); +} + +/* Like ta_strdup_append(), but limit the length of a with n. + * (See also: ta_strndup()) + */ +bool ta_strndup_append(char **str, const char *a, size_t n) +{ + return strndup_append_at(str, *str ? strlen(*str) : 0, a, n); +} + +/* Like ta_strdup_append_buffer(), but limit the length of a with n. + * (See also: ta_strndup()) + */ +bool ta_strndup_append_buffer(char **str, const char *a, size_t n) +{ + size_t size = ta_get_size(*str); + if (size > 0) + size -= 1; + return strndup_append_at(str, size, a, n); +} + +static bool ta_vasprintf_append_at(char **str, size_t at, const char *fmt, + va_list ap) +{ + assert(ta_get_size(*str) >= at); + + int size; + va_list copy; + va_copy(copy, ap); + char c; + size = vsnprintf(&c, 1, fmt, copy); + va_end(copy); + + if (size < 0) + return false; + + if (ta_get_size(*str) < at + size + 1) { + char *t = ta_realloc_size(NULL, *str, at + size + 1); + if (!t) + return false; + *str = t; + } + vsnprintf(*str + at, size + 1, fmt, ap); + + ta_dbg_mark_as_string(*str); + + return true; +} + +/* Like snprintf(); returns the formatted string as allocation (or NULL on OOM + * or snprintf() errors). + */ +char *ta_asprintf(void *ta_parent, const char *fmt, ...) +{ + char *res; + va_list ap; + va_start(ap, fmt); + res = ta_vasprintf(ta_parent, fmt, ap); + va_end(ap); + return res; +} + +char *ta_vasprintf(void *ta_parent, const char *fmt, va_list ap) +{ + char *res = NULL; + ta_vasprintf_append_at(&res, 0, fmt, ap); + ta_set_parent(res, ta_parent); + if (!res) { + ta_free(res); + return NULL; + } + return res; +} + +/* Append the formatted string to *str (after strlen(*str)). The allocation is + * ta_realloced if needed. + * Returns false on OOM or snprintf() errors, with *str left untouched. + */ +bool ta_asprintf_append(char **str, const char *fmt, ...) +{ + bool res; + va_list ap; + va_start(ap, fmt); + res = ta_vasprintf_append(str, fmt, ap); + va_end(ap); + return res; +} + +bool ta_vasprintf_append(char **str, const char *fmt, va_list ap) +{ + return ta_vasprintf_append_at(str, *str ? strlen(*str) : 0, fmt, ap); +} + +/* Append the formatted string at the end of the allocation of *str. It + * overwrites the last byte of the allocation too (which is assumed to be the + * '\0' terminating the string). Compared to ta_asprintf_append(), this is + * useful if you know that the string ends with the allocation, so that the + * extra strlen() can be avoided for better performance. + * Returns false on OOM or snprintf() errors, with *str left untouched. + */ +bool ta_asprintf_append_buffer(char **str, const char *fmt, ...) +{ + bool res; + va_list ap; + va_start(ap, fmt); + res = ta_vasprintf_append_buffer(str, fmt, ap); + va_end(ap); + return res; +} + +bool ta_vasprintf_append_buffer(char **str, const char *fmt, va_list ap) +{ + size_t size = ta_get_size(*str); + if (size > 0) + size -= 1; + return ta_vasprintf_append_at(str, size, fmt, ap); +} + + +void *ta_oom_p(void *p) +{ + if (!p) + abort(); + return p; +} + +void ta_oom_b(bool b) +{ + if (!b) + abort(); +} + +char *ta_oom_s(char *s) +{ + if (!s) + abort(); + return s; +} + +void *ta_xmemdup(void *ta_parent, void *ptr, size_t size) +{ + void *new = ta_memdup(ta_parent, ptr, size); + ta_oom_b(new || !ptr); + return new; +} + +void *ta_xrealloc_size(void *ta_parent, void *ptr, size_t size) +{ + ptr = ta_realloc_size(ta_parent, ptr, size); + ta_oom_b(ptr || !size); + return ptr; +} + +char *ta_xstrdup(void *ta_parent, const char *str) +{ + char *res = ta_strdup(ta_parent, str); + ta_oom_b(res || !str); + return res; +} + +char *ta_xstrndup(void *ta_parent, const char *str, size_t n) +{ + char *res = ta_strndup(ta_parent, str, n); + ta_oom_b(res || !str); + return res; +} |