summaryrefslogtreecommitdiffstats
path: root/ta
diff options
context:
space:
mode:
Diffstat (limited to 'ta')
-rw-r--r--ta/README39
-rw-r--r--ta/ta.c404
-rw-r--r--ta/ta.h157
-rw-r--r--ta/ta_talloc.c77
-rw-r--r--ta/ta_talloc.h157
-rw-r--r--ta/ta_utils.c315
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.
diff --git a/ta/ta.c b/ta/ta.c
new file mode 100644
index 0000000..2f68400
--- /dev/null
+++ b/ta/ta.c
@@ -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
diff --git a/ta/ta.h b/ta/ta.h
new file mode 100644
index 0000000..4baac8f
--- /dev/null
+++ b/ta/ta.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_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;
+}