From 8c56bfb8c06db7eae394662753955a04e08910b6 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 14 Nov 2024 11:43:32 +0100 Subject: [PATCH 1/2] Squashed 'third_party/c-list/' content from commit 9aa81d84cadc git-subtree-dir: third_party/c-list git-subtree-split: 9aa81d84cadc67e92b441c89f84c57e72dd1e8a9 --- .editorconfig | 11 + .github/workflows/ci.yml | 32 +++ AUTHORS | 41 ++++ NEWS.md | 77 ++++++ README.md | 52 ++++ meson.build | 46 ++++ src/c-list.h | 504 +++++++++++++++++++++++++++++++++++++++ src/meson.build | 34 +++ src/test-api.c | 144 +++++++++++ src/test-basic.c | 319 +++++++++++++++++++++++++ src/test-embed.c | 173 ++++++++++++++ 11 files changed, 1433 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 AUTHORS create mode 100644 NEWS.md create mode 100644 README.md create mode 100644 meson.build create mode 100644 src/c-list.h create mode 100644 src/meson.build create mode 100644 src/test-api.c create mode 100644 src/test-basic.c create mode 100644 src/test-embed.c diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b10bb4f3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{c,h}] +indent_style = space +indent_size = 8 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..00bca960 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Continuous Integration + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + ci-linux: + name: Linux CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: true + m32: true + matrixmode: true + valgrind: true + ci-macos: + name: MacOS CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: false + macos: true + ci-windows: + name: Windows CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: false + windows: true diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..76dea872 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,41 @@ +LICENSE: + This project is dual-licensed under both the Apache License, Version + 2.0, and the GNU Lesser General Public License, Version 2.1+. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2015-2022 Red Hat, Inc. + +AUTHORS: (ordered alphabetically) + Danilo Horta + David Rheinsberg + Lucas De Marchi + Michele Dionisio + Thomas Haller + Tom Gundersen diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 00000000..095f1ea7 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,77 @@ +# c-list - Circular Intrusive Double Linked List Collection + +## CHANGES WITH 3.1.0: + + * The minimum required meson version is now 0.60.0. + + * New function c_list_split() is added. It reverses c_list_splice() + and thus allows to split a list in half. + + Contributions from: David Rheinsberg, Michele Dionisio + + - Brno, 2022-06-22 + +## CHANGES WITH 3: + + * API break: The c_list_loop_*() symbols were removed, since we saw + little use for them. No user was known at the time, so + all projects should build with the new API version + unchanged. + Since c-list does not distribute any compiled code, there + is no ABI issue with this change. + + * Two new symbols c_list_length() and c_list_contains(). They are meant + for debugging purposes, to easily verify list integrity. Since they + run in O(n) time, they are not recommended for any other use than + debugging. + + * New symbol c_list_init() is provided as alternative to the verbose + C_LIST_INIT assignment. + + * The c-list API is extended to work well with `const CList` objects. + That is, any read-only accessor function allows constant objects as + input now. + Note that this does not propagate into other members linked in the + list. Using `const` for CList members is of little practical use. + However, it might be of use for its embedding objects, so we now + allow it in the CList API as well. + + * The c_list_splice() call now clears the source list, rather than + returning with stale pointers. Technically, this is also an API + break, but unlikely to affect any existing code. + + Contributions from: David Herrmann, Thomas Haller + + - Berlin, 2017-08-13 + +## CHANGES WITH 2: + + * Adjust project-name in build-system to reflect the actual project. The + previous releases incorrectly claimed to be c-rbtree in the build + system. + + * Add c_list_swap() that swaps two lists given their head pointers. + + * Add c_list_splice() that moves a list. + + * Add LGPL2.1+ as license so c-list can be imported into GPL2 projects. + It is now officially dual-licensed. + + * As usual a bunch of fixes, additional tests, and documentation + updates. + + Contributions from: David Herrmann, Tom Gundersen + + - Lund, 2017-05-03 + +## CHANGES WITH 1: + + * Initial release of c-list. + + * This project provides an implementation of a circular double linked + list in standard ISO-C11. License is ASL-2.0 and the build system + used is `Meson'. + + Contributions from: David Herrmann, Tom Gundersen + + - Berlin, 2017-03-03 diff --git a/README.md b/README.md new file mode 100644 index 00000000..a2e47528 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +c-list +====== + +Circular Intrusive Double Linked List Collection + +The c-list project implements an intrusive collection based on circular double +linked lists in ISO-C11. It aims for minimal API constraints, leaving maximum +control over the data-structures to the API consumer. + +### Project + + * **Website**: + * **Bug Tracker**: + +### Requirements + +The requirements for this project are: + + * `libc` (e.g., `glibc >= 2.16`) + +At build-time, the following software is required: + + * `meson >= 0.60` + * `pkg-config >= 0.29` + +### Build + +The meson build-system is used for this project. Contact upstream +documentation for detailed help. In most situations the following +commands are sufficient to build and install from source: + +```sh +mkdir build +cd build +meson setup .. +ninja +meson test +ninja install +``` + +No custom configuration options are available. + +### Repository: + + - **web**: + - **https**: `https://github.com/c-util/c-list.git` + - **ssh**: `git@github.com:c-util/c-list.git` + +### License: + + - **Apache-2.0** OR **LGPL-2.1-or-later** + - See AUTHORS file for details. diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..b5ef78e6 --- /dev/null +++ b/meson.build @@ -0,0 +1,46 @@ +project( + 'c-list', + 'c', + default_options: [ + 'c_std=c99', + ], + license: 'Apache', + meson_version: '>=0.60.0', + version: '3.1.0', +) +major = meson.project_version().split('.')[0] +project_description = 'Circular Intrusive Double Linked List Collection' + +mod_pkgconfig = import('pkgconfig') + +# See c-stdaux for details on these. We do not have c-stdaux as dependency, so +# we keep a duplicated set here, reduced to the minimum. +cflags = meson.get_compiler('c').get_supported_arguments( + '-D_GNU_SOURCE', + + '-Wno-gnu-alignof-expression', + '-Wno-maybe-uninitialized', + '-Wno-unknown-warning-option', + '-Wno-unused-parameter', + + '-Wno-error=type-limits', + '-Wno-error=missing-field-initializers', + + '-Wdate-time', + '-Wdeclaration-after-statement', + '-Wlogical-op', + '-Wmissing-include-dirs', + '-Wmissing-noreturn', + '-Wnested-externs', + '-Wredundant-decls', + '-Wshadow', + '-Wstrict-aliasing=3', + '-Wsuggest-attribute=noreturn', + '-Wundef', + '-Wwrite-strings', +) +add_project_arguments(cflags, language: 'c') + +subdir('src') + +meson.override_dependency('libclist-'+major, libclist_dep, static: true) diff --git a/src/c-list.h b/src/c-list.h new file mode 100644 index 00000000..711b54d6 --- /dev/null +++ b/src/c-list.h @@ -0,0 +1,504 @@ +#pragma once + +/* + * Circular Intrusive Double Linked List Collection in ISO-C11 + * + * This implements a generic circular double linked list. List entries must + * embed the CList object, which provides pointers to the next and previous + * element. Insertion and removal can be done in O(1) due to the double links. + * Furthermore, the list is circular, thus allows access to front/tail in O(1) + * as well, even if you only have a single head pointer (which is not how the + * list is usually operated, though). + * + * Note that you are free to use the list implementation without a head + * pointer. However, usual operation uses a single CList object as head, which + * is itself linked in the list and as such must be identified as list head. + * This allows very simply list operations and avoids a lot of special cases. + * Most importantly, you can unlink entries without requiring a head pointer. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct CList CList; + +/** + * struct CList - Entry of a circular double linked list + * @next: next entry + * @prev: previous entry + * + * Each entry in a list must embed a CList object. This object contains + * pointers to its next and previous elements, which can be freely accessed by + * the API user at any time. Note that the list is circular, and the list head + * is linked in the list as well. + * + * The list head must be initialized via C_LIST_INIT before use. There is no + * reason to initialize entry objects before linking them. However, if you need + * a boolean state that tells you whether the entry is linked or not, you should + * initialize the entry via C_LIST_INIT as well. + */ +struct CList { + CList *next; + CList *prev; +}; + +#define C_LIST_INIT(_var) { .next = &(_var), .prev = &(_var) } + +/** + * c_list_init() - initialize list entry + * @what: list entry to initialize + * + * Return: @what is returned. + */ +static inline CList *c_list_init(CList *what) { + *what = (CList)C_LIST_INIT(*what); + return what; +} + +/** + * c_list_entry_offset() - get parent container of list entry + * @what: list entry, or NULL + * @offset: offset of the list member in its surrounding type + * + * If the list entry @what is embedded into a surrounding structure, this will + * turn the list entry pointer @what into a pointer to the parent container + * (sometimes called container_of(3)). Use the `c_list_entry()` macro for an + * easier API. + * + * If @what is NULL, this will also return NULL. + * + * Return: Pointer to parent container, or NULL. + */ +static inline void *c_list_entry_offset(const CList *what, size_t offset) { + if (what) { + /* + * We allow calling "c_list_entry()" on the list head, which is + * commonly a plain CList struct. The returned entry pointer is + * thus invalid. For instance, this is used by the + * c_list_for_each_entry*() macros. Gcc correctly warns about that + * with "-Warray-bounds". However, as long as the value is never + * dereferenced, this is fine. We explicitly use integer arithmetic + * to circumvent the Gcc warning. + */ + return (void *)(((uintptr_t)(void *)what) - offset); + } + return NULL; +} + +/** + * c_list_entry() - get parent container of list entry + * @_what: list entry, or NULL + * @_t: type of parent container + * @_m: member name of list entry in @_t + * + * If the list entry @_what is embedded into a surrounding structure, this will + * turn the list entry pointer @_what into a pointer to the parent container + * (using offsetof(3), or sometimes called container_of(3)). + * + * If @_what is NULL, this will also return NULL. + * + * Return: Pointer to parent container, or NULL. + */ +#define c_list_entry(_what, _t, _m) \ + ((_t *)c_list_entry_offset((_what), offsetof(_t, _m))) + +/** + * c_list_is_linked() - check whether an entry is linked + * @what: entry to check, or NULL + * + * Return: True if @what is linked in a list, false if not. + */ +static inline _Bool c_list_is_linked(const CList *what) { + return what && what->next != what; +} + +/** + * c_list_is_empty() - check whether a list is empty + * @list: list to check, or NULL + * + * This is the same as !c_list_is_linked(). + * + * Return: True if @list is empty, false if not. + */ +static inline _Bool c_list_is_empty(const CList *list) { + return !c_list_is_linked(list); +} + +/** + * c_list_link_before() - link entry into list + * @where: linked list entry used as anchor + * @what: entry to link + * + * This links @what directly in front of @where. @where can either be a list + * head or any entry in the list. + * + * If @where points to the list head, this effectively links @what as new tail + * element. Hence, the macro c_list_link_tail() is an alias to this. + * + * @what is not inspected prior to being linked. Hence, it better not be linked + * into another list, or the other list will be corrupted. + */ +static inline void c_list_link_before(CList *where, CList *what) { + CList *prev = where->prev, *next = where; + + next->prev = what; + what->next = next; + what->prev = prev; + prev->next = what; +} +#define c_list_link_tail(_list, _what) c_list_link_before((_list), (_what)) + +/** + * c_list_link_after() - link entry into list + * @where: linked list entry used as anchor + * @what: entry to link + * + * This links @what directly after @where. @where can either be a list head or + * any entry in the list. + * + * If @where points to the list head, this effectively links @what as new front + * element. Hence, the macro c_list_link_front() is an alias to this. + * + * @what is not inspected prior to being linked. Hence, it better not be linked + * into another list, or the other list will be corrupted. + */ +static inline void c_list_link_after(CList *where, CList *what) { + CList *prev = where, *next = where->next; + + next->prev = what; + what->next = next; + what->prev = prev; + prev->next = what; +} +#define c_list_link_front(_list, _what) c_list_link_after((_list), (_what)) + +/** + * c_list_unlink_stale() - unlink element from list + * @what: element to unlink + * + * This unlinks @what. If @what was initialized via C_LIST_INIT(), it has no + * effect. If @what was never linked, nor initialized, behavior is undefined. + * + * Note that this does not modify @what. It just modifies the previous and next + * elements in the list to no longer reference @what. If you want to make sure + * @what is re-initialized after removal, use c_list_unlink(). + */ +static inline void c_list_unlink_stale(CList *what) { + CList *prev = what->prev, *next = what->next; + + next->prev = prev; + prev->next = next; +} + +/** + * c_list_unlink() - unlink element from list and re-initialize + * @what: element to unlink + * + * This is like c_list_unlink_stale() but re-initializes @what after removal. + */ +static inline void c_list_unlink(CList *what) { + /* condition is not needed, but avoids STOREs in fast-path */ + if (c_list_is_linked(what)) { + c_list_unlink_stale(what); + *what = (CList)C_LIST_INIT(*what); + } +} + +/** + * c_list_swap() - exchange the contents of two lists + * @list1: the list to operate on + * @list2: the list to operate on + * + * This replaces the contents of the list @list1 with the contents + * of @list2, and vice versa. + */ +static inline void c_list_swap(CList *list1, CList *list2) { + CList t; + + /* make neighbors of list1 point to list2, and vice versa */ + t = *list1; + t.next->prev = list2; + t.prev->next = list2; + t = *list2; + t.next->prev = list1; + t.prev->next = list1; + + /* swap list1 and list2 now that their neighbors were fixed up */ + t = *list1; + *list1 = *list2; + *list2 = t; +} + +/** + * c_list_splice() - splice one list into another + * @target: the list to splice into + * @source: the list to splice + * + * This removes all the entries from @source and splice them into @target. + * The order of the two lists is preserved and the source is appended + * to the end of target. + * + * On return, the source list will be empty. + */ +static inline void c_list_splice(CList *target, CList *source) { + if (!c_list_is_empty(source)) { + /* attach the front of @source to the tail of @target */ + source->next->prev = target->prev; + target->prev->next = source->next; + + /* attach the tail of @source to the front of @target */ + source->prev->next = target; + target->prev = source->prev; + + /* clear source */ + *source = (CList)C_LIST_INIT(*source); + } +} + +/** + * c_list_split() - split one list in two + * @source: the list to split + * @where: new starting element of newlist + * @target: new list + * + * This splits @source in two. All elements following @where (including @where) + * are moved to @target, replacing any old list. If @where points to @source + * (i.e., the end of the list), @target will be empty. + */ +static inline void c_list_split(CList *source, CList *where, CList *target) { + if (where == source) { + *target = (CList)C_LIST_INIT(*target); + } else { + target->next = where; + target->prev = source->prev; + + where->prev->next = source; + source->prev = where->prev; + + where->prev = target; + target->prev->next = target; + } +} + +/** + * c_list_first() - return pointer to first element, or NULL if empty + * @list: list to operate on, or NULL + * + * This returns a pointer to the first element, or NULL if empty. This never + * returns a pointer to the list head. + * + * Return: Pointer to first list element, or NULL if empty. + */ +static inline CList *c_list_first(CList *list) { + return c_list_is_empty(list) ? NULL : list->next; +} + +/** + * c_list_last() - return pointer to last element, or NULL if empty + * @list: list to operate on, or NULL + * + * This returns a pointer to the last element, or NULL if empty. This never + * returns a pointer to the list head. + * + * Return: Pointer to last list element, or NULL if empty. + */ +static inline CList *c_list_last(CList *list) { + return c_list_is_empty(list) ? NULL : list->prev; +} + +/** + * c_list_first_entry() - return pointer to first entry, or NULL if empty + * @_list: list to operate on, or NULL + * @_t: type of list entries + * @_m: name of CList member in @_t + * + * This is like c_list_first(), but also applies c_list_entry() on the result. + * + * Return: Pointer to first list entry, or NULL if empty. + */ +#define c_list_first_entry(_list, _t, _m) \ + c_list_entry(c_list_first(_list), _t, _m) + +/** + * c_list_last_entry() - return pointer to last entry, or NULL if empty + * @_list: list to operate on, or NULL + * @_t: type of list entries + * @_m: name of CList member in @_t + * + * This is like c_list_last(), but also applies c_list_entry() on the result. + * + * Return: Pointer to last list entry, or NULL if empty. + */ +#define c_list_last_entry(_list, _t, _m) \ + c_list_entry(c_list_last(_list), _t, _m) + +/** + * c_list_for_each*() - iterators + * + * The c_list_for_each*() macros provide simple for-loop wrappers to iterate + * a linked list. They come in a set of flavours: + * + * - "entry": This combines c_list_entry() with the loop iterator, so the + * iterator always has the type of the surrounding object, rather + * than CList. + * + * - "safe": The loop iterator always keeps track of the next element to + * visit. This means, you can safely modify the current element, + * while retaining loop-integrity. + * You still must not touch any other entry of the list. Otherwise, + * the loop-iterator will be corrupted. + * + * - "continue": Rather than starting the iteration at the front of the list, + * use the current value of the iterator as starting position. + * Note that the first loop iteration will be the following + * element, not the given element. + * + * - "unlink": This unlinks the current element from the list before the loop + * code is run. Note that this only does a partial unlink, since + * it assumes the entire list will be unlinked. You must not + * break out of the loop, or the list will be in an inconsistent + * state. + */ + +/* direct/raw iterators */ + +#define c_list_for_each(_iter, _list) \ + for (_iter = (_list)->next; \ + (_iter) != (_list); \ + _iter = (_iter)->next) + +#define c_list_for_each_safe(_iter, _safe, _list) \ + for (_iter = (_list)->next, _safe = (_iter)->next; \ + (_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +#define c_list_for_each_continue(_iter, _list) \ + for (_iter = (_iter) ? (_iter)->next : (_list)->next; \ + (_iter) != (_list); \ + _iter = (_iter)->next) + +#define c_list_for_each_safe_continue(_iter, _safe, _list) \ + for (_iter = (_iter) ? (_iter)->next : (_list)->next, \ + _safe = (_iter)->next; \ + (_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +#define c_list_for_each_safe_unlink(_iter, _safe, _list) \ + for (_iter = (_list)->next, _safe = (_iter)->next; \ + c_list_init(_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +/* c_list_entry() based iterators */ + +#define c_list_for_each_entry(_iter, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_entry_continue(_iter, _list, _m) \ + for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ + __typeof__(*_iter), \ + _m); \ + &(_iter)->_m != (_list); \ + _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_entry_safe_continue(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ + __typeof__(*_iter), \ + _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_entry_safe_unlink(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + c_list_init(&(_iter)->_m) != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) + +/** + * c_list_flush() - flush all entries from a list + * @list: list to flush + * + * This unlinks all entries from the given list @list and reinitializes their + * link-nodes via C_LIST_INIT(). + * + * Note that the entries are not modified in any other way, nor is their memory + * released. This function just unlinks them and resets all the list nodes. It + * is particularly useful with temporary lists on the stack in combination with + * the GCC-extension __attribute__((__cleanup__(arg))). + */ +static inline void c_list_flush(CList *list) { + CList *iter, *safe; + + c_list_for_each_safe_unlink(iter, safe, list) + /* empty */ ; +} + +/** + * c_list_length() - return number of linked entries, excluding the head + * @list: list to operate on + * + * Returns the number of entries in the list, excluding the list head @list. + * That is, for a list that is empty according to c_list_is_empty(), the + * returned length is 0. This requires to iterate the list and has thus O(n) + * runtime. + * + * Note that this function is meant for debugging purposes only. If you need + * the list size during normal operation, you should maintain a counter + * separately. + * + * Return: Number of items in @list. + */ +static inline size_t c_list_length(const CList *list) { + size_t n = 0; + const CList *iter; + + c_list_for_each(iter, list) + ++n; + + return n; +} + +/** + * c_list_contains() - check whether an entry is linked in a certain list + * @list: list to operate on + * @what: entry to look for + * + * This checks whether @what is linked into @list. This requires a linear + * search through the list, as such runs in O(n). Note that the list-head is + * considered part of the list, and hence this returns true if @what equals + * @list. + * + * Note that this function is meant for debugging purposes, and consistency + * checks. You should always be aware whether your objects are linked in a + * specific list. + * + * Return: True if @what is in @list, false otherwise. + */ +static inline _Bool c_list_contains(const CList *list, const CList *what) { + const CList *iter; + + c_list_for_each(iter, list) + if (what == iter) + return 1; + + return what == list; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 00000000..ec7f29d5 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,34 @@ +# +# target: libclist.so +# (No .so is built so far, since we are header-only. This might change in the +# future, if we add more complex list helpers.) +# + +libclist_dep = declare_dependency( + include_directories: include_directories('.'), + version: meson.project_version(), +) + +if not meson.is_subproject() + install_headers('c-list.h') + + mod_pkgconfig.generate( + description: project_description, + filebase: 'libclist-'+major, + name: 'libclist', + version: meson.project_version(), + ) +endif + +# +# target: test-* +# + +test_api = executable('test-api', ['test-api.c'], dependencies: libclist_dep) +test('API Symbol Visibility', test_api) + +test_basic = executable('test-basic', ['test-basic.c'], dependencies: libclist_dep) +test('Basic API Behavior', test_basic) + +test_embed = executable('test-embed', ['test-embed.c'], dependencies: libclist_dep) +test('Embedded List Nodes', test_embed) diff --git a/src/test-api.c b/src/test-api.c new file mode 100644 index 00000000..864d198a --- /dev/null +++ b/src/test-api.c @@ -0,0 +1,144 @@ +/* + * Tests for Public API + * This test, unlikely the others, is linked against the real, distributed, + * shared library. Its sole purpose is to test for symbol availability. + */ + +#undef NDEBUG +#include +#include +#include +#include +#include "c-list.h" + +typedef struct { + int id; + CList link; +} Node; + +static void test_api(void) { + CList *list_iter, *list_safe; + CList list = C_LIST_INIT(list), list2 = C_LIST_INIT(list2); + Node node = { .id = 0, .link = C_LIST_INIT(node.link) }; + + assert(c_list_init(&list) == &list); + assert(!c_list_entry_offset(NULL, 0)); + assert(!c_list_entry_offset(NULL, offsetof(Node, link))); + assert(!c_list_entry(NULL, Node, link)); + assert(c_list_entry(&node.link, Node, link) == &node); + assert(!c_list_is_linked(&node.link)); + assert(c_list_is_empty(&list)); + assert(c_list_length(&list) == 0); + assert(c_list_contains(&list, &list)); + assert(!c_list_contains(&list, &node.link)); + c_list_flush(&list); + + /* basic link / unlink calls */ + + c_list_link_before(&list, &node.link); + assert(c_list_is_linked(&node.link)); + assert(!c_list_is_empty(&list)); + assert(c_list_length(&list) == 1); + assert(c_list_contains(&list, &list)); + assert(c_list_contains(&list, &node.link)); + + c_list_unlink_stale(&node.link); + assert(c_list_is_linked(&node.link)); + assert(c_list_is_empty(&list)); + assert(c_list_length(&list) == 0); + + c_list_link_after(&list, &node.link); + assert(c_list_is_linked(&node.link)); + assert(!c_list_is_empty(&list)); + + c_list_unlink(&node.link); + assert(!c_list_is_linked(&node.link)); + assert(c_list_is_empty(&list)); + + /* link / unlink aliases */ + + c_list_link_front(&list, &node.link); + assert(c_list_is_linked(&node.link)); + + c_list_unlink(&node.link); + assert(!c_list_is_linked(&node.link)); + + c_list_link_tail(&list, &node.link); + assert(c_list_is_linked(&node.link)); + + c_list_unlink(&node.link); + assert(!c_list_is_linked(&node.link)); + + /* swap / splice / split list operators */ + + c_list_swap(&list, &list); + assert(c_list_is_empty(&list)); + + c_list_splice(&list, &list); + assert(c_list_is_empty(&list)); + + c_list_split(&list, &list, &list2); + assert(c_list_is_empty(&list)); + assert(c_list_is_empty(&list2)); + + /* direct/raw iterators */ + + c_list_for_each(list_iter, &list) + assert(list_iter != &list); + + c_list_for_each_safe(list_iter, list_safe, &list) + assert(list_iter != &list); + + list_iter = NULL; + c_list_for_each_continue(list_iter, &list) + assert(list_iter != &list); + + list_iter = NULL; + c_list_for_each_safe_continue(list_iter, list_safe, &list) + assert(list_iter != &list); + + c_list_for_each_safe_unlink(list_iter, list_safe, &list) + assert(list_iter != &list); + + /* list accessors */ + + assert(!c_list_first(&list)); + assert(!c_list_last(&list)); + assert(!c_list_first_entry(&list, Node, link)); + assert(!c_list_last_entry(&list, Node, link)); +} + +#if defined(__GNUC__) || defined(__clang__) +static void test_api_gnu(void) { + CList list = C_LIST_INIT(list); + Node *node_iter, *node_safe; + + /* c_list_entry() based iterators */ + + c_list_for_each_entry(node_iter, &list, link) + assert(&node_iter->link != &list); + + c_list_for_each_entry_safe(node_iter, node_safe, &list, link) + assert(&node_iter->link != &list); + + node_iter = NULL; + c_list_for_each_entry_continue(node_iter, &list, link) + assert(&node_iter->link != &list); + + node_iter = NULL; + c_list_for_each_entry_safe_continue(node_iter, node_safe, &list, link) + assert(&node_iter->link != &list); + + c_list_for_each_entry_safe_unlink(node_iter, node_safe, &list, link) + assert(&node_iter->link != &list); +} +#else +static void test_api_gnu(void) { +} +#endif + +int main(void) { + test_api(); + test_api_gnu(); + return 0; +} diff --git a/src/test-basic.c b/src/test-basic.c new file mode 100644 index 00000000..58ed8636 --- /dev/null +++ b/src/test-basic.c @@ -0,0 +1,319 @@ +/* + * Tests for basic functionality + * This contains basic, deterministic tests for list behavior, API + * functionality, and usage. + */ + +#undef NDEBUG +#include +#include +#include +#include +#include "c-list.h" + +static void assert_list_integrity(CList *list) { + CList *iter; + + iter = list; + do { + assert(iter->next->prev == iter); + assert(iter->prev->next == iter); + + iter = iter->next; + } while (iter != list); +} + +static void test_iterators(void) { + CList *iter, *safe, a, b, list = C_LIST_INIT(list); + unsigned int i; + + assert(!c_list_first(&list)); + assert(!c_list_last(&list)); + + /* link @a and verify iterators see just it */ + + c_list_link_tail(&list, &a); + assert(c_list_is_linked(&a)); + assert(c_list_first(&list) == &a); + assert(c_list_last(&list) == &a); + + i = 0; + c_list_for_each(iter, &list) { + assert(iter == &a); + ++i; + } + assert(i == 1); + + i = 0; + iter = NULL; + c_list_for_each_continue(iter, &list) { + assert(iter == &a); + ++i; + } + assert(i == 1); + + i = 0; + iter = &a; + c_list_for_each_continue(iter, &list) + ++i; + assert(i == 0); + + /* link @b as well and verify iterators again */ + + c_list_link_tail(&list, &b); + assert(c_list_is_linked(&a)); + assert(c_list_is_linked(&b)); + + i = 0; + c_list_for_each(iter, &list) { + assert((i == 0 && iter == &a) || + (i == 1 && iter == &b)); + ++i; + } + assert(i == 2); + + i = 0; + iter = NULL; + c_list_for_each_continue(iter, &list) { + assert((i == 0 && iter == &a) || + (i == 1 && iter == &b)); + ++i; + } + assert(i == 2); + + i = 0; + iter = &a; + c_list_for_each_continue(iter, &list) { + assert(iter == &b); + ++i; + } + assert(i == 1); + + i = 0; + iter = &b; + c_list_for_each_continue(iter, &list) + ++i; + assert(i == 0); + + /* verify safe-iterator while removing elements */ + + i = 0; + c_list_for_each_safe(iter, safe, &list) { + assert(iter == &a || iter == &b); + c_list_unlink_stale(iter); + ++i; + } + assert(i == 2); + + assert(c_list_is_empty(&list)); + + /* link both and verify *_unlink() iterators */ + + c_list_link_tail(&list, &a); + c_list_link_tail(&list, &b); + + i = 0; + c_list_for_each_safe_unlink(iter, safe, &list) { + assert(iter == &a || iter == &b); + assert(!c_list_is_linked(iter)); + ++i; + } + assert(i == 2); + + assert(c_list_is_empty(&list)); +} + +static void test_swap(void) { + CList list1 = (CList)C_LIST_INIT(list1); + CList list2 = (CList)C_LIST_INIT(list2); + CList list; + + c_list_swap(&list1, &list2); + + assert(list1.prev == list1.next && list1.prev == &list1); + assert(list2.prev == list2.next && list2.prev == &list2); + + c_list_link_tail(&list1, &list); + + assert(c_list_first(&list1) == &list); + assert(c_list_last(&list1) == &list); + assert(list.next == &list1); + assert(list.prev == &list1); + + c_list_swap(&list1, &list2); + + assert(c_list_first(&list2) == &list); + assert(c_list_last(&list2) == &list); + assert(list.next == &list2); + assert(list.prev == &list2); + + assert(list1.prev == list1.next && list1.prev == &list1); +} + +static void test_splice(void) { + CList target = (CList)C_LIST_INIT(target); + CList source = (CList)C_LIST_INIT(source); + CList e1, e2; + + c_list_link_tail(&source, &e1); + + c_list_splice(&target, &source); + assert(c_list_first(&target) == &e1); + assert(c_list_last(&target) == &e1); + + source = (CList)C_LIST_INIT(source); + + c_list_link_tail(&source, &e2); + + c_list_splice(&target, &source); + assert(c_list_first(&target) == &e1); + assert(c_list_last(&target) == &e2); +} + +static void test_split(void) { + CList e1, e2; + + /* split empty list */ + { + CList source = C_LIST_INIT(source), target; + + c_list_split(&source, &source, &target); + assert(c_list_is_empty(&source)); + assert(c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } + + /* split 1-element list excluding the element */ + { + CList source = C_LIST_INIT(source), target; + + c_list_link_tail(&source, &e1); + c_list_split(&source, &source, &target); + assert(!c_list_is_empty(&source)); + assert(c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } + + /* split 1-element list including the element */ + { + CList source = C_LIST_INIT(source), target; + + c_list_link_tail(&source, &e1); + c_list_split(&source, &e1, &target); + assert(c_list_is_empty(&source)); + assert(!c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } + + /* split 2-element list excluding the elements */ + { + CList source = C_LIST_INIT(source), target; + + c_list_link_tail(&source, &e1); + c_list_link_tail(&source, &e2); + c_list_split(&source, &source, &target); + assert(!c_list_is_empty(&source)); + assert(c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } + + /* split 2-element list including one element */ + { + CList source = C_LIST_INIT(source), target; + + c_list_link_tail(&source, &e1); + c_list_link_tail(&source, &e2); + c_list_split(&source, &e2, &target); + assert(!c_list_is_empty(&source)); + assert(!c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } + + /* split 2-element list including both elements */ + { + CList source = C_LIST_INIT(source), target; + + c_list_link_tail(&source, &e1); + c_list_link_tail(&source, &e2); + c_list_split(&source, &e1, &target); + assert(c_list_is_empty(&source)); + assert(!c_list_is_empty(&target)); + assert_list_integrity(&source); + assert_list_integrity(&target); + } +} + + +static void test_flush(void) { + CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2); + CList list1 = C_LIST_INIT(list1), list2 = C_LIST_INIT(list2); + + c_list_link_tail(&list2, &e1); + c_list_link_tail(&list2, &e2); + + assert(c_list_is_linked(&e1)); + assert(c_list_is_linked(&e2)); + + c_list_flush(&list1); + c_list_flush(&list2); + + assert(!c_list_is_linked(&e1)); + assert(!c_list_is_linked(&e2)); +} + +static void test_macros(void) { + /* Verify `c_list_entry()` evaluates arguments only once. */ + { + struct TestList { + int a; + CList link; + int b; + } list = { .link = C_LIST_INIT(list.link) }; + CList *p[2] = { &list.link, NULL }; + unsigned int i = 0; + + assert(i == 0); + assert(c_list_entry(p[i++], struct TestList, link) == &list); + assert(i == 1); + } +} + +#if defined(__GNUC__) || defined(__clang__) +static void test_gnu(void) { + CList e1 = C_LIST_INIT(e1), e2 = C_LIST_INIT(e2); + + /* Test `c_list_flush()` in combination with cleanup attributes. */ + { + __attribute((cleanup(c_list_flush))) CList list1 = C_LIST_INIT(list1); + __attribute((cleanup(c_list_flush))) CList list2 = C_LIST_INIT(list2); + + c_list_link_tail(&list2, &e1); + c_list_link_tail(&list2, &e2); + + assert(c_list_is_linked(&e1)); + assert(c_list_is_linked(&e2)); + } + + assert(!c_list_is_linked(&e1)); + assert(!c_list_is_linked(&e2)); +} +#else +static void test_gnu(void) { +} +#endif + +int main(void) { + test_iterators(); + test_swap(); + test_splice(); + test_split(); + test_flush(); + test_macros(); + test_gnu(); + return 0; +} diff --git a/src/test-embed.c b/src/test-embed.c new file mode 100644 index 00000000..7ee6ff01 --- /dev/null +++ b/src/test-embed.c @@ -0,0 +1,173 @@ +/* + * Tests for embedded CList members + */ + +#undef NDEBUG +#include +#include +#include +#include +#include "c-list.h" + +typedef struct Entry Entry; + +struct Entry { + short foo; + CList link; + short bar; +}; + +static void test_entry(void) { + CList list = C_LIST_INIT(list); + Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 }; + Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 }; + Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 }; + Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 }; + Entry *e; + CList *iter, *safe; + size_t i; + + /* verify c_list_entry() works as expected (even with NULL) */ + + assert(!c_list_entry(NULL, Entry, link)); + assert(&e1 == c_list_entry(&e1.link, Entry, link)); + + /* verify @list is empty */ + + assert(!c_list_first_entry(&list, Entry, link)); + assert(!c_list_last_entry(&list, Entry, link)); + + /* link 2 entries and verify list state */ + + c_list_link_tail(&list, &e1.link); + c_list_link_tail(&list, &e2.link); + + assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7); + assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11); + assert(c_list_last_entry(&list, Entry, link)->foo == 2 * 7); + assert(c_list_last_entry(&list, Entry, link)->bar == 2 * 11); + + i = 0; + c_list_for_each(iter, &list) { + e = c_list_entry(iter, Entry, link); + assert(i != 0 || e == &e1); + assert(i != 1 || e == &e2); + assert(i < 2); + ++i; + } + assert(i == 2); + + /* link 2 more entries */ + + c_list_link_tail(&list, &e3.link); + c_list_link_tail(&list, &e4.link); + + assert(c_list_first_entry(&list, Entry, link)->foo == 1 * 7); + assert(c_list_first_entry(&list, Entry, link)->bar == 1 * 11); + assert(c_list_last_entry(&list, Entry, link)->foo == 4 * 7); + assert(c_list_last_entry(&list, Entry, link)->bar == 4 * 11); + + i = 0; + c_list_for_each(iter, &list) { + e = c_list_entry(iter, Entry, link); + assert(i != 0 || e == &e1); + assert(i != 1 || e == &e2); + assert(i != 2 || e == &e3); + assert(i != 3 || e == &e4); + assert(i < 4); + ++i; + } + assert(i == 4); + + assert(!c_list_is_empty(&list)); + assert(c_list_is_linked(&e1.link)); + assert(c_list_is_linked(&e2.link)); + assert(c_list_is_linked(&e3.link)); + assert(c_list_is_linked(&e4.link)); + + /* remove via safe iterator */ + + i = 0; + c_list_for_each_safe(iter, safe, &list) { + e = c_list_entry(iter, Entry, link); + assert(i != 0 || e == &e1); + assert(i != 1 || e == &e2); + assert(i != 2 || e == &e3); + assert(i != 3 || e == &e4); + assert(i < 4); + ++i; + c_list_unlink(&e->link); + } + assert(i == 4); + + assert(c_list_is_empty(&list)); + assert(!c_list_is_linked(&e1.link)); + assert(!c_list_is_linked(&e2.link)); + assert(!c_list_is_linked(&e3.link)); + assert(!c_list_is_linked(&e4.link)); +} + +#if defined(__GNUC__) || defined(__clang__) +static void test_entry_gnu(void) { + CList list = C_LIST_INIT(list); + Entry e1 = { .foo = 1 * 7, .bar = 1 * 11 }; + Entry e2 = { .foo = 2 * 7, .bar = 2 * 11 }; + Entry e3 = { .foo = 3 * 7, .bar = 3 * 11 }; + Entry e4 = { .foo = 4 * 7, .bar = 4 * 11 }; + Entry *e, *safe; + size_t i; + + /* link entries and verify list state */ + + c_list_link_tail(&list, &e1.link); + c_list_link_tail(&list, &e2.link); + c_list_link_tail(&list, &e3.link); + c_list_link_tail(&list, &e4.link); + + i = 0; + c_list_for_each_entry(e, &list, link) { + assert(i != 0 || e == &e1); + assert(i != 1 || e == &e2); + assert(i != 2 || e == &e3); + assert(i != 3 || e == &e4); + assert(i < 4); + ++i; + } + assert(i == 4); + + assert(!c_list_is_empty(&list)); + assert(c_list_is_linked(&e1.link)); + assert(c_list_is_linked(&e2.link)); + assert(c_list_is_linked(&e3.link)); + assert(c_list_is_linked(&e4.link)); + + /* remove via safe iterator */ + + i = 0; + c_list_for_each_entry_safe(e, safe, &list, link) { + assert(i != 0 || e == &e1); + assert(i != 1 || e == &e2); + assert(i != 2 || e == &e3); + assert(i != 3 || e == &e4); + assert(i < 4); + ++i; + c_list_unlink(&e->link); + } + assert(i == 4); + + assert(c_list_is_empty(&list)); + assert(!c_list_is_linked(&e1.link)); + assert(!c_list_is_linked(&e2.link)); + assert(!c_list_is_linked(&e3.link)); + assert(!c_list_is_linked(&e4.link)); +} +#else +static void test_entry_gnu(void) { +} +#endif + +int main(void) { + test_entry(); + test_entry_gnu(); + return 0; +} From 9c9da23132b2ba9bd1898820fdd611e9db1e639c Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 14 Nov 2024 11:47:17 +0100 Subject: [PATCH 2/2] c-list: hook up "c-list.h" for internal usage --- Makefile.am | 4 ++++ tests/check-direct.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Makefile.am b/Makefile.am index b07082a7..8411d644 100644 --- a/Makefile.am +++ b/Makefile.am @@ -338,6 +338,9 @@ noinst_HEADERS = \ include/nl-priv-dynamic-core/object-api.h \ include/nl-priv-dynamic-route/nl-priv-dynamic-route.h \ include/nl-priv-static-route/nl-priv-static-route.h \ + \ + third_party/c-list/src/c-list.h \ + \ $(NULL) ############################################################################### @@ -395,6 +398,7 @@ default_includes = \ -I$(srcdir)/include/linux-private \ -I$(srcdir)/include \ -I$(builddir)/include \ + -I$(srcdir)/third_party/c-list/src \ $(NULL) default_cppflags = \ diff --git a/tests/check-direct.c b/tests/check-direct.c index 33b7742d..808ab437 100644 --- a/tests/check-direct.c +++ b/tests/check-direct.c @@ -10,6 +10,8 @@ #include #include +#include "c-list.h" + #include "nl-priv-static-route/nl-priv-static-route.h" #include "nl-aux-core/nl-core.h"