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;
}
This diff is collapsed.
/* 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