diff options
| author | William Casarin <jb55@jb55.com> | 2018-07-09 12:10:32 -0700 |
|---|---|---|
| committer | William Casarin <jb55@jb55.com> | 2018-07-09 12:10:32 -0700 |
| commit | 1b8fbbd843ddeb5fc81c9303db9c590a436d499b (patch) | |
| tree | a7227dfe8e4fbaee7b1e0b58b24994dce8078f3f /ccan/take | |
| parent | 37a9cdd2e80386f2c94e14e4f511284ae14c745a (diff) | |
progress
Diffstat (limited to 'ccan/take')
| l--------- | ccan/take/LICENSE | 1 | ||||
| -rw-r--r-- | ccan/take/_info | 61 | ||||
| -rw-r--r-- | ccan/take/take.c | 115 | ||||
| -rw-r--r-- | ccan/take/take.h | 136 | ||||
| -rw-r--r-- | ccan/take/test/run-debug.c | 34 | ||||
| -rw-r--r-- | ccan/take/test/run.c | 102 |
6 files changed, 449 insertions, 0 deletions
diff --git a/ccan/take/LICENSE b/ccan/take/LICENSE new file mode 120000 index 0000000..b7951da --- /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 0000000..c8cc4ac --- /dev/null +++ b/ccan/take/_info @@ -0,0 +1,61 @@ +#include "config.h" +#include <stdio.h> +#include <string.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 <rusty@rustcorp.com.au> + * License: CC0 (Public domain) + * + * Example: + * // Given "foo/bar.c" outputs basename is bar.c + * #include <ccan/take/take.h> + * #include <string.h> + * + * // Dumb basename program and driver. + * static char *base(const char *file TAKES) + * { + * 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 (argc > 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"); + printf("ccan/str\n"); + return 0; + } + + return 1; +} diff --git a/ccan/take/take.c b/ccan/take/take.c new file mode 100644 index 0000000..c628aac --- /dev/null +++ b/ccan/take/take.c @@ -0,0 +1,115 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#include <ccan/take/take.h> +#include <ccan/likely/likely.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const void **takenarr; +static const char **labelarr; +static size_t max_taken, num_taken; +static size_t allocfail; +static void (*allocfailfn)(const void *p); + +void *take_(const void *p, const char *label) +{ + /* Overallocate: it's better than risking calloc returning NULL! */ + if (unlikely(label && !labelarr)) + labelarr = calloc(max_taken+1, sizeof(*labelarr)); + + 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; + /* Once labelarr is set, we maintain it. */ + if (labelarr) + labelarr = realloc(labelarr, + sizeof(*labelarr) * (max_taken+1)); + max_taken++; + } + if (unlikely(labelarr)) + labelarr[num_taken] = label; + 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; +} + +const char *taken_any(void) +{ + static char pointer_buf[32]; + + if (num_taken == 0) + return NULL; + + /* We're *allowed* to have some with labels, some without. */ + if (labelarr) { + size_t i; + for (i = 0; i < num_taken; i++) + if (labelarr[i]) + return labelarr[i]; + } + + sprintf(pointer_buf, "%p", takenarr[0]); + return pointer_buf; +} + +void take_cleanup(void) +{ + max_taken = num_taken = 0; + free(takenarr); + takenarr = NULL; + free(labelarr); + labelarr = 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 0000000..8950c6b --- /dev/null +++ b/ccan/take/take.h @@ -0,0 +1,136 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#ifndef CCAN_TAKE_H +#define CCAN_TAKE_H +#include "config.h" +#include <stdbool.h> +#include <ccan/str/str.h> + +#ifdef CCAN_TAKE_DEBUG +#define TAKE_LABEL(p) __FILE__ ":" stringify(__LINE__) ":" stringify(p) +#else +#define TAKE_LABEL(p) NULL +#endif + +/** + * TAKES - annotate a formal parameter as being take()-able + * + * This doesn't do anything, but useful for documentation. + * + * Example: + * void print_string(const char *str TAKES); + * + */ +#define TAKES + +/** + * 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), TAKE_LABEL(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 TAKES) + * { + * 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. With CCAN_TAKE_DEBUG, returns + * the label where the pointer was passed to take(), otherwise returns + * a static char buffer with the pointer value in it. NULL if none are taken. + * + * Example: + * static void cleanup(void) + * { + * assert(!taken_any()); + * } + */ +const char *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, const char *label); +#endif /* CCAN_TAKE_H */ diff --git a/ccan/take/test/run-debug.c b/ccan/take/test/run-debug.c new file mode 100644 index 0000000..a9dda6e --- /dev/null +++ b/ccan/take/test/run-debug.c @@ -0,0 +1,34 @@ +#include <stdlib.h> +#include <stdbool.h> + +#define CCAN_TAKE_DEBUG 1 +#include <ccan/take/take.h> +#include <ccan/take/take.c> +#include <ccan/tap/tap.h> + +int main(void) +{ + const char *p = "hi"; + + plan_tests(14); + + /* We can take NULL. */ + ok1(take(NULL) == NULL); + ok1(is_taken(NULL)); + ok1(strstr(taken_any(), "run-debug.c:16:")); + 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(strends(taken_any(), "run-debug.c:25:p")); + ok1(taken(p)); /* Undoes take() */ + ok1(!is_taken(p)); + ok1(!taken(p)); + ok1(!taken_any()); + + return exit_status(); +} diff --git a/ccan/take/test/run.c b/ccan/take/test/run.c new file mode 100644 index 0000000..ba170fb --- /dev/null +++ b/ccan/take/test/run.c @@ -0,0 +1,102 @@ +#include <stdlib.h> +#include <stdbool.h> + +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 <ccan/take/take.h> +#include <ccan/take/take.c> +#include <ccan/tap/tap.h> + +static int my_allocfail_called; +static void my_allocfail(const void *p UNNEEDED) +{ + 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(); +} |
