summaryrefslogtreecommitdiff
path: root/ccan/take
diff options
context:
space:
mode:
authorWilliam Casarin <jb55@jb55.com>2018-07-09 12:10:32 -0700
committerWilliam Casarin <jb55@jb55.com>2018-07-09 12:10:32 -0700
commit1b8fbbd843ddeb5fc81c9303db9c590a436d499b (patch)
treea7227dfe8e4fbaee7b1e0b58b24994dce8078f3f /ccan/take
parent37a9cdd2e80386f2c94e14e4f511284ae14c745a (diff)
progress
Diffstat (limited to 'ccan/take')
l---------ccan/take/LICENSE1
-rw-r--r--ccan/take/_info61
-rw-r--r--ccan/take/take.c115
-rw-r--r--ccan/take/take.h136
-rw-r--r--ccan/take/test/run-debug.c34
-rw-r--r--ccan/take/test/run.c102
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();
+}