From: James Jones Date: Fri, 19 Feb 2021 12:32:08 +0000 (-0600) Subject: Add pre-order and post-order rbtree iterators. (#3945) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=61611a3c3a409fc67673f9854bd079db9b78ccdd;p=thirdparty%2Ffreeradius-server.git Add pre-order and post-order rbtree iterators. (#3945) The test we have is based on static test data with the values appropriate for pre and post iterators determined by hand. We can generate additional test cases as desired. --- diff --git a/src/lib/util/rbtree.c b/src/lib/util/rbtree.c index 3a4458eb282..8932aa0a3f9 100644 --- a/src/lib/util/rbtree.c +++ b/src/lib/util/rbtree.c @@ -946,6 +946,182 @@ void *rbtree_iter_next_inorder(fr_rb_tree_iter_t *iter) return x->data; } +/** Initialise a pre-order iterator + * + * @note If iteration ends early because of a loop condition #rbtree_iter_done must be called. + * + * @param[out] iter to initialise. + * @param[in] tree to iterate over. + * @return + * - The first node. Mutex will be held. + * - NULL if the tree is empty. + */ +void *rbtree_iter_init_preorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) +{ + fr_rb_node_t *x = tree->root; + + if (x == NIL) return NULL; + + if (tree->lock) pthread_mutex_lock(&tree->mutex); + + /* + * First, the root. + */ + *iter = (fr_rb_tree_iter_t){ + .tree = tree, + .node = x + }; + + return x->data; +} + +/** Return the next node + * + * @note Will unlock the tree if no more elements remain. + * + * @param[in] iter previously initialised with #rbtree_iter_init + * @return + * - The next node. + * - NULL if no more nodes remain. + */ +void *rbtree_iter_next_preorder(fr_rb_tree_iter_t *iter) +{ + fr_rb_node_t *x = iter->node, *y; + + /* + * Catch callers repeatedly calling iterator + * at the end. + */ + if (unlikely(iter->node == NIL)) return NULL; + + /* + * Next is a child of the just-returned node, if it has one. + * (Left child first.) + */ + if (x->left != NIL) { + x = x->left; + iter->node = x; + return x->data; + } + if (x->right != NIL) { + x = x->right; + iter->node = x; + return x->data; + } + + /* + * Otherwise, the nearest ancestor's unreturned right + * child, if one exists. + */ + for (; (y = x->parent) != NIL; x = y) { + if (y->right != NIL && y->right != x) { + x = y->right; + iter->node = x; + return x->data; + } + } + + /* + * None of the above? We're done. + */ + iter->node = NIL; + if (iter->tree->lock) + pthread_mutex_unlock(&iter->tree->mutex); + return NULL; +} + +/** Initialise a pre-order iterator + * + * @note If iteration ends early because of a loop condition #rbtree_iter_done must be called. + * + * @param[out] iter to initialise. + * @param[in] tree to iterate over. + * @return + * - The first node. Mutex will be held. + * - NULL if the tree is empty. + */ +void *rbtree_iter_init_postorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) +{ + fr_rb_node_t *x = tree->root; + + if (x == NIL) return NULL; + + if (tree->lock) pthread_mutex_lock(&tree->mutex); + + /* + * First: the deepest leaf to the left (jogging to the + * right if there's a right child but no left). + */ + for (;;) { + for (; x->left != NIL; x = x->left) ; + if (x->right == NIL) break; + x = x->right; + } + + *iter = (fr_rb_tree_iter_t){ + .tree = tree, + .node = x + }; + + return x->data; +} + +/** Return the next node + * + * @note Will unlock the tree if no more elements remain. + * + * @param[in] iter previously initialised with #rbtree_iter_init + * @return + * - The next node. + * - NULL if no more nodes remain. + */ +void *rbtree_iter_next_postorder(fr_rb_tree_iter_t *iter) +{ + fr_rb_node_t *x = iter->node, *y; + + /* + * Catch callers repeatedly calling iterator + * at the end. + */ + if (unlikely(iter->node == NIL)) return NULL; + + /* + * This is postorder, so a just-returned node's + * descendants have all been returned. If there + * is another node, it's an ancestor or one of + * its not-yet-returned descendants...but if + * we're at the root, we're done. + */ + y = x->parent; + if (y == NIL) { + iter->node = NIL; + if (iter->tree->lock) pthread_mutex_unlock(&iter->tree->mutex); + return NULL; + } + + /* + * Return the parent if it has no right child, or it has one but + * it's been returned. + */ + if (y->right == NIL || y->right == x) { + iter->node = y; + return y->data; + } + + /* + * Otherwise, it's as if we're starting over with the right child. + */ + x = y->right; + for (;;) { + for (; x->left != NIL; x = x->left) ; + if (x->right == NIL) break; + x = x->right; + } + + iter->node = x; + return x->data; +} + /** Explicitly unlock the tree * * @note Must be called if iterating over the tree ends early. diff --git a/src/lib/util/rbtree.h b/src/lib/util/rbtree.h index de7d5bde1b8..9aa27f5dd78 100644 --- a/src/lib/util/rbtree.h +++ b/src/lib/util/rbtree.h @@ -177,6 +177,14 @@ void *rbtree_iter_init_inorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT void *rbtree_iter_next_inorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull); +void *rbtree_iter_init_preorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT(nonnull); + +void *rbtree_iter_next_preorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull); + +void *rbtree_iter_init_postorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT(nonnull); + +void *rbtree_iter_next_postorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull); + void rbtree_iter_done(fr_rb_tree_iter_t *iter) CC_HINT(nonnull); #ifdef __cplusplus diff --git a/src/lib/util/rbtree_tests.c b/src/lib/util/rbtree_tests.c index edbd33306ed..fd8803f51ff 100644 --- a/src/lib/util/rbtree_tests.c +++ b/src/lib/util/rbtree_tests.c @@ -40,7 +40,7 @@ static int fr_rb_test_cmp(void const *one, void const *two) return CMP(a->num, b->num); } -static void test_rbtree_iter(void) +static void test_rbtree_iter_inorder(void) { rbtree_t *t; fr_rb_test_node_t sorted[MAXSIZE]; @@ -48,6 +48,7 @@ static void test_rbtree_iter(void) size_t n, i; fr_rb_tree_iter_t iter; + TEST_CASE("in-order iterator"); t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK); TEST_CHECK(t != NULL); @@ -80,8 +81,87 @@ static void test_rbtree_iter(void) talloc_free(t); } +/* + * There's no natural test for pre- and post-order traversal + * as there is for in-order, so we must content ourselves + * with static test data. + */ +static uint32_t pre_post_input[] = {0, 15, 256, 49, 3, 8192, 144, 4, 4096, 25194}; +static uint32_t pre_output[] = {15, 3, 0, 4, 256, 49, 144, 8192, 4096, 25194}; +static uint32_t post_output[] = {0, 4, 3, 144, 49, 4096, 25194, 8192, 256, 15}; + +static void test_rbtree_iter_preorder(void) +{ + rbtree_t *t; + fr_rb_test_node_t *p; + size_t i; + fr_rb_tree_iter_t iter; + + TEST_CASE("pre-order iterator"); + /* + * Build a tree from pre_post_input. + */ + t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK); + TEST_CHECK(t != NULL); + + for (i = 0; i < sizeof(pre_post_input) / sizeof(uint32_t); i++) { + p = talloc(t, fr_rb_test_node_t); + p->num = pre_post_input[i]; + rbtree_insert(t, p); + } + + for (p = rbtree_iter_init_preorder(&iter, t), i = 0; + p; + p = rbtree_iter_next_preorder(&iter), i++) { + TEST_MSG("Checking pre_output[%zu] = %u vs n = %u", i, pre_output[i], p->num); + TEST_CHECK(pre_output[i] == p->num); + TEST_CHECK(pthread_mutex_trylock(&t->mutex) != 0); /* Lock still held */ + } + + TEST_CHECK(pthread_mutex_trylock(&t->mutex) == 0); /* Lock released */ + pthread_mutex_unlock(&t->mutex); + + talloc_free(t); +} + +static void test_rbtree_iter_postorder(void) +{ + rbtree_t *t; + fr_rb_test_node_t *p; + size_t i; + fr_rb_tree_iter_t iter; + + TEST_CASE("post-order iterator"); + /* + * Build a tree from pre_post_input. + */ + t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK); + TEST_CHECK(t != NULL); + + for (i = 0; i < sizeof(pre_post_input) / sizeof(uint32_t); i++) { + p = talloc(t, fr_rb_test_node_t); + p->num = pre_post_input[i]; + rbtree_insert(t, p); + } + + for (p = rbtree_iter_init_postorder(&iter, t), i = 0; + p; + p = rbtree_iter_next_postorder(&iter), i++) { + TEST_MSG("Checking post_output[%zu] s = %u vs n = %u", i, post_output[i], p->num); + TEST_CHECK(post_output[i] == p->num); + TEST_CHECK(pthread_mutex_trylock(&t->mutex) != 0); /* Lock still held */ + } + + TEST_CHECK(pthread_mutex_trylock(&t->mutex) == 0); /* Lock released */ + pthread_mutex_unlock(&t->mutex); + + talloc_free(t); +} + TEST_LIST = { - { "rbtree_iter", test_rbtree_iter }, + { "rbtree_iter_inorder", test_rbtree_iter_inorder }, + { "rbtree_iter_preorder", test_rbtree_iter_preorder }, + { "rbtree_iter_postorder", test_rbtree_iter_postorder }, { NULL } };