From ec9c39da18e08f8e9502baaf1679518c95ff695c Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 28 May 2025 14:05:43 +1000 Subject: [PATCH] util: add a number of list helpers The list_debug function is ifdef'd out, it'll be useful to debug whenever we have some list corruption. Part-of: --- src/util-list.c | 67 +++++++++++++++++++++++++++ src/util-list.h | 52 +++++++++++++++++++++ test/test-utils.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) diff --git a/src/util-list.c b/src/util-list.c index 900172d2..99498c88 100644 --- a/src/util-list.c +++ b/src/util-list.c @@ -30,6 +30,34 @@ #include #include "util-list.h" +#include "libinput-util.h" + +#if 0 +static void +list_debug(struct list *list) +{ + struct list *head = list; + + assert((head->next != NULL && head->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + trace("list %p:", head); + trace(" head->next %p", head->next); + trace(" head->prev %p", head->prev); + struct list *elm = head->next; + int index = 0; + while (elm != head) { + trace("..%-3d: %p->next %p", + index, elm, elm->next); + trace("..%-3d: %p->prev %p", + index, elm, elm->prev); + elm = elm->next; + index++; + if (index > 20) + break; + } +} +#endif void list_init(struct list *list) @@ -65,6 +93,45 @@ list_append(struct list *list, struct list *elm) elm->prev->next = elm; } +void +list_chain(struct list *list, struct list *other) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert((other->next != NULL && other->prev != NULL) || + !"other->next|prev is NULL, possibly missing list_init()"); + + if (list_empty(other)) + return; + + struct list *last = list->prev; + struct list *first = other->next; + + first->prev = last; + last->next = first; + + list->prev->next = other->next; + other->next->prev = list->prev; + other->prev->next = list; + list->prev = other->prev; + list_init(other); +} + +size_t +list_length(const struct list *list) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + size_t count = 0; + const struct list *elm; + + for (elm = list->next; elm != list; elm = elm->next) + count++; + + return count; +} + void list_remove(struct list *elm) { diff --git a/src/util-list.h b/src/util-list.h index 88b39832..d04f57b9 100644 --- a/src/util-list.h +++ b/src/util-list.h @@ -80,6 +80,13 @@ void list_insert(struct list *list, struct list *elm); */ void list_append(struct list *list, struct list *elm); +/** + * Chain other onto list, resetting other to be the empty list. + */ +void list_chain(struct list *list, struct list *other); + +size_t list_length(const struct list *list); + /** * Takes the given pointer ands inserts it to the list with the pointer's field. * The pointer is reset to NULL. Use this to prevent automatic cleanup @@ -187,6 +194,51 @@ bool list_empty(const struct list *list); #define list_first_entry_by_type(head, container_type, member) \ container_of((head)->next, container_type, member) +/** + * Given a list 'head', return the last entry of type 'pos' that has a + * member 'link'. + * + * The 'pos' argument is solely used to determine the type be returned and + * not modified otherwise. It is common to use the same pointer that the + * return value of list_last_entry() is assigned to, for example: + * + * @code + * struct foo { + * struct list list_of_bars; + * }; + * + * struct bar { + * struct list link; + * } + * + * struct foo *f = get_a_foo(); + * struct bar *b = 0; // initialize to avoid static analysis errors + * b = list_last_entry(&f->list_of_bars, b, link); + * @endcode + */ +#define list_last_entry(head, pointer_of_type, member) \ + container_of((head)->prev, __typeof__(*pointer_of_type), member) + +/** + * Given a list 'head', return the last entry of type 'container_type' that + * has a member 'link'. + * + * @code + * struct foo { + * struct list list_of_bars; + * }; + * + * struct bar { + * struct list link; + * } + * + * struct foo *f = get_a_foo(); + * struct bar *b = list_last_entry(&f->list_of_bars, struct bar, link); + * @endcode + */ +#define list_last_entry_by_type(head, container_type, member) \ + container_of((head)->prev, container_type, member) + /** * Iterate through the list. * diff --git a/test/test-utils.c b/test/test-utils.c index 4c746779..f823b363 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -1862,6 +1862,57 @@ START_TEST(list_test_append) } END_TEST +START_TEST(list_test_chain) +{ + struct list_test { + int val; + struct list node; + } tests[] = { + { .val = 1 }, + { .val = 2 }, + { .val = 3 }, + { .val = 4 }, + }; + struct list l1, l2; + struct list_test *t; + int val; + + list_init(&l1); + list_init(&l2); + + list_chain(&l1, &l2); + litest_assert(list_empty(&l2)); + + list_append(&l2, &tests[0].node); + list_append(&l2, &tests[1].node); + list_chain(&l1, &l2); + litest_assert(list_empty(&l2)); + + val = 1; + list_for_each_safe(t, &l1, node) { + litest_assert_int_eq(t->val, val); + val++; + list_remove(&t->node); + } + litest_assert_int_eq(val, 3); + + list_append(&l1, &tests[0].node); + list_append(&l1, &tests[1].node); + list_append(&l2, &tests[2].node); + list_append(&l2, &tests[3].node); + + list_chain(&l1, &l2); + litest_assert(list_empty(&l2)); + + val = 1; + list_for_each(t, &l1, node) { + litest_assert_int_eq(t->val, val); + val++; + } + litest_assert_int_eq(val, 5); +} +END_TEST + START_TEST(list_test_foreach) { struct list_test { @@ -1895,6 +1946,65 @@ START_TEST(list_test_foreach) } END_TEST +START_TEST(list_test_first_last) +{ + struct list_test { + int val; + struct list node; + } tests[] = { + { .val = 1 }, + { .val = 2 }, + { .val = 3 }, + { .val = 4 }, + }; + struct list head; + + list_init(&head); + + ARRAY_FOR_EACH(tests, t) { + list_append(&head, &t->node); + } + + struct list_test *first; + struct list_test *last; + + first = list_first_entry(&head, first, node); + last = list_last_entry(&head, last, node); + litest_assert_ptr_eq(first, &tests[0]); + litest_assert_ptr_eq(last, &tests[3]); + + struct list_test *second; + struct list_test *penultimate; + + second = list_first_entry(&first->node, first, node); + penultimate = list_last_entry(&last->node, last, node); + litest_assert_ptr_eq(second, &tests[1]); + litest_assert_ptr_eq(penultimate, &tests[2]); + + /* Now remove nodes */ + + /* No change expected */ + list_remove(&tests[2].node); + first = list_first_entry(&head, first, node); + last = list_last_entry(&head, last, node); + litest_assert_ptr_eq(first, &tests[0]); + litest_assert_ptr_eq(last, &tests[3]); + + list_remove(&tests[3].node); + first = list_first_entry(&head, first, node); + last = list_last_entry(&head, last, node); + litest_assert_ptr_eq(first, &tests[0]); + litest_assert_ptr_eq(last, &tests[1]); + + list_remove(&tests[0].node); + first = list_first_entry(&head, first, node); + last = list_last_entry(&head, last, node); + litest_assert_ptr_eq(first, &tests[1]); + litest_assert_ptr_eq(last, &tests[1]); + +} +END_TEST + START_TEST(strverscmp_test) { litest_assert_int_eq(libinput_strverscmp("", ""), 0); @@ -2507,6 +2617,8 @@ int main(void) ADD_TEST(list_test_insert); ADD_TEST(list_test_append); ADD_TEST(list_test_foreach); + ADD_TEST(list_test_first_last); + ADD_TEST(list_test_chain); ADD_TEST(strverscmp_test); ADD_TEST(streq_test); ADD_TEST(strneq_test);