From 4ef9bd22ed091dd6513f89cdca2dfd03b6e3ffd2 Mon Sep 17 00:00:00 2001 From: Ed Schouten Date: Thu, 13 Oct 2016 18:25:40 +0000 Subject: [PATCH] Improve typing of POSIX search tree functions. Back in 2015 when I reimplemented these functions to use an AVL tree, I was annoyed by the weakness of the typing of these functions. Both tree nodes and keys are represented by 'void *', meaning that things like the documentation for these functions are an absolute train wreck. To make things worse, users of these functions need to cast the return value of tfind()/tsearch() from 'void *' to 'type_of_key **' in order to access the key. Technically speaking such casts violate aliasing rules. I've observed actual breakages as a result of this by enabling features like LTO. I've filed a bug report at the Austin Group. Looking at the way the bug got resolved, they made a pretty good step in the right direction. A new type 'posix_tnode' has been added to correspond to tree nodes. It is still defined as 'void' for source-level compatibility, but in the very far future it could be replaced by a proper structure type containing a key pointer. MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D8205 --- include/search.h | 23 ++++++++++------- lib/libc/stdlib/tdelete.c | 24 ++++++++---------- lib/libc/stdlib/tfind.c | 7 ++---- lib/libc/stdlib/tsearch.3 | 29 ++++++++++++++++------ lib/libc/stdlib/tsearch.c | 37 +++++++++++++--------------- lib/libc/stdlib/twalk.c | 9 +++---- lib/libc/tests/stdlib/tsearch_test.c | 4 +-- 7 files changed, 70 insertions(+), 63 deletions(-) diff --git a/include/search.h b/include/search.h index 4c1f534c667a..068446fdcb88 100644 --- a/include/search.h +++ b/include/search.h @@ -34,16 +34,18 @@ typedef enum { } VISIT; #ifdef _SEARCH_PRIVATE -typedef struct node { - void *key; - struct node *llink, *rlink; - signed char balance; -} node_t; +typedef struct __posix_tnode { + void *key; + struct __posix_tnode *llink, *rlink; + signed char balance; +} posix_tnode; struct que_elem { struct que_elem *next; struct que_elem *prev; }; +#else +typedef void posix_tnode; #endif #if __BSD_VISIBLE @@ -62,12 +64,15 @@ void *lfind(const void *, const void *, size_t *, size_t, void *lsearch(const void *, void *, size_t *, size_t, int (*)(const void *, const void *)); void remque(void *); -void *tdelete(const void * __restrict, void ** __restrict, +void *tdelete(const void * __restrict, posix_tnode ** __restrict, int (*)(const void *, const void *)); -void *tfind(const void *, void * const *, +posix_tnode * + tfind(const void *, posix_tnode * const *, int (*)(const void *, const void *)); -void *tsearch(const void *, void **, int (*)(const void *, const void *)); -void twalk(const void *, void (*)(const void *, VISIT, int)); +posix_tnode * + tsearch(const void *, posix_tnode **, + int (*)(const void *, const void *)); +void twalk(const posix_tnode *, void (*)(const posix_tnode *, VISIT, int)); #if __BSD_VISIBLE int hcreate_r(size_t, struct hsearch_data *); diff --git a/lib/libc/stdlib/tdelete.c b/lib/libc/stdlib/tdelete.c index ff63576a1bf5..38b2bd739170 100644 --- a/lib/libc/stdlib/tdelete.c +++ b/lib/libc/stdlib/tdelete.c @@ -46,9 +46,9 @@ __FBSDID("$FreeBSD$"); * that we won't need to perform any rotations above \ * this point. In this case rotations are always \ * capable of keeping the subtree in balance. Make \ - * this the base node and reset the path. \ + * this the root node and reset the path. \ */ \ - base = leaf; \ + rootp = leaf; \ path_init(&path); \ } \ path_taking_left(&path); \ @@ -59,7 +59,7 @@ __FBSDID("$FreeBSD$"); #define GO_RIGHT() do { \ if ((*leaf)->balance == 0 || \ ((*leaf)->balance > 0 && (*leaf)->llink->balance == 0)) { \ - base = leaf; \ + rootp = leaf; \ path_init(&path); \ } \ path_taking_right(&path); \ @@ -67,18 +67,16 @@ __FBSDID("$FreeBSD$"); } while (0) void * -tdelete(const void *restrict key, void **restrict rootp, +tdelete(const void *restrict key, posix_tnode **restrict rootp, int (*compar)(const void *, const void *)) { struct path path; - node_t *root, **base, **leaf, *old, **n, *x, *y, *z; - void *result; + posix_tnode **leaf, *old, **n, *x, *y, *z, *result; int cmp; /* POSIX requires that tdelete() returns NULL if rootp is NULL. */ if (rootp == NULL) return (NULL); - root = *rootp; /* * Find the leaf that needs to be removed. Return if we cannot @@ -86,19 +84,18 @@ tdelete(const void *restrict key, void **restrict rootp, * to get to the node, as we will need it to adjust the * balances. */ - result = (void *)1; + result = (posix_tnode *)1; path_init(&path); - base = &root; - leaf = &root; + leaf = rootp; for (;;) { if (*leaf == NULL) return (NULL); cmp = compar(key, (*leaf)->key); if (cmp < 0) { - result = &(*leaf)->key; + result = *leaf; GO_LEFT(); } else if (cmp > 0) { - result = &(*leaf)->key; + result = *leaf; GO_RIGHT(); } else { break; @@ -134,7 +131,7 @@ tdelete(const void *restrict key, void **restrict rootp, * and left-left case that only exists when deleting. Hence the * duplication of code. */ - for (n = base; n != leaf;) { + for (n = rootp; n != leaf;) { if (path_took_left(&path)) { x = *n; if (x->balance < 0) { @@ -207,6 +204,5 @@ tdelete(const void *restrict key, void **restrict rootp, } /* Return the parent of the old entry. */ - *rootp = root; return (result); } diff --git a/lib/libc/stdlib/tfind.c b/lib/libc/stdlib/tfind.c index 0ad391e79981..afcbc16cd2d4 100644 --- a/lib/libc/stdlib/tfind.c +++ b/lib/libc/stdlib/tfind.c @@ -4,8 +4,6 @@ * Tree search generalized from Knuth (6.2.2) Algorithm T just like * the AT&T man page says. * - * The node_t structure is for internal use only, lint doesn't grok it. - * * Written by reading the System V Interface Definition, not the code. * * Totally public domain. @@ -29,11 +27,10 @@ __FBSDID("$FreeBSD$"); * vkey - key to be found * vrootp - address of the tree root */ -void * -tfind(const void *vkey, void * const *vrootp, +posix_tnode * +tfind(const void *vkey, posix_tnode * const *rootp, int (*compar)(const void *, const void *)) { - node_t **rootp = (node_t **)vrootp; if (rootp == NULL) return NULL; diff --git a/lib/libc/stdlib/tsearch.3 b/lib/libc/stdlib/tsearch.3 index 2205f7e5aa85..f178c208ae34 100644 --- a/lib/libc/stdlib/tsearch.3 +++ b/lib/libc/stdlib/tsearch.3 @@ -27,7 +27,7 @@ .\" OpenBSD: tsearch.3,v 1.2 1998/06/21 22:13:49 millert Exp .\" $FreeBSD$ .\" -.Dd December 6, 2015 +.Dd October 9, 2016 .Dt TSEARCH 3 .Os .Sh NAME @@ -36,13 +36,13 @@ .Sh SYNOPSIS .In search.h .Ft void * -.Fn tdelete "const void * restrict key" "void ** restrict rootp" "int (*compar) (const void *, const void *)" -.Ft void * -.Fn tfind "const void *key" "void * const *rootp" "int (*compar) (const void *, const void *)" -.Ft void * -.Fn tsearch "const void *key" "void **rootp" "int (*compar) (const void *, const void *)" +.Fn tdelete "const void * restrict key" "posix_tnode ** restrict rootp" "int (*compar) (const void *, const void *)" +.Ft posix_tnode * +.Fn tfind "const void *key" "posix_tnode * const *rootp" "int (*compar) (const void *, const void *)" +.Ft posix_tnode * +.Fn tsearch "const void *key" "posix_tnode **rootp" "int (*compar) (const void *, const void *)" .Ft void -.Fn twalk "const void *root" "void (*action) (const void *, VISIT, int)" +.Fn twalk "const posix_tnode *root" "void (*action) (const posix_tnode *, VISIT, int)" .Sh DESCRIPTION The .Fn tdelete , @@ -134,3 +134,18 @@ function returns no value. .Xr bsearch 3 , .Xr hsearch 3 , .Xr lsearch 3 +.Sh STANDARDS +These functions conform to +.St -p1003.1-2008 . +.Pp +The +.Fa posix_tnode +type is not part of +.St -p1003.1-2008 , +but it is expected to be standardized by future versions of the standard. +It is defined as +.Fa void +for source-level compatibility. +Using +.Fa posix_tnode +makes it easier to distinguish between nodes and keys. diff --git a/lib/libc/stdlib/tsearch.c b/lib/libc/stdlib/tsearch.c index b96a27562670..a15c2c20517b 100644 --- a/lib/libc/stdlib/tsearch.c +++ b/lib/libc/stdlib/tsearch.c @@ -32,18 +32,17 @@ __FBSDID("$FreeBSD$"); #include "tsearch_path.h" -void * -tsearch(const void *key, void **rootp, +posix_tnode * +tsearch(const void *key, posix_tnode **rootp, int (*compar)(const void *, const void *)) { struct path path; - node_t *root, **base, **leaf, *result, *n, *x, *y, *z; + posix_tnode **leaf, *result, *n, *x, *y, *z; int cmp; /* POSIX requires that tsearch() returns NULL if rootp is NULL. */ if (rootp == NULL) - return (NULL); - root = *rootp; + return (NULL); /* * Find the leaf where the new key needs to be inserted. Return @@ -52,8 +51,7 @@ tsearch(const void *key, void **rootp, * balances. */ path_init(&path); - base = &root; - leaf = &root; + leaf = rootp; while (*leaf != NULL) { if ((*leaf)->balance != 0) { /* @@ -62,9 +60,9 @@ tsearch(const void *key, void **rootp, * need to perform any rotations above this * point. In this case rotations are always * capable of keeping the subtree in balance. - * Make this the base node and reset the path. + * Make this the root node and reset the path. */ - base = leaf; + rootp = leaf; path_init(&path); } cmp = compar(key, (*leaf)->key); @@ -75,7 +73,7 @@ tsearch(const void *key, void **rootp, path_taking_right(&path); leaf = &(*leaf)->rlink; } else { - return (&(*leaf)->key); + return (*leaf); } } @@ -94,7 +92,7 @@ tsearch(const void *key, void **rootp, * have a balance of zero, meaning that these nodes will not get * out of balance. */ - for (n = *base; n != *leaf;) { + for (n = *rootp; n != *leaf;) { if (path_took_left(&path)) { n->balance += 1; n = n->llink; @@ -106,10 +104,10 @@ tsearch(const void *key, void **rootp, /* * Adjusting the balances may have pushed the balance of the - * base node out of range. Perform a rotation to bring the + * root node out of range. Perform a rotation to bring the * balance back in range. */ - x = *base; + x = *rootp; if (x->balance > 1) { y = x->llink; if (y->balance < 0) { @@ -129,7 +127,7 @@ tsearch(const void *key, void **rootp, z->llink = y; x->llink = z->rlink; z->rlink = x; - *base = z; + *rootp = z; x->balance = z->balance > 0 ? -1 : 0; y->balance = z->balance < 0 ? 1 : 0; @@ -146,7 +144,7 @@ tsearch(const void *key, void **rootp, */ x->llink = y->rlink; y->rlink = x; - *base = y; + *rootp = y; x->balance = 0; y->balance = 0; @@ -165,12 +163,12 @@ tsearch(const void *key, void **rootp, * / \ A B C D * B C */ - node_t *z = y->llink; + posix_tnode *z = y->llink; x->rlink = z->llink; z->llink = x; y->llink = z->rlink; z->rlink = y; - *base = z; + *rootp = z; x->balance = z->balance < 0 ? 1 : 0; y->balance = z->balance > 0 ? -1 : 0; @@ -187,7 +185,7 @@ tsearch(const void *key, void **rootp, */ x->rlink = y->llink; y->llink = x; - *base = y; + *rootp = y; x->balance = 0; y->balance = 0; @@ -195,6 +193,5 @@ tsearch(const void *key, void **rootp, } /* Return the new entry. */ - *rootp = root; - return (&result->key); + return (result); } diff --git a/lib/libc/stdlib/twalk.c b/lib/libc/stdlib/twalk.c index 7acee414d11f..4f999b4c0a48 100644 --- a/lib/libc/stdlib/twalk.c +++ b/lib/libc/stdlib/twalk.c @@ -4,8 +4,6 @@ * Tree search generalized from Knuth (6.2.2) Algorithm T just like * the AT&T man page says. * - * The node_t structure is for internal use only, lint doesn't grok it. - * * Written by reading the System V Interface Definition, not the code. * * Totally public domain. @@ -23,12 +21,11 @@ __FBSDID("$FreeBSD$"); #include #include -typedef void (*cmp_fn_t)(const void *, VISIT, int); +typedef void (*cmp_fn_t)(const posix_tnode *, VISIT, int); /* Walk the nodes of a tree */ static void -trecurse(const node_t *root, /* Root of the tree to be walked */ - cmp_fn_t action, int level) +trecurse(const posix_tnode *root, cmp_fn_t action, int level) { if (root->llink == NULL && root->rlink == NULL) @@ -46,7 +43,7 @@ trecurse(const node_t *root, /* Root of the tree to be walked */ /* Walk the nodes of a tree */ void -twalk(const void *vroot, cmp_fn_t action) /* Root of the tree to be walked */ +twalk(const posix_tnode *vroot, cmp_fn_t action) { if (vroot != NULL && action != NULL) trecurse(vroot, action, 0); diff --git a/lib/libc/tests/stdlib/tsearch_test.c b/lib/libc/tests/stdlib/tsearch_test.c index b08fd94bc63e..88c62f580336 100644 --- a/lib/libc/tests/stdlib/tsearch_test.c +++ b/lib/libc/tests/stdlib/tsearch_test.c @@ -34,7 +34,7 @@ __FBSDID("$FreeBSD$"); /* Validates the integrity of an AVL tree. */ static inline unsigned int -tnode_assert(const node_t *n) +tnode_assert(const posix_tnode *n) { unsigned int height_left, height_right; int balance; @@ -79,7 +79,7 @@ ATF_TC_BODY(tsearch_test, tc) keys[i] = i; /* Apply random operations on a binary tree and check the results. */ - void *root = NULL; + posix_tnode *root = NULL; bool present[NKEYS] = {}; for (int i = 0; i < NKEYS * 10; ++i) { int key = nrand48(random_state) % NKEYS;