commonlib/list: Add list_length() and more to API

In a follow-up patch (CB:90962), the list will be changed to a circular
one, and list_node fields 'next' and 'prev' will become private to the
implementation.

To allow smooth transition to circular lists for all call sites, add the
following functions to the list API:

- list_is_empty()
- list_next()
- list_prev()
- list_first()
- list_last()
- list_length()

All list API call sites are expected to use the public API instead of
the raw 'next' and 'prev' pointers.

Change-Id: Ib1040f5caab8550ea52db9b55a074d7d79c591e5
Signed-off-by: Yu-Ping Wu <yupingso@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/90961
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Jakub "Kuba" Czapiga <czapiga@google.com>
Reviewed-by: Julius Werner <jwerner@chromium.org>
This commit is contained in:
Yu-Ping Wu 2026-01-28 12:30:50 +08:00 committed by Matt DeVillier
commit e50f7e8b49
3 changed files with 120 additions and 37 deletions

View file

@ -5,6 +5,7 @@
#define __COMMONLIB_LIST_H__
#include <commonlib/helpers.h>
#include <stdbool.h>
#include <stdint.h>
struct list_node {
@ -18,9 +19,41 @@ void list_remove(struct list_node *node);
void list_insert_after(struct list_node *node, struct list_node *after);
// Insert list_node node before list_node before in a doubly linked list.
void list_insert_before(struct list_node *node, struct list_node *before);
// Appends the node to the end of the list.
// Append the node to the end of the list.
void list_append(struct list_node *node, struct list_node *head);
// Return if the list is empty.
static inline bool list_is_empty(const struct list_node *head)
{
return !head->next;
}
// Get next node.
static inline const struct list_node *list_next(const struct list_node *node,
const struct list_node *head)
{
return node->next;
};
// Get prev node.
static inline const struct list_node *list_prev(const struct list_node *node,
const struct list_node *head)
{
return node->prev == head ? NULL : node->prev;
};
// Get first node.
static inline const struct list_node *list_first(const struct list_node *head)
{
return list_is_empty(head) ? NULL : head->next;
}
// Get last node.
const struct list_node *list_last(const struct list_node *head);
// Get the number of list elements.
size_t list_length(const struct list_node *head);
#define list_for_each(ptr, head, member) \
for ((ptr) = container_of((head).next, typeof(*(ptr)), member); \
(uintptr_t)ptr + (uintptr_t)offsetof(typeof(*(ptr)), member); \

View file

@ -36,3 +36,24 @@ void list_append(struct list_node *node, struct list_node *head)
list_insert_after(node, head);
}
const struct list_node *list_last(const struct list_node *head)
{
const struct list_node *ptr = head;
while (ptr->next)
ptr = ptr->next;
return ptr == head ? NULL : ptr;
}
size_t list_length(const struct list_node *head)
{
struct {
struct list_node node;
} const *ptr;
size_t len = 0;
list_for_each(ptr, *head, node)
len++;
return len;
}

View file

@ -1,9 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <tests/test.h>
#include <commonlib/list.h>
#include <stdlib.h>
#include <string.h>
#include <commonlib/list.h>
#include <tests/test.h>
#include <types.h>
struct test_container {
int value;
@ -11,10 +12,40 @@ struct test_container {
struct list_node list_node;
};
void test_list_insert_after(void **state)
static void test_list_empty(void **state)
{
struct list_node head = {};
struct test_container *node;
list_for_each(node, head, list_node) {
assert_true(false);
}
assert_true(list_is_empty(&head));
assert_null(list_first(&head));
assert_null(list_last(&head));
assert_int_equal(0, list_length(&head));
}
static void test_list_one_node(void **state)
{
struct list_node head = {};
struct test_container c = { .value = 1 };
list_insert_after(&c.list_node, &head);
assert_false(list_is_empty(&head));
assert_null(list_next(&c.list_node, &head));
assert_null(list_prev(&c.list_node, &head));
assert_ptr_equal(&c.list_node, list_first(&head));
assert_ptr_equal(&c.list_node, list_last(&head));
assert_int_equal(1, list_length(&head));
}
static void test_list_insert_after(void **state)
{
int i = 0;
struct list_node root = { .prev = NULL, .next = NULL };
struct list_node head = { .prev = NULL, .next = NULL };
struct test_container *c1 = (struct test_container *)malloc(sizeof(*c1));
struct test_container *c2 = (struct test_container *)malloc(sizeof(*c2));
struct test_container *c3 = (struct test_container *)malloc(sizeof(*c2));
@ -29,26 +60,33 @@ void test_list_insert_after(void **state)
c2->value = values[1];
c3->value = values[2];
list_insert_after(&c1->list_node, &root);
list_insert_after(&c1->list_node, &head);
list_insert_after(&c2->list_node, &c1->list_node);
list_insert_after(&c3->list_node, &c2->list_node);
list_for_each(ptr, root, list_node) {
list_for_each(ptr, head, list_node) {
assert_int_equal(values[i], ptr->value);
i++;
}
assert_int_equal(3, i);
assert_int_equal(3, list_length(&head));
assert_false(list_is_empty(&head));
assert_ptr_equal(&c3->list_node, list_next(&c2->list_node, &head));
assert_ptr_equal(&c1->list_node, list_prev(&c2->list_node, &head));
assert_ptr_equal(&c1->list_node, list_first(&head));
assert_ptr_equal(&c3->list_node, list_last(&head));
free(c3);
free(c2);
free(c1);
}
void test_list_insert_before(void **state)
static void test_list_insert_before(void **state)
{
int i = 0;
struct list_node root = { .prev = NULL, .next = NULL };
struct list_node head = { .prev = NULL, .next = NULL };
struct test_container *c1 = (struct test_container *)malloc(sizeof(*c1));
struct test_container *c2 = (struct test_container *)malloc(sizeof(*c2));
struct test_container *c3 = (struct test_container *)malloc(sizeof(*c2));
@ -63,87 +101,78 @@ void test_list_insert_before(void **state)
c2->value = values[1];
c3->value = values[2];
list_insert_after(&c3->list_node, &root);
list_insert_after(&c3->list_node, &head);
list_insert_before(&c2->list_node, &c3->list_node);
list_insert_before(&c1->list_node, &c2->list_node);
list_for_each(ptr, root, list_node) {
list_for_each(ptr, head, list_node) {
assert_int_equal(values[i], ptr->value);
i++;
}
assert_int_equal(3, i);
assert_int_equal(3, list_length(&head));
assert_false(list_is_empty(&head));
free(c3);
free(c2);
free(c1);
}
void test_list_remove(void **state)
static void test_list_remove(void **state)
{
struct list_node root = { .prev = NULL, .next = NULL };
struct list_node head = { .prev = NULL, .next = NULL };
struct test_container *c1 = (struct test_container *)malloc(sizeof(*c1));
struct test_container *c2 = (struct test_container *)malloc(sizeof(*c2));
struct test_container *ptr;
int len;
list_insert_after(&c1->list_node, &root);
list_insert_after(&c1->list_node, &head);
list_insert_after(&c2->list_node, &c1->list_node);
len = 0;
list_for_each(ptr, root, list_node) {
len++;
}
assert_int_equal(2, len);
assert_int_equal(2, list_length(&head));
list_remove(&c1->list_node);
len = 0;
list_for_each(ptr, root, list_node) {
len++;
}
assert_int_equal(1, len);
assert_int_equal(1, list_length(&head));
list_remove(&c2->list_node);
len = 0;
list_for_each(ptr, root, list_node) {
len++;
}
assert_int_equal(0, len);
assert_int_equal(0, list_length(&head));
free(c2);
free(c1);
}
void test_list_append(void **state)
static void test_list_append(void **state)
{
size_t idx;
struct test_container *node;
struct list_node root = {};
struct list_node head = {};
struct test_container nodes[] = {
{1}, {2}, {3}
};
for (idx = 0; idx < ARRAY_SIZE(nodes); ++idx)
list_append(&nodes[idx].list_node, &root);
list_append(&nodes[idx].list_node, &head);
idx = 0;
list_for_each(node, root, list_node) {
list_for_each(node, head, list_node) {
assert_ptr_equal(node, &nodes[idx]);
idx++;
}
assert_int_equal(3, idx);
assert_int_equal(3, list_length(&head));
}
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_list_empty),
cmocka_unit_test(test_list_one_node),
cmocka_unit_test(test_list_insert_after),
cmocka_unit_test(test_list_insert_before),
cmocka_unit_test(test_list_remove),
cmocka_unit_test(test_list_append),
};
return cb_run_group_tests(tests, NULL, NULL);
}