From 4e5111fb40e1882b8108d61277d3e506cb958124 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 18 Jul 2009 17:59:57 +0930 Subject: [PATCH] talloc_link; a replacement for talloc's references. --- ccan/talloc_link/_info | 146 +++++++++++++++++++++++++++++++++ ccan/talloc_link/talloc_link.c | 95 +++++++++++++++++++++ ccan/talloc_link/talloc_link.h | 47 +++++++++++ ccan/talloc_link/test/run.c | 116 ++++++++++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 ccan/talloc_link/_info create mode 100644 ccan/talloc_link/talloc_link.c create mode 100644 ccan/talloc_link/talloc_link.h create mode 100644 ccan/talloc_link/test/run.c diff --git a/ccan/talloc_link/_info b/ccan/talloc_link/_info new file mode 100644 index 00000000..fe66c24e --- /dev/null +++ b/ccan/talloc_link/_info @@ -0,0 +1,146 @@ +#include +#include +#include "config.h" + +/** + * talloc_link - link helper for talloc + * + * Talloc references can be confusing and buggy. In the cases where an object + * needs multiple parents, all parents need to be aware of the situation; thus + * talloc_link is a helper where all "parents" talloc_link an object they + * agree to share ownership of. + * + * Example: + * // Silly program which keeps a cache of uppercased strings. + * // The cache wants to keep strings around even after they may have + * // been "freed" by the caller. + * #include + * #include + * #include + * #include + * #include + * #include + * + * struct upcache { + * const char *str; + * const char *upstr; + * }; + * + * static struct upcache *cache; + * static unsigned int cache_hits = 0; + * #define CACHE_SIZE 4 + * void init_upcase(void) + * { + * cache = talloc_zero_array(NULL, struct upcache, CACHE_SIZE); + * } + * + * static struct upcache *lookup_upcase(const char *str) + * { + * unsigned int i; + * for (i = 0; i < CACHE_SIZE; i++) + * if (cache[i].str && !strcmp(cache[i].str, str)) { + * cache_hits++; + * return &cache[i]; + * } + * return NULL; + * } + * + * static struct upcache *new_upcase(const char *str) + * { + * unsigned int i; + * char *upstr; + * + * upstr = talloc_linked(cache, talloc_strdup(NULL, str)); + * if (!upstr) + * return NULL; + * + * i = random() % CACHE_SIZE; + * + * // Throw out old: works fine if cache[i].upstr is NULL. + * talloc_delink(cache, cache[i].upstr); + * + * // Replace with new. + * cache[i].str = str; + * cache[i].upstr = upstr; + * while (*upstr) { + * *upstr = toupper(*upstr); + * upstr++; + * } + * return &cache[i]; + * } + * + * // If you want to keep the result, talloc_link it. + * const char *get_upcase(const char *str) + * { + * struct upcache *uc = lookup_upcase(str); + * if (!uc) + * uc = new_upcase(str); + * if (!uc) + * return NULL; + * return uc->upstr; + * } + * + * void exit_upcase(void) + * { + * talloc_free(cache); + * printf("Cache hits: %u\n", cache_hits); + * } + * + * int main(int argc, char *argv[]) + * { + * unsigned int i; + * const char **values; + * + * // Will dump any memory leaks to stderr on exit. + * talloc_enable_leak_report(); + * + * // Initialize cache. + * init_upcase(); + * + * // Throw values in. + * values = talloc_array(NULL, const char *, argc); + * for (i = 1; i < argc; i++) { + * values[i-1] = talloc_link(values, get_upcase(argv[i])); + * if (!values[i-1]) + * err(1, "Out of memory"); + * } + * // This will free all the values, but cache will still work. + * talloc_free(values); + * + * // Repeat! + * values = talloc_array(NULL, const char *, argc); + * for (i = 1; i < argc; i++) { + * values[i-1] = talloc_link(values, get_upcase(argv[i])); + * if (!values[i-1]) + * err(1, "Out of memory"); + * } + * + * // This will remove cache links, but we still have a link. + * exit_upcase(); + * + * // Show values, so we output something. + * for (i = 0; i < argc - 1; i++) + * printf("%s ", values[i]); + * printf("\n"); + * + * // This will finally free the upcase strings (last link). + * talloc_free(values); + * + * return 0; + * } + * + * Licence: GPL (2 or any later version) + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/talloc\n"); + printf("ccan/list\n"); + return 0; + } + + return 1; +} diff --git a/ccan/talloc_link/talloc_link.c b/ccan/talloc_link/talloc_link.c new file mode 100644 index 00000000..b72b436e --- /dev/null +++ b/ccan/talloc_link/talloc_link.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include + +/* Fake parent, if they care. */ +static void *talloc_links = NULL; + +/* This is the parent of the linked object, so we can implement delink. */ +struct talloc_linked { + struct list_head links; + const void *obj; +}; + +/* This is a child of the linker, but not a parent of ref. */ +struct talloc_link { + struct list_node list; + struct talloc_linked *linked; +}; + +static int destroy_link(struct talloc_link *link) +{ + list_del(&link->list); + if (list_empty(&link->linked->links)) + talloc_free(link->linked); + return 0; +} + +static bool add_link(const void *ctx, struct talloc_linked *linked) +{ + struct talloc_link *link = talloc(ctx, struct talloc_link); + if (!link) + return false; + + link->linked = linked; + list_add(&linked->links, &link->list); + talloc_set_destructor(link, destroy_link); + return true; +} + +void *_talloc_linked(const void *ctx, const void *newobj) +{ + struct talloc_linked *linked; + + if (talloc_parent(newobj)) { + /* Assume leak reporting is on: create dummy parent. */ + if (!talloc_links) + talloc_links = talloc_named_const(NULL, 0, + "talloc_links"); + /* This should now have same pseudo-NULL parent. */ + assert(talloc_parent(newobj) == talloc_parent(talloc_links)); + } + + linked = talloc(talloc_links, struct talloc_linked); + if (!linked) { + talloc_free(newobj); + return NULL; + } + list_head_init(&linked->links); + linked->obj = talloc_steal(linked, newobj); + + if (!add_link(ctx, linked)) { + talloc_free(linked); + return NULL; + } + + return (void *)newobj; +} + +void *_talloc_link(const void *ctx, const void *obj) +{ + struct talloc_linked *linked; + + linked = talloc_get_type(talloc_parent(obj), struct talloc_linked); + assert(!list_empty(&linked->links)); + return add_link(ctx, linked) ? (void *)obj : NULL; +} + +void talloc_delink(const void *ctx, const void *obj) +{ + struct talloc_linked *linked; + struct talloc_link *i; + + if (!obj) + return; + + linked = talloc_get_type(talloc_parent(obj), struct talloc_linked); + list_for_each(&linked->links, i, list) { + if (talloc_is_parent(i, ctx)) { + talloc_free(i); + return; + } + } + abort(); +} diff --git a/ccan/talloc_link/talloc_link.h b/ccan/talloc_link/talloc_link.h new file mode 100644 index 00000000..a51b760c --- /dev/null +++ b/ccan/talloc_link/talloc_link.h @@ -0,0 +1,47 @@ +#ifndef TALLOC_LINK_H +#define TALLOC_LINK_H +#include + +/** + * talloc_linked - set up an object with an initial link. + * @ctx - the context to initially link to + * @newobj - the newly allocated object (with a NULL parent) + * + * The object will be freed when @ctx is freed (or talloc_delink(ctx, + * newobj) is called), unless more links are added using + * talloc_link(). + * + * For convenient chaining, it returns @newobj on success, or frees + * @newobj and returns NULL. + */ +#define talloc_linked(ctx, newobj) \ + ((_TALLOC_TYPEOF(newobj))_talloc_linked((ctx), (newobj))) + +/** + * talloc_link - add another link to a linkable object. + * @ctx - the context to link to + * @obj - the object previously made linkable with talloc_linked(). + * + * The @obj will only be freed when all contexts linked to it are + * freed (or talloc_delink()ed). + * + * Returns @obj, or NULL on failure (out of memory). + */ +#define talloc_link(ctx, obj) \ + ((_TALLOC_TYPEOF(obj))_talloc_link((ctx), (obj))) + +/** + * talloc_delink - explicitly remove a link from a linkable object. + * @ctx - the context previously used for talloc_link/talloc_linked + * @obj - the object previously used for talloc_link/talloc_linked + * + * Explicitly remove a link: normally it is implied by freeing @ctx. + * Removing the last link frees the object. + */ +void talloc_delink(const void *ctx, const void *linked); + +/* Internal helpers. */ +void *_talloc_link(const void *ctx, const void *linked); +void *_talloc_linked(const void *ctx, const void *linked); + +#endif /* TALLOC_LINK_H */ diff --git a/ccan/talloc_link/test/run.c b/ccan/talloc_link/test/run.c new file mode 100644 index 00000000..e095bd9e --- /dev/null +++ b/ccan/talloc_link/test/run.c @@ -0,0 +1,116 @@ +#include "talloc_link/talloc_link.h" +#include "tap/tap.h" +#include "talloc_link/talloc_link.c" +#include +#include + +static unsigned int destroy_count = 0; +static int destroy_obj(void *obj) +{ + destroy_count++; + return 0; +} + +int main(int argc, char *argv[]) +{ + void *obj, *p1, *p2, *p3; + + plan_tests(16); + + talloc_enable_leak_report(); + + /* Single parent case. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + + ok(destroy_count == 0, "destroy_count = %u", destroy_count); + talloc_free(p1); + ok(destroy_count == 1, "destroy_count = %u", destroy_count); + + /* Dual parent case. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + p2 = talloc(NULL, char); + talloc_link(p2, obj); + + talloc_free(p1); + ok(destroy_count == 1, "destroy_count = %u", destroy_count); + talloc_free(p2); + ok(destroy_count == 2, "destroy_count = %u", destroy_count); + + /* Triple parent case. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + + p2 = talloc(NULL, char); + p3 = talloc(NULL, char); + + talloc_link(p2, obj); + talloc_link(p3, obj); + + talloc_free(p1); + ok(destroy_count == 2, "destroy_count = %u", destroy_count); + talloc_free(p2); + ok(destroy_count == 2, "destroy_count = %u", destroy_count); + talloc_free(p3); + ok(destroy_count == 3, "destroy_count = %u", destroy_count); + + /* Single delink case. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + + ok(destroy_count == 3, "destroy_count = %u", destroy_count); + talloc_delink(p1, obj); + ok(destroy_count == 4, "destroy_count = %u", destroy_count); + talloc_free(p1); + + /* Double delink case. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + + p2 = talloc(NULL, char); + talloc_link(p2, obj); + + talloc_delink(p1, obj); + ok(destroy_count == 4, "destroy_count = %u", destroy_count); + talloc_delink(p2, obj); + ok(destroy_count == 5, "destroy_count = %u", destroy_count); + talloc_free(p1); + talloc_free(p2); + + /* Delink and free. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + p2 = talloc(NULL, char); + talloc_link(p2, obj); + + talloc_delink(p1, obj); + ok(destroy_count == 5, "destroy_count = %u", destroy_count); + talloc_free(p2); + ok(destroy_count == 6, "destroy_count = %u", destroy_count); + talloc_free(p1); + + /* Free and delink. */ + p1 = talloc(NULL, char); + obj = talloc_linked(p1, talloc(NULL, char)); + talloc_set_destructor(obj, destroy_obj); + p2 = talloc(NULL, char); + talloc_link(p2, obj); + + talloc_free(p1); + ok(destroy_count == 6, "destroy_count = %u", destroy_count); + talloc_delink(p2, obj); + ok(destroy_count == 7, "destroy_count = %u", destroy_count); + talloc_free(p2); + + /* No leaks? */ + ok1(talloc_total_size(NULL) == 0); + + return exit_status(); +} -- 2.39.2