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: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1209>
This commit is contained in:
Peter Hutterer 2025-05-28 14:05:43 +10:00 committed by Marge Bot
parent 57a0a219b0
commit ec9c39da18
3 changed files with 231 additions and 0 deletions

View file

@ -30,6 +30,34 @@
#include <stdbool.h>
#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)
{

View file

@ -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.
*

View file

@ -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);