From: Rusty Russell Date: Wed, 21 Nov 2012 23:28:31 +0000 (+1030) Subject: take: new module for parameter ownership. X-Git-Url: http://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=c8a55bb39404772d459f0153f07db7ecc91ecfd0;ds=sidebyside take: new module for parameter ownership. Signed-off-by: Rusty Russell --- diff --git a/ccan/take/LICENSE b/ccan/take/LICENSE new file mode 120000 index 00000000..b7951dab --- /dev/null +++ b/ccan/take/LICENSE @@ -0,0 +1 @@ +../../licenses/CC0 \ No newline at end of file diff --git a/ccan/take/_info b/ccan/take/_info new file mode 100644 index 00000000..34b08861 --- /dev/null +++ b/ccan/take/_info @@ -0,0 +1,59 @@ +#include +#include "config.h" + +/** + * take - routines to mark pointers to be consumed by called functions. + * + * This code helps to implement ownership transfer on a per-arg basis: + * the caller wraps the pointer argument in take() and the callee checks + * taken() to see if it should consume it. + * + * Author: Rusty Russell + * License: CC0 (Public domain) + * + * Example: + * // Given foo/bar.c outputs basename is bar.c + * #include + * #include + * + * // Dumb basename program and driver. + * static char *base(const char *file) + * { + * const char *p = strrchr(file, '/'); + * if (!p) + * p = file; + * else + * p++; + * + * // Use arg in place if we're allowed. + * if (taken(file)) + * return memmove((char *)file, p, strlen(p)+1); + * else + * return strdup(p); + * } + * + * int main(int argc, char *argv[]) + * { + * char *b; + * + * if (argv[1]) // Mangle in place. + * b = base(take(argv[1])); + * else + * b = base("test/string"); + * + * printf("basename is %s\n", b); + * return 0; + * } + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/likely\n"); + return 0; + } + + return 1; +} diff --git a/ccan/take/take.c b/ccan/take/take.c new file mode 100644 index 00000000..73e5c29d --- /dev/null +++ b/ccan/take/take.c @@ -0,0 +1,86 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#include +#include +#include +#include + +static const void **takenarr; +static size_t max_taken, num_taken; +static size_t allocfail; +static void (*allocfailfn)(const void *p); + +void *take_(const void *p) +{ + if (unlikely(num_taken == max_taken)) { + const void **new; + + new = realloc(takenarr, sizeof(*takenarr) * (max_taken+1)); + if (unlikely(!new)) { + if (allocfailfn) { + allocfail++; + allocfailfn(p); + return NULL; + } + /* Otherwise we leak p. */ + return (void *)p; + } + takenarr = new; + max_taken++; + } + takenarr[num_taken++] = p; + return (void *)p; +} + +static size_t find_taken(const void *p) +{ + size_t i; + + for (i = 0; i < num_taken; i++) { + if (takenarr[i] == p) + return i+1; + } + return 0; +} + +bool taken(const void *p) +{ + size_t i; + + if (!p && unlikely(allocfail)) { + allocfail--; + return true; + } + + i = find_taken(p); + if (!i) + return false; + + memmove(&takenarr[i-1], &takenarr[i], + (--num_taken - (i - 1))*sizeof(takenarr[0])); + return true; +} + +bool is_taken(const void *p) +{ + if (!p && unlikely(allocfail)) + return true; + + return find_taken(p) > 0; +} + +bool taken_any(void) +{ + return num_taken != 0; +} + +void take_cleanup(void) +{ + max_taken = num_taken = 0; + free(takenarr); + takenarr = NULL; +} + +void take_allocfail(void (*fn)(const void *p)) +{ + allocfailfn = fn; +} diff --git a/ccan/take/take.h b/ccan/take/take.h new file mode 100644 index 00000000..b6ac4a9f --- /dev/null +++ b/ccan/take/take.h @@ -0,0 +1,116 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#ifndef CCAN_TAKE_H +#define CCAN_TAKE_H +#include "config.h" +#include + +/** + * take - record a pointer to be consumed by the function its handed to. + * @p: the pointer to mark, or NULL. + * + * This marks a pointer object to be freed by the called function, + * which is extremely useful for chaining functions. It works on + * NULL, for pass-through error handling. + */ +#define take(p) (take_typeof(p) take_((p))) + +/** + * taken - check (and un-take) a pointer was passed with take() + * @p: the pointer to check. + * + * A function which accepts take() arguments uses this to see if it + * should own the pointer; it will be removed from the take list, so + * this only returns true once. + * + * Example: + * // Silly routine to add 1 + * static int *add_one(const int *num) + * { + * int *ret; + * if (taken(num)) + * ret = (int *)num; + * else + * ret = malloc(sizeof(int)); + * if (ret) + * *ret = (*num) + 1; + * return ret; + * } + */ +bool taken(const void *p); + +/** + * is_taken - check if a pointer was passed with take() + * @p: the pointer to check. + * + * This is like the above, but doesn't remove it from the taken list. + * + * Example: + * // Silly routine to add 1: doesn't handle taken args! + * static int *add_one_notake(const int *num) + * { + * int *ret = malloc(sizeof(int)); + * assert(!is_taken(num)); + * if (ret) + * *ret = (*num) + 1; + * return ret; + * } + */ +bool is_taken(const void *p); + +/** + * taken_any - are there any taken pointers? + * + * Mainly useful for debugging take() leaks. + * + * Example: + * static void cleanup(void) + * { + * assert(!taken_any()); + * } + */ +bool taken_any(void); + +/** + * take_cleanup - remove all taken pointers from list. + * + * This is useful in atexit() handlers for valgrind-style leak detection. + * + * Example: + * static void cleanup2(void) + * { + * take_cleanup(); + * } + */ +void take_cleanup(void); + +/** + * take_allocfail - set function to call if we can't reallocated taken array. + * @fn: the function. + * + * If this is not set, then if the array reallocation fails, the + * pointer won't be marked taken(). If @fn returns, it is expected to + * free the pointer; we return NULL from take() and the function handles + * it like any allocation failure. + * + * Example: + * static void free_on_fail(const void *p) + * { + * free((void *)p); + * } + * + * static void init(void) + * { + * take_allocfail(free_on_fail); + * } + */ +void take_allocfail(void (*fn)(const void *p)); + +/* Private functions */ +#if HAVE_TYPEOF +#define take_typeof(ptr) (__typeof__(ptr)) +#else +#define take_typeof(ptr) +#endif + +void *take_(const void *p); +#endif /* CCAN_TAKE_H */ diff --git a/ccan/take/test/run.c b/ccan/take/test/run.c new file mode 100644 index 00000000..0c8ca2a1 --- /dev/null +++ b/ccan/take/test/run.c @@ -0,0 +1,102 @@ +#include +#include + +static bool fail_realloc; +static void *my_realloc(void *p, size_t len) +{ + if (fail_realloc) + return NULL; + return realloc(p, len); +} +#define realloc my_realloc + +#include +#include +#include + +static int my_allocfail_called; +static void my_allocfail(const void *p) +{ + my_allocfail_called++; +} + +static void recurse(const char *takeme, int count) +{ + if (count < 1000) + recurse(take(strdup(takeme)), count+1); + if (taken(takeme)) + free((char *)takeme); +} + +int main(void) +{ + const char *p = "hi"; + + plan_tests(43); + + /* We can take NULL. */ + ok1(take(NULL) == NULL); + ok1(is_taken(NULL)); + ok1(taken_any()); + ok1(taken(NULL)); /* Undoes take() */ + ok1(!is_taken(NULL)); + ok1(!taken(NULL)); + + /* We can take NULL twice! */ + ok1(take(NULL) == NULL); + ok1(take(NULL) == NULL); + ok1(is_taken(NULL)); + ok1(taken_any()); + ok1(taken(NULL)); /* Undoes take() */ + ok1(is_taken(NULL)); + ok1(taken_any()); + ok1(taken(NULL)); /* Undoes take() */ + ok1(!is_taken(NULL)); + ok1(!taken(NULL)); + ok1(!taken_any()); + + /* We can take a real pointer. */ + ok1(take(p) == p); + ok1(is_taken(p)); + ok1(taken_any()); + ok1(taken(p)); /* Undoes take() */ + ok1(!is_taken(p)); + ok1(!taken(p)); + ok1(!taken_any()); + + /* Force a failure. */ + ok1(!my_allocfail_called); + ok1(take(p) == p); + ok1(take(p+1) == p+1); + + fail_realloc = true; + /* Without a handler, must pass through and leak. */ + ok1(take(p+2) == p+2); + ok1(!taken(p+2)); + + /* Now, with a handler. */ + take_allocfail(my_allocfail); + ok1(take(p+2) == NULL); + + ok1(my_allocfail_called == 1); + ok1(taken_any()); + ok1(taken(p)); + ok1(taken(p+1)); + ok1(is_taken(NULL)); + ok1(taken(NULL)); + ok1(!taken(NULL)); + ok1(!taken_any()); + + /* Test some deep nesting. */ + fail_realloc = false; + recurse("hello", 0); + ok1(max_taken == 1000); + ok1(!taken_any()); + + take_cleanup(); + ok1(num_taken == 0); + ok1(max_taken == 0); + ok1(takenarr == NULL); + + return exit_status(); +}