Commit 0e34459a authored by Rusty Russell's avatar Rusty Russell

tal: new module.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 33527c60
../../licenses/BSD-MIT
\ No newline at end of file
#include <stdio.h>
#include <string.h>
#include "config.h"
/**
* tal - compact tree allocator routines (inspired by talloc)
*
* Tal is a hierarchical allocator; any pointer allocated by tal can
* become the parent of another allocation. When you free that parent,
* the children (and grandchildren, etc) are automatically freed.
*
* This allows you to build complex objects based on their lifetimes, eg:
*
* struct foo *X = tal(NULL, struct foo);
* X->name = tal_strdup(X, "foo");
*
* and the pointer X->name would be a "child" of the tal context "X";
* tal_free(X->name) would free X->name as expected, by tal_free(X) would
* free X and X->name.
*
* With an overhead of approximately 2.1 pointers per object (vs. talloc's
* 12 pointers), it's a little slower in freeing single objects, though
* comparable for allocation and freeing whole object trees). It does not
* support talloc's references or failing destructors.
*
* Example:
* #include <stdio.h>
* #include <stdarg.h>
* #include <err.h>
* #include <ccan/talloc/talloc.h>
*
* // A structure containing a popened command.
* struct command {
* FILE *f;
* const char *command;
* };
*
* // When struct command is freed, we also want to pclose pipe.
* static void close_cmd(struct command *cmd)
* {
* pclose(cmd->f);
* }
*
* // This function opens a writable pipe to the given command.
* static struct command *open_output_cmd(const tal_t *ctx,
* const char *fmt, ...)
* {
* va_list ap;
* struct command *cmd = tal(ctx, struct command);
*
* if (!cmd)
* return NULL;
*
* va_start(ap, fmt);
* cmd->command = tal_vasprintf(cmd, fmt, ap);
* va_end(ap);
* if (!cmd->command) {
* tal_free(cmd);
* return NULL;
* }
*
* cmd->f = popen(cmd->command, "w");
* if (!cmd->f) {
* tal_free(cmd);
* return NULL;
* }
* tal_add_destructor(cmd, close_cmd);
* return cmd;
* }
*
* int main(int argc, char *argv[])
* {
* struct command *cmd;
*
* if (argc != 2)
* errx(1, "Usage: %s <command>\n", argv[0]);
*
* cmd = open_output_cmd(NULL, "%s hello", argv[1]);
* if (!cmd)
* err(1, "Running '%s hello'", argv[1]);
* fprintf(cmd->f, "This is a test\n");
* tal_free(cmd);
* return 0;
* }
*
* License: BSD-MIT
*/
int main(int argc, char *argv[])
{
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
printf("ccan/compiler\n");
printf("ccan/hash\n");
printf("ccan/likely\n");
printf("ccan/list\n");
printf("ccan/typesafe_cb\n");
return 0;
}
return 1;
}
/* Licensed under BSD-MIT - see LICENSE file for details */
#include <ccan/tal/tal.h>
#include <ccan/compiler/compiler.h>
#include <ccan/hash/hash.h>
#include <ccan/list/list.h>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
//#define TAL_DEBUG 1
/* How large should grouips get? */
#define GROUP_NODE_AVERAGE 32
/* 32-bit type field, first byte 0 in either endianness. */
enum prop_type {
CHILDREN = 0x00c1d500,
GROUP = 0x00600d00,
DESTRUCTOR = 0x00de5700,
NAME = 0x00111100,
};
struct tal_hdr {
struct tal_hdr *next;
struct prop_hdr *prop;
};
struct prop_hdr {
enum prop_type type;
struct prop_hdr *next;
};
/* Unlike other properties, this is owned by parent, not child! */
struct group {
struct prop_hdr hdr; /* GROUP */
struct list_head list; /* Head for child->group, node for others. */
/* We point to parent's children property, as it doesn't move! */
struct children *parent_child;
struct tal_hdr *first_child;
};
struct children {
struct prop_hdr hdr; /* CHILDREN */
struct tal_hdr *parent;
/* We always have one group. Others may be added. */
struct group group;
};
struct destructor {
struct prop_hdr hdr; /* DESTRUCTOR */
void (*destroy)(void *me);
};
struct name {
struct prop_hdr hdr; /* NAME */
char name[];
};
static struct {
struct tal_hdr hdr;
struct children c;
} null_parent = { { NULL, &null_parent.c.hdr },
{ { CHILDREN, NULL },
&null_parent.hdr,
{ { GROUP, NULL },
{ { &null_parent.c.group.list.n,
&null_parent.c.group.list.n } },
&null_parent.c, NULL } }
};
static void *(*allocfn)(size_t size) = malloc;
static void *(*resizefn)(void *, size_t size) = realloc;
static void (*freefn)(void *) = free;
static void (*errorfn)(const char *msg) = (void *)abort;
static inline void COLD call_error(const char *msg)
{
errorfn(msg);
}
static bool get_destroying_bit(struct tal_hdr *next)
{
return (size_t)next & 1;
}
static void set_destroying_bit(struct tal_hdr **next)
{
*next = (void *)((size_t)next | 1);
}
static struct tal_hdr *ignore_destroying_bit(struct tal_hdr *next)
{
return (void *)((size_t)next & ~(size_t)1);
}
static struct group *next_group(struct group *group)
{
return list_entry(group->list.n.next, struct group, list.n);
}
static bool atexit_set = false;
/* This means valgrind can see leaks. */
static void unlink_null(void)
{
struct group *i, *next;
for (i = next_group(&null_parent.c.group);
i != &null_parent.c.group;
i = next) {
next = next_group(i);
freefn(i);
}
null_parent.c.group.first_child = NULL;
}
#ifndef NDEBUG
static const void *bounds_start, *bounds_end;
static void update_bounds(const void *new)
{
if (unlikely(!bounds_start))
bounds_start = bounds_end = new;
else if (new < bounds_start)
bounds_start = new;
else if (new > bounds_end)
bounds_end = new;
}
static bool in_bounds(const void *p)
{
return !p || (p >= bounds_start && p <= bounds_end);
}
#else
static void update_bounds(const void *new)
{
}
static bool in_bounds(const void *p)
{
return true;
}
#endif
static void check_bounds(const void *p)
{
if (!in_bounds(p))
call_error("Not a valid header");
}
static struct tal_hdr *to_tal_hdr(const void *ctx)
{
struct tal_hdr *t;
t = (struct tal_hdr *)((char *)ctx - sizeof(struct tal_hdr));
check_bounds(t);
check_bounds(ignore_destroying_bit(t->next));
return t;
}
static struct tal_hdr *to_tal_hdr_or_null(const void *ctx)
{
if (!ctx)
return &null_parent.hdr;
return to_tal_hdr(ctx);
}
static void *from_tal_hdr(struct tal_hdr *hdr)
{
return hdr + 1;
}
#ifdef TAL_DEBUG
static void *from_tal_hdr_or_null(struct tal_hdr *hdr)
{
if (hdr == &null_parent.hdr)
return NULL;
return from_tal_hdr(hdr);
}
static struct tal_hdr *debug_tal(struct tal_hdr *tal)
{
tal_check(from_tal_hdr_or_null(tal), "TAL_DEBUG ");
return tal;
}
#else
static struct tal_hdr *debug_tal(struct tal_hdr *tal)
{
return tal;
}
#endif
static void *allocate(size_t size)
{
void *ret;
/* Don't hand silly sizes to malloc. */
if (size >> (CHAR_BIT*sizeof(size) - 1)) {
call_error("allocation size overflow");
return NULL;
}
ret = allocfn(size);
if (!ret)
call_error("allocation failed");
else
update_bounds(ret);
return ret;
}
/* We carefully start all real properties with a zero byte. */
static bool is_literal(const struct prop_hdr *prop)
{
return ((char *)prop)[0] != 0;
}
static struct prop_hdr **find_property_ptr(const struct tal_hdr *t,
enum prop_type type)
{
struct prop_hdr **p;
for (p = (struct prop_hdr **)&t->prop; *p; p = &(*p)->next) {
if (is_literal(*p)) {
if (type == NAME)
return p;
break;
}
if ((*p)->type == type)
return p;
}
return NULL;
}
static void *find_property(const struct tal_hdr *parent, enum prop_type type)
{
struct prop_hdr **p = find_property_ptr(parent, type);
if (p)
return *p;
return NULL;
}
static void init_property(struct prop_hdr *hdr,
struct tal_hdr *parent,
enum prop_type type)
{
hdr->type = type;
hdr->next = parent->prop;
parent->prop = hdr;
}
static struct destructor *add_destructor_property(struct tal_hdr *t,
void (*destroy)(void *))
{
struct destructor *prop = allocate(sizeof(*prop));
if (prop) {
init_property(&prop->hdr, t, DESTRUCTOR);
prop->destroy = destroy;
}
return prop;
}
static struct name *add_name_property(struct tal_hdr *t, const char *name)
{
struct name *prop;
prop = allocate(sizeof(*prop) + strlen(name) + 1);
if (prop) {
init_property(&prop->hdr, t, NAME);
strcpy(prop->name, name);
}
return prop;
}
static void init_group_property(struct group *group,
struct children *parent_child,
struct tal_hdr *child)
{
init_property(&group->hdr, child, GROUP);
group->parent_child = parent_child;
group->first_child = child;
}
static struct children *add_child_property(struct tal_hdr *parent,
struct tal_hdr *child)
{
struct children *prop = allocate(sizeof(*prop));
if (prop) {
init_property(&prop->hdr, parent, CHILDREN);
prop->parent = parent;
init_group_property(&prop->group, prop, child);
list_head_init(&prop->group.list);
}
return prop;
}
static struct group *add_group_property(struct tal_hdr *child,
struct children *parent_child)
{
struct group *prop = allocate(sizeof(*prop));
if (prop)
init_group_property(prop, parent_child, child);
return prop;
}
static bool add_child(struct tal_hdr *parent, struct tal_hdr *child)
{
struct group *group;
struct children *children = find_property(parent, CHILDREN);
if (!children) {
children = add_child_property(parent, child);
if (!children)
return false;
children->group.list.n.next = children->group.list.n.prev
= &children->group.list.n;
/* Child links to itself. */
child->next = child;
return true;
}
/* Last one (may be children->group itself). */
group = next_group(&children->group);
/* Empty group can happen: null_parent, or all children freed. */
if (unlikely(!group->first_child)) {
assert(group == &children->group);
/* This hits on first child appended to null parent. */
if (unlikely(!atexit_set)) {
atexit(unlink_null);
atexit_set = true;
}
/* Link group into this child, make it the first one. */
group->hdr.next = child->prop;
child->prop = &group->hdr;
group->first_child = child;
/* Child links to itself. */
child->next = child;
return true;
}
if (unlikely(hash_pointer(child, 0) % GROUP_NODE_AVERAGE == 0)) {
struct group *newgroup;
newgroup = add_group_property(child, children);
if (likely(newgroup)) {
list_add(&children->group.list, &newgroup->list.n);
/* Child links to itself. */
child->next = child;
return true;
}
/* Fall through: on allocation failure reuse old group. */
}
/* We insert after head, otherwise we'd need to find end. */
child->next = group->first_child->next;
group->first_child->next = child;
return true;
}
static void del_tree(struct tal_hdr *t)
{
struct prop_hdr **prop, *p, *next;
/* Already being destroyed? Don't loop. */
if (unlikely(get_destroying_bit(t->next)))
return;
set_destroying_bit(&t->next);
/* Carefully call destructors, removing as we go. */
while ((prop = find_property_ptr(t, DESTRUCTOR))) {
struct destructor *d = (struct destructor *)*prop;
d->destroy(from_tal_hdr(t));
*prop = d->hdr.next;
freefn(d);
}
/* Now free children and groups. */
prop = find_property_ptr(t, CHILDREN);
if (prop) {
struct children *c = (struct children *)*prop;
struct group *group, *next;
group = &c->group;
do {
next = next_group(group);
if (group->first_child) {
struct tal_hdr *i, *nextc;
i = group->first_child;
do {
nextc = i->next;
del_tree(i);
i = nextc;
} while (i != group->first_child);
}
if (group != &c->group)
freefn(group);
group = next;
} while (group != &c->group);
}
/* Finally free our properties (groups are freed by parent). */
for (p = t->prop; p && !is_literal(p); p = next) {
next = p->next;
if (p->type != GROUP)
freefn(p);
}
freefn(t);
}
void *tal_alloc_(const tal_t *ctx, size_t size, bool clear)
{
struct tal_hdr *child, *parent = debug_tal(to_tal_hdr_or_null(ctx));
child = allocate(sizeof(struct tal_hdr) + size);
if (!child)
return NULL;
if (clear)
memset(from_tal_hdr(child), 0, size);
child->prop = NULL;
if (!add_child(parent, child)) {
freefn(child);
return NULL;
}
debug_tal(parent);
return from_tal_hdr(debug_tal(child));
}
/* Update back ptrs, etc, as required.
* May return pointer to parent. */
static struct tal_hdr *remove_node(struct tal_hdr *t)
{
struct prop_hdr **prop;
struct tal_hdr *prev;
/* Loop around to find previous node. */
for (prev = t->next; prev->next != t; prev = prev->next);
/* Unlink ourselves. */
prev->next = t->next;
/* Are we the node with the group property? */
prop = find_property_ptr(t, GROUP);
if (prop) {
struct group *group = (struct group *)*prop;
/* Are we the only one? */
if (prev == t) {
struct children *c = group->parent_child;
/* Is this the group embedded in the child property? */
if (group == &c->group) {
group->first_child = NULL;
} else {
/* Empty group, so free it. */
list_del_from(&c->group.list, &group->list.n);
*prop = group->hdr.next;
freefn(group);
}
return c->parent;
} else {
/* Move property to next node. */
group->first_child = t->next;
*prop = group->hdr.next;
group->hdr.next = t->next->prop;
t->next->prop = &group->hdr;
}
}
return NULL;
}
void tal_free(const tal_t *ctx)
{
struct tal_hdr *t;
if (!ctx)
return;
t = debug_tal(to_tal_hdr(ctx));
remove_node(t);
del_tree(t);
}
void *tal_steal_(const tal_t *new_parent, const tal_t *ctx)
{
if (ctx) {
struct tal_hdr *newpar, *t, *old_next, *old_parent;
newpar = debug_tal(to_tal_hdr_or_null(new_parent));
t = debug_tal(to_tal_hdr(ctx));
/* Save enough data to get us back if we fail! */
old_next = t->next;
/* Unlink it from old parent. */
old_parent = remove_node(t);
if (unlikely(!add_child(newpar, t))) {
/* If we were last child, parent returned by
* remove_node, otherwise search old siblings
* for it. */
if (!old_parent) {
struct group *g;
while (!(g = find_property(old_next, GROUP)))
old_next = old_next->next;
old_parent = g->parent_child->parent;
}
/* We can always add to old parent, becuase it has one
* group already. */
if (!add_child(old_parent, t))
abort();
return NULL;
}
debug_tal(newpar);
}
return (void *)ctx;
}
bool tal_add_destructor_(tal_t *ctx, void (*destroy)(void *me))
{
return add_destructor_property(debug_tal(to_tal_hdr(ctx)), destroy);
}
bool tal_set_name_(tal_t *ctx, const char *name, bool literal)
{
struct tal_hdr *t = debug_tal(to_tal_hdr(ctx));
struct prop_hdr **prop = find_property_ptr(t, NAME);
/* Get rid of any old name */
if (prop) {
struct name *name = (struct name *)*prop;
if (is_literal(&name->hdr))
*prop = NULL;
else {
*prop = name->hdr.next;
freefn(name);
}
}
if (literal && name[0]) {
struct prop_hdr **p;
/* Append literal. */
for (p = &t->prop; *p && !is_literal(*p); p = &(*p)->next);
*p = (struct prop_hdr *)name;
return true;
}
if (!add_name_property(t, name))
return false;
debug_tal(t);
return true;
}
const char *tal_name(const tal_t *t)
{
struct name *n;
n = find_property(debug_tal(to_tal_hdr(t)), NAME);
if (!n)
return NULL;
if (is_literal(&n->hdr))
return (const char *)n;
return n->name;
}
/* Start one past first child: make stopping natural in circ. list. */
static struct tal_hdr *first_child(struct tal_hdr *parent)
{
struct children *child;
struct group *group;
child = find_property(parent, CHILDREN);
if (!child)
return NULL;
/* Careful of empty group embedded in child property. */
if (child->group.first_child)
return child->group.first_child->next;
/* There could still be another group! */
group = next_group(&child->group);
if (group == &child->group)
return NULL;
return group->first_child->next;
}
tal_t *tal_first(const tal_t *root)
{
struct tal_hdr *c, *t = debug_tal(to_tal_hdr_or_null(root));
c = first_child(t);
if (!c)
return NULL;
return from_tal_hdr(c);
}
tal_t *tal_next(const tal_t *root, const tal_t *prev)
{
struct tal_hdr *c, *t = debug_tal(to_tal_hdr(prev)), *top;
struct group *group;
/* Children? */
c = first_child(t);
if (c)
return from_tal_hdr(c);
top = to_tal_hdr_or_null(root);
do {
struct group *next;
/* Are we back to first child in group? */
group = find_property(t, GROUP);
if (!group)
return from_tal_hdr(t->next);
/* Last group is one inside children property. */
next = next_group(group);
if (next != &group->parent_child->group)
return from_tal_hdr(next->first_child->next);
/* OK, go back to parent. */
t = group->parent_child->parent;
} while (t != top);
return NULL;
}
tal_t *tal_parent(const tal_t *ctx)
{
struct group *group;
struct tal_hdr *t = debug_tal(to_tal_hdr(ctx));
while (!(group = find_property(t, GROUP)))
t = t->next;
if (group->parent_child->parent == &null_parent.hdr)
return NULL;
return from_tal_hdr(group->parent_child->parent);
}
void *tal_realloc_(tal_t *ctx, size_t size)
{
struct tal_hdr *old_t, *t, **prev;
struct group *group;
struct children *child;
old_t = debug_tal(to_tal_hdr(ctx));
t = resizefn(old_t, size + sizeof(struct tal_hdr));
if (!t) {
call_error("Reallocation failure");
tal_free(old_t);
return NULL;
}
if (t == old_t)
return ctx;
update_bounds(t);
/* Fix up linked list pointer. */
for (prev = &t->next; *prev != old_t; prev = &(*prev)->next);
*prev = t;
/* Fix up group pointer, if any. */
group = find_property(t, GROUP);
if (group) {
assert(group->first_child == old_t);
group->first_child = t;
}
/* Fix up child propertie's parent pointer. */
child = find_property(t, CHILDREN);
if (child) {
assert(child->parent == old_t);
child->parent = t;
}
return from_tal_hdr(debug_tal(t));
}
char *tal_strdup(const tal_t *ctx, const char *p)
{
return tal_memdup(ctx, p, strlen(p)+1);
}
char *tal_strndup(const tal_t *ctx, const char *p, size_t n)
{
char *ret;
if (strlen(p) < n)
n = strlen(p);
ret = tal_memdup(ctx, p, n+1);
if (ret)
ret[n] = '\0';
return ret;
}
void *tal_memdup(const tal_t *ctx, const void *p, size_t n)
{
void *ret = tal_arr(ctx, char, n);
if (ret)
memcpy(ret, p, n);
return ret;
}
char *tal_asprintf(const tal_t *ctx, const char *fmt, ...)
{
va_list ap;
char *ret;
va_start(ap, fmt);
ret = tal_vasprintf(ctx, fmt, ap);
va_end(ap);
return ret;
}
char *tal_vasprintf(const tal_t *ctx, const char *fmt, va_list ap)
{
size_t max = strlen(fmt) * 2;
char *buf = tal_arr(ctx, char, max);
int ret;
while (buf) {
va_list ap2;
va_copy(ap2, ap);
ret = vsnprintf(buf, max, fmt, ap2);
va_end(ap2);
if (ret < max)
break;
buf = tal_resize(buf, max *= 2);
}
return buf;
}
void tal_set_backend(void *(*alloc_fn)(size_t size),
void *(*resize_fn)(void *, size_t size),
void (*free_fn)(void *),
void (*error_fn)(const char *msg))
{
if (alloc_fn)
allocfn = alloc_fn;
if (resize_fn)
resizefn = resize_fn;
if (free_fn)
freefn = free_fn;
if (error_fn)
errorfn = error_fn;
}
#ifdef CCAN_TAL_DEBUG
static void dump_node(unsigned int indent, const struct tal_hdr *t)
{
unsigned int i;
const struct prop_hdr *p;
for (i = 0; i < indent; i++)
printf(" ");
printf("%p", t);
for (p = t->prop; p; p = p->next) {
struct group *g;
struct children *c;
struct destructor *d;
struct name *n;
if (is_literal(p)) {
printf(" \"%s\"", (const char *)p);
break;
}
switch (p->type) {
case CHILDREN:
c = (struct children *)p;
printf(" CHILDREN(%p):parent=%p,group=%p\n",
p, c->parent, &c->group);
g = &c->group;
printf(" GROUP(%p):list={%p,%p},parent_ch=%p,first=%p",
g, g->list.n.next, g->list.n.next,
g->parent_child, g->first_child);
break;
case GROUP:
g = (struct group *)p;
printf(" GROUP(%p):list={%p,%p},,parent_ch=%p,first=%p",
p, g->list.n.next, g->list.n.next,
g->parent_child, g->first_child);
break;
case DESTRUCTOR:
d = (struct destructor *)p;
printf(" DESTRUCTOR(%p):fn=%p", p, d->destroy);
break;
case NAME:
n = (struct name *)p;
printf(" NAME(%p):%s", p, n->name);
break;
default:
printf(" **UNKNOWN(%p):%i**", p, p->type);
}
}
printf("\n");
}
static void tal_dump_(unsigned int level, const struct tal_hdr *t)
{
struct children *children;
struct group *group;
dump_node(level, t);
children = find_property(t, CHILDREN);
if (!children)
return;
group = &children->group;
do {
struct tal_hdr *i;
i = group->first_child;
if (i) {
do {
tal_dump_(level+1, i);
i = i->next;
} while (i != group->first_child);
}
group = next_group(group);
} while (group != &children->group);
}
void tal_dump(void)
{
tal_dump_(0, &null_parent.hdr);
}
#endif /* CCAN_TAL_DEBUG */
#ifndef NDEBUG
static bool check_err(struct tal_hdr *t, const char *errorstr,
const char *errmsg)
{
if (errorstr) {
/* Try not to malloc: it may be corrupted. */
char msg[strlen(errorstr) + 20 + strlen(errmsg) + 1];
sprintf(msg, "%s:%p %s", errorstr, from_tal_hdr(t), errmsg);
call_error(msg);
}
return false;
}
static bool check_group(struct group *group,
struct tal_hdr *t, const char *errorstr);
static bool check_node(struct group *group,
struct tal_hdr *t, const char *errorstr)
{
struct prop_hdr *p;
struct name *name = NULL;
struct children *children = NULL;
struct group *gr = NULL;
if (t != &null_parent.hdr && !in_bounds(t))
return check_err(t, errorstr, "invalid pointer");
for (p = t->prop; p; p = p->next) {
if (is_literal(p)) {
if (name)
return check_err(t, errorstr,
"has extra literal");
name = (struct name *)p;
break;
}
if (p != &null_parent.c.hdr && !in_bounds(p))
return check_err(t, errorstr,
"has bad property pointer");
switch (p->type) {
case GROUP:
if (gr)
return check_err(t, errorstr,
"has two groups");
gr = (struct group *)p;
break;
case CHILDREN:
if (children)
return check_err(t, errorstr,
"has two child nodes");
children = (struct children *)p;
break;
case DESTRUCTOR:
break;
case NAME:
if (name)
return check_err(t, errorstr,
"has two names");
name = (struct name *)p;
break;
default:
return check_err(t, errorstr, "has unknown property");
}
}
if (group && gr != group)
return check_err(t, errorstr, "has bad group");
if (children) {
if (!list_check(&children->group.list, errorstr))
return false;
gr = &children->group;
do {
if (gr->first_child) {
if (!check_group(gr, gr->first_child, errorstr))
return false;
} else if (gr != &children->group) {
/* Empty groups should be deleted! */
return check_err(t, errorstr,
"has empty group");
}
gr = next_group(gr);
} while (gr != &children->group);
}
return true;
}
static bool check_group(struct group *group,
struct tal_hdr *t, const char *errorstr)
{
struct tal_hdr *i;
i = t;
do {
if (!check_node(group, i, errorstr))
return false;
group = NULL;
i = i->next;
} while (i != t);
return true;
}
bool tal_check(const tal_t *ctx, const char *errorstr)
{
struct tal_hdr *t = to_tal_hdr_or_null(ctx);
return check_node(NULL, t, errorstr);
}
#else /* NDEBUG */
bool tal_check(const tal_t *ctx, const char *errorstr)
{
return true;
}
#endif
/* Licensed under BSD-MIT - see LICENSE file for details */
#ifndef CCAN_TAL_H
#define CCAN_TAL_H
#include "config.h"
#include <ccan/compiler/compiler.h>
#include <ccan/likely/likely.h>
#include <ccan/typesafe_cb/typesafe_cb.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
/**
* tal_t - convenient alias for void to mark tal pointers.
*
* Since any pointer can be a tal-allocated pointer, it's often
* useful to use this typedef to mark them explicitly.
*/
typedef void tal_t;
/**
* tal - basic allocator function
* @ctx: NULL, or tal allocated object to be parent.
* @type: the type to allocate.
*
* Allocates a specific type, with a given parent context.
*/
#define tal(ctx, type) tal_arr((ctx), type, 1)
/**
* talz - zeroing allocator function
* @ctx: NULL, or tal allocated object to be parent.
* @type: the type to allocate.
*
* Equivalent to tal() followed by memset() to zero.
*/
#define talz(ctx, type) tal_arrz((ctx), type, 1)
/**
* tal_free - free a tal-allocated pointer.
* @p: NULL, or tal allocated object to free.
*
* This calls the destructors for p (if any), then does the same for all its
* children (recursively) before finally freeing the memory.
*/
void tal_free(const tal_t *p);
/**
* tal_arr - allocate an array of objects.
* @ctx: NULL, or tal allocated object to be parent.
* @type: the type to allocate.
* @count: the number to allocate.
*/
#define tal_arr(ctx, type, count) \
((type *)tal_alloc_((ctx), tal_sizeof_(sizeof(type), (count)), false))
/**
* tal_arrz - allocate an array of zeroed objects.
* @ctx: NULL, or tal allocated object to be parent.
* @type: the type to allocate.
* @count: the number to allocate.
*/
#define tal_arrz(ctx, type, count) \
((type *)tal_alloc_((ctx), tal_sizeof_(sizeof(type), (count)), true))
/**
* tal_resize - enlarge or reduce a tal_arr(z).
* @p: The tal allocated array to resize.
* @count: the number to allocate.
*
* This returns the new pointer, or NULL (and destroys the old one)
* on failure.
*/
#define tal_resize(p, count) \
((tal_typeof(p) tal_realloc_((p), tal_sizeof_(sizeof(*p), (count)))))
/**
* tal_steal - change the parent of a tal-allocated pointer.
* @ctx: The new parent.
* @ptr: The tal allocated object to move.
*
* This may need to perform an allocation, in which case it may fail; thus
* it can return NULL, otherwise returns @ptr.
*
* Weird macro avoids gcc's 'warning: value computed is not used'.
*/
#if HAVE_STATEMENT_EXPR
#define tal_steal(ctx, ptr) \
({ (tal_typeof(ptr) tal_steal_((ctx),(ptr))); })
#else
#define tal_steal(ctx, ptr) \
(tal_typeof(ptr) tal_steal_((ctx),(ptr)))
#endif
/**
* tal_add_destructor - add a callback function when this context is destroyed.
* @ptr: The tal allocated object.
* @function: the function to call before it's freed.
*/
#define tal_add_destructor(ptr, function) \
tal_add_destructor_((ptr), typesafe_cb(void, void *, (function), (ptr)))
/**
* tal_set_name - attach a name to a tal pointer.
* @ptr: The tal allocated object.
* @name: The name to use.
*
* The name is copied, unless we're certain it's a string literal.
*/
#define tal_set_name(ptr, name) \
tal_set_name_((ptr), (name), TAL_IS_LITERAL(name))
/**
* tal_name - get the name for a tal pointer.
* @ptr: The tal allocated object.
*
* Returns NULL if no name has been set.
*/
const char *tal_name(const tal_t *ptr);
/**
* tal_first - get the first tal object child.
* @root: The tal allocated object to start with, or NULL.
*
* Returns NULL if there are no children.
*/
tal_t *tal_first(const tal_t *root);
/**
* tal_next - get the next tal object child.
* @root: The tal allocated object to start with, or NULL.
* @prev: The return value from tal_first or tal_next.
*
* Returns NULL if there are no more children. This should be safe to
* call on an altering tree unless @prev is no longer a descendent of
* @root.
*/
tal_t *tal_next(const tal_t *root, const tal_t *prev);
/**
* tal_parent - get the parent of a tal object.
* @ctx: The tal allocated object.
*
* Returns the parent, which may be NULL.
*/
tal_t *tal_parent(const tal_t *ctx);
/**
* tal_memdup - duplicate memory.
* @ctx: NULL, or tal allocated object to be parent.
* @p: the memory to copy
* @n: the number of bytes.
*/
void *tal_memdup(const tal_t *ctx, const void *p, size_t n);
/**
* tal_strdup - duplicate a string.
* @ctx: NULL, or tal allocated object to be parent.
* @p: the string to copy
*/
char *tal_strdup(const tal_t *ctx, const char *p);
/**
* tal_strndup - duplicate a limited amount of a string.
* @ctx: NULL, or tal allocated object to be parent.
* @p: the string to copy
* @n: the maximum length to copy.
*
* Always gives a nul-terminated string, with strlen() <= @n.
*/
char *tal_strndup(const tal_t *ctx, const char *p, size_t n);
/**
* tal_asprintf - allocate a formatted string
* @ctx: NULL, or tal allocated object to be parent.
* @fmt: the printf-style format.
*/
char *tal_asprintf(const tal_t *ctx, const char *fmt, ...) PRINTF_FMT(2,3);
/**
* tal_vasprintf - allocate a formatted string (va_list version)
* @ctx: NULL, or tal allocated object to be parent.
* @fmt: the printf-style format.
* @va: the va_list containing the format args.
*/
char *tal_vasprintf(const tal_t *ctx, const char *fmt, va_list ap)
PRINTF_FMT(2,0);
/**
* tal_set_backend - set the allocation or error functions to use
* @alloc_fn: allocator or NULL (default is malloc)
* @resize_fn: re-allocator or NULL (default is realloc)
* @free_fn: free function or NULL (default is free)
* @error_fn: called on errors or NULL (default is abort)
*
* The defaults are set up so tal functions never return NULL, but you
* can override erorr_fn to change that. error_fn can return, and is
* called if alloc_fn or resize_fn fail.
*
* If any parameter is NULL, that function is unchanged.
*/
void tal_set_backend(void *(*alloc_fn)(size_t size),
void *(*resize_fn)(void *, size_t size),
void (*free_fn)(void *),
void (*error_fn)(const char *msg));
/**
* tal_check - set the allocation or error functions to use
* @ctx: a tal context, or NULL.
* @errorstr: a string to prepend calls to error_fn, or NULL.
*
* This sanity-checks a tal tree (unless NDEBUG is defined, in which case
* it simply returns true). If errorstr is not null, error_fn is called
* when a problem is found, otherwise it is not.
*/
bool tal_check(const tal_t *ctx, const char *errorstr);
#ifdef CCAN_TAL_DEBUG
/**
* tal_dump - dump entire tal tree.
*
* This is a helper for debugging tal itself, which dumps all the tal internal
* state.
*/
void tal_dump(void);
#endif
/* Internal support functions */
#if HAVE_BUILTIN_CONSTANT_P
#define TAL_IS_LITERAL(str) __builtin_constant_p(str)
#else
#define TAL_IS_LITERAL(str) (sizeof(&*(str)) != sizeof(char *))
#endif
bool tal_set_name_(tal_t *ctx, const char *name, bool literal);
static inline size_t tal_sizeof_(size_t size, size_t count)
{
/* Multiplication wrap */
if (count && unlikely(size * count / size != count))
return (size_t)-1024;
size *= count;
/* Make sure we don't wrap adding header. */
if (size > (size_t)-1024)
return (size_t)-1024;
return size;
}
#if HAVE_TYPEOF
#define tal_typeof(ptr) (__typeof__(ptr))
#else
#define tal_typeof(ptr)
#endif
void *tal_alloc_(const tal_t *ctx, size_t bytes, bool clear);
tal_t *tal_steal_(const tal_t *new_parent, const tal_t *t);
void *tal_realloc_(tal_t *ctx, size_t size);
bool tal_add_destructor_(tal_t *ctx, void (*destroy)(void *me));
#endif /* CCAN_TAL_H */
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
static int alloc_count, when_to_fail, err_count;
static bool stealing;
static void *failing_alloc(size_t len)
{
if (alloc_count++ == when_to_fail)
return NULL;
/* once we've failed once, it shouldn't ask again (steal can though). */
assert(stealing || alloc_count <= when_to_fail);
return malloc(len);
}
static void nofail_on_error(const char *msg)
{
diag("ERROR: %s", msg);
err_count++;
}
static void destroy_p(void *p)
{
}
int main(void)
{
void *p, *c1, *c2;
bool success;
plan_tests(21);
tal_set_backend(failing_alloc, NULL, NULL, nofail_on_error);
/* Fail at each possible point in an allocation. */
when_to_fail = err_count = 0;
do {
alloc_count = 0;
p = tal(NULL, char);
when_to_fail++;
} while (!p);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
/* Do it again. */
when_to_fail = err_count = 0;
do {
alloc_count = 0;
c1 = tal(p, char);
when_to_fail++;
} while (!c1);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
/* Now for second child. */
when_to_fail = err_count = 0;
do {
alloc_count = 0;
c2 = tal(p, char);
when_to_fail++;
} while (!c2);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
/* Note: adding a child will fall through if group alloc fails. */
ok1 (err_count == when_to_fail - 1 || err_count == when_to_fail);
/* Now while adding a destructor. */
when_to_fail = err_count = 0;
do {
alloc_count = 0;
success = tal_add_destructor(p, destroy_p);
when_to_fail++;
} while (!success);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
/* Now while adding a name. */
when_to_fail = err_count = 0;
do {
const char name[] = "some name";
alloc_count = 0;
success = tal_set_name(p, name);
when_to_fail++;
} while (!success);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
/* Now while stealing. */
stealing = true;
when_to_fail = err_count = 0;
do {
alloc_count = 0;
success = tal_steal(c2, c1) != NULL;
when_to_fail++;
} while (!success);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
/* Now stealing with more children (more coverage). */
when_to_fail = 1000;
(void)tal(p, char);
c1 = tal(p, char);
c2 = tal(p, char);
(void)tal(p, char);
/* Now steal again. */
when_to_fail = err_count = 0;
do {
alloc_count = 0;
success = tal_steal(c2, c1) != NULL;
when_to_fail++;
} while (!success);
ok1(alloc_count >= 1);
ok1(when_to_fail > 1);
ok1(err_count == when_to_fail - 1);
tal_free(p);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
int main(void)
{
char *parent, *c[4];
int i;
plan_tests(9);
parent = tal(NULL, char);
ok1(parent);
/* Zeroing allocations. */
for (i = 0; i < 4; i++) {
c[i] = talz(parent, char);
ok1(*c[i] == '\0');
tal_free(c[i]);
}
/* Array allocation. */
for (i = 0; i < 4; i++) {
c[i] = tal_arr(parent, char, 4);
strcpy(c[i], "abc");
tal_free(c[i]);
}
/* Zeroing array allocation. */
for (i = 0; i < 4; i++) {
c[i] = tal_arrz(parent, char, 4);
ok1(!c[i][0] && !c[i][1] && !c[i][2] && !c[i][3]);
strcpy(c[i], "abc");
tal_free(c[i]);
}
tal_free(parent);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
static char *parent, *child;
static int destroy_count;
/* Parent gets destroyed first. */
static void destroy_parent(char *p)
{
ok1(p == parent);
ok1(destroy_count == 0);
/* Can still access child. */
*child = '1';
destroy_count++;
}
static void destroy_child(char *p)
{
ok1(p == child);
ok1(destroy_count == 1);
/* Can still access parent (though destructor has been called). */
*parent = '1';
destroy_count++;
}
static void destroy_inc(char *p)
{
destroy_count++;
}
int main(void)
{
char *child2;
plan_tests(12);
parent = tal(NULL, char);
child = tal(parent, char);
ok1(tal_add_destructor(parent, destroy_parent));
ok1(tal_add_destructor(child, destroy_child));
tal_free(parent);
ok1(destroy_count == 2);
parent = tal(NULL, char);
child = tal(parent, char);
child2 = tal(parent, char);
ok1(tal_add_destructor(parent, destroy_inc));
ok1(tal_add_destructor(parent, destroy_inc));
ok1(tal_add_destructor(child, destroy_inc));
ok1(tal_add_destructor(child2, destroy_inc));
tal_free(parent);
ok1(destroy_count == 6);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
#define NUM 1000
int main(void)
{
char *p[NUM], *iter;
int i;
plan_tests(NUM + 1 + NUM);
/* Create a random tree, but make sure we get multiple
* top-level groups! */
for (i = 0; i < NUM; i++) {
p[i] = tal(NULL, char);
*p[i] = '0';
if (next_group(&null_parent.c.group) != &null_parent.c.group)
break;
}
for (i++; i < NUM; i++) {
p[i] = tal(p[rand() % i], char);
*p[i] = '0';
}
i = 0;
for (iter = tal_first(NULL); iter; iter = tal_next(NULL, iter)) {
i++;
ok1(*iter == '0');
*iter = '1';
}
ok1(i == NUM);
for (i = NUM-1; i >= 0; i--) {
ok1(*p[i] == '1');
tal_free(p[i]);
}
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
int main(void)
{
int *p;
char name[] = "test name";
plan_tests(5);
p = tal(NULL, int);
ok1(tal_name(p) == NULL);
tal_set_name(p, "some literal");
ok1(strcmp(tal_name(p), "some literal") == 0);
tal_set_name(p, name);
ok1(strcmp(tal_name(p), name) == 0);
/* You can't reuse my pointer though! */
ok1(tal_name(p) != name);
tal_set_name(p, "some other literal");
ok1(strcmp(tal_name(p), "some other literal") == 0);
tal_free(p);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
static int error_count;
static void my_error(const char *msg)
{
error_count++;
ok1(strstr(msg, "overflow"));
}
int main(void)
{
void *p;
plan_tests(6);
tal_set_backend(NULL, NULL, NULL, my_error);
p = tal_arr(NULL, int, (size_t)-1);
ok1(!p);
ok1(error_count == 1);
p = tal_arr(NULL, char, (size_t)-2);
ok1(!p);
ok1(error_count == 2);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
int main(void)
{
char *parent, *c;
plan_tests(9);
parent = tal(NULL, char);
ok1(parent);
c = tal_strdup(parent, "hello");
ok1(strcmp(c, "hello") == 0);
ok1(tal_parent(c) == parent);
c = tal_strndup(parent, "hello", 3);
ok1(strcmp(c, "hel") == 0);
ok1(tal_parent(c) == parent);
c = tal_memdup(parent, "hello", 6);
ok1(strcmp(c, "hello") == 0);
ok1(tal_parent(c) == parent);
c = tal_asprintf(parent, "hello %s", "there");
ok1(strcmp(c, "hello there") == 0);
ok1(tal_parent(c) == parent);
tal_free(parent);
return exit_status();
}
#include <stdlib.h>
#include <stdbool.h>
/* Make sure it always uses our allocation/resize/free fns! */
static bool my_alloc_called;
static void *my_alloc(size_t len)
{
my_alloc_called = true;
return (char *)malloc(len + 16) + 16;
}
static void my_free(void *p)
{
return free((char *)p - 16);
}
static void *my_realloc(void *old, size_t new_size)
{
return (char *)realloc((char *)old - 16, new_size + 16) + 16;
}
#define free ((void (*)(void *))abort)
#define malloc ((void *(*)(size_t))abort)
#define realloc ((void *(*)(void *, size_t))abort)
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
#define NUM_ALLOCS 1000
static void destroy_p(void *p)
{
}
int main(void)
{
void *p, *c[NUM_ALLOCS];
int i;
char *name;
/* Mostly we rely on the allocator (or valgrind) crashing. */
plan_tests(1);
tal_set_backend(my_alloc, my_realloc, my_free, NULL);
p = tal(NULL, char);
ok1(my_alloc_called);
/* Adding properties makes us allocated. */
tal_add_destructor(p, destroy_p);
tal_set_name(p, "test");
name = tal_asprintf(NULL, "test2");
tal_set_name(p, name);
/* makes us free old name */
tal_set_name(p, name);
tal_free(name);
/* Add lots of children. */
for (i = 0; i < NUM_ALLOCS; i++)
c[i] = tal(p, char);
/* Now steal a few. */
for (i = 1; i < NUM_ALLOCS / 2; i++)
tal_steal(c[0], c[i]);
/* Now free individual ones.. */
for (i = NUM_ALLOCS / 2; i < NUM_ALLOCS; i++)
tal_free(c[i]);
/* Finally, free the parent. */
tal_free(p);
return exit_status();
}
#include <ccan/tal/tal.h>
#include <ccan/tal/tal.c>
#include <ccan/tap/tap.h>
int main(void)
{
char *parent, *c[4], *p;
int i, j;
plan_tests(10);
parent = tal(NULL, char);
ok1(parent);
for (i = 0; i < 4; i++)
c[i] = tal(parent, char);
for (i = 0; i < 4; i++)
ok1(tal_parent(c[i]) == parent);
/* Iteration test. */
i = 0;
for (p = tal_first(parent); p; p = tal_next(parent, p)) {
*p = '1';
i++;
}
ok1(i == 4);
ok1(*c[0] == '1');
ok1(*c[1] == '1');
ok1(*c[2] == '1');
ok1(*c[3] == '1');
/* Free parent. */
tal_free(parent);
parent = tal(NULL, char);
/* Test freeing in every order */
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++)
c[j] = tal(parent, char);
tal_free(c[i]);
debug_tal(to_tal_hdr(parent));
tal_free(c[(i+1) % 4]);
debug_tal(to_tal_hdr(parent));
tal_free(c[(i+2) % 4]);
debug_tal(to_tal_hdr(parent));
tal_free(c[(i+3) % 4]);
debug_tal(to_tal_hdr(parent));
}
tal_free(parent);
return exit_status();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment