changes the compare function wrapper for pointer lists so that it no longer invokes the actual compare function for NULL pointers

Sat, 11 Oct 2025 11:55:46 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 11 Oct 2025 11:55:46 +0200
changeset 1422
8bfccb342895
parent 1421
809eb30cd621
child 1423
9a72258446cd

changes the compare function wrapper for pointer lists so that it no longer invokes the actual compare function for NULL pointers

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
tests/ucxtest.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Fri Oct 10 19:40:24 2025 +0200
+++ b/CHANGELOG	Sat Oct 11 11:55:46 2025 +0200
@@ -28,6 +28,7 @@
  * changes the implementation of cx_strreplacen() for improved efficiency
  * changes all cxListIterator() and cxMapIterator() family of functions to also accept NULL as argument
  * changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element
+ * changes the compare function wrapper for pointer lists so that it no longer invokes the actual compare function for NULL pointers
  * fixes critical memory overflow in the stack-based array reallocator (this unfortunately breaks the function signature)
  * fixes critical bug in cx_array_insert_sorted() that caused an infinite loop when inserting duplicates
  * fixes mempool implementation not supporting NULL as argument for realloc
--- a/docs/Writerside/topics/about.md	Fri Oct 10 19:40:24 2025 +0200
+++ b/docs/Writerside/topics/about.md	Sat Oct 11 11:55:46 2025 +0200
@@ -55,6 +55,7 @@
 * changes the implementation of cx_strreplacen() for improved efficiency
 * changes all cxListIterator() and cxMapIterator() family of functions to also accept NULL as argument
 * changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element
+* changes the compare function wrapper for pointer lists so that it no longer invokes the actual compare function for NULL pointers
 * fixes critical memory overflow in the stack-based array reallocator (this unfortunately breaks the function signature)
 * fixes critical bug in cx_array_insert_sorted() that caused an infinite loop when inserting duplicates
 * fixes mempool implementation not supporting NULL as argument for realloc
--- a/src/list.c	Fri Oct 10 19:40:24 2025 +0200
+++ b/src/list.c	Sat Oct 11 11:55:46 2025 +0200
@@ -38,10 +38,18 @@
         const void *l,
         const void *r
 ) {
+    // l and r are guaranteed to be non-NULL pointing to the list's memory
     void *const *lptr = l;
     void *const *rptr = r;
-    const void *left = lptr == NULL ? NULL : *lptr;
-    const void *right = rptr == NULL ? NULL : *rptr;
+    const void *left = *lptr;
+    const void *right = *rptr;
+    if (left == NULL) {
+        // NULL is smaller than any value except NULL
+        return right == NULL ? 0 : -1;
+    } else if (right == NULL) {
+        // any value is larger than NULL
+        return 1;
+    }
     return cx_pl_cmpfunc_impl(left, right);
 }
 
@@ -297,7 +305,10 @@
     for (; i < n; i++) {
         if (NULL == invoke_list_func(
             insert_element, list, index + i,
-            src + (i * elem_size))) return i;
+            src + i * elem_size)
+        ) {
+            return i; // LCOV_EXCL_LINE
+        }
     }
     return i;
 }
@@ -336,7 +347,9 @@
                         si++;
                         inserted++;
                     }
-                    if (inserted == n) return inserted;
+                    if (inserted == n) {
+                        return inserted;
+                    }
                 }
                 continue;
             }
@@ -357,17 +370,22 @@
 
         // insert the elements at location si
         if (ins == 1) {
-            if (NULL == invoke_list_func(
-                insert_element, list, di, src)) return inserted;
+            if (NULL == invoke_list_func(insert_element, list, di, src)) {
+                return inserted; // LCOV_EXCL_LINE
+            }
         } else {
             size_t r = invoke_list_func(insert_array, list, di, src, ins);
-            if (r < ins) return inserted + r;
+            if (r < ins) {
+                return inserted + r;  // LCOV_EXCL_LINE
+            }
         }
         inserted += ins;
         di += ins;
 
         // everything inserted?
-        if (inserted == n) return inserted;
+        if (inserted == n) {
+            return inserted;
+        }
         src = next;
     }
 
@@ -399,7 +417,7 @@
     size_t elem_size = list->collection.elem_size;
     size_t list_size = list->collection.size;
     void *tmp = cxMallocDefault(elem_size * list_size);
-    if (tmp == NULL) abort();
+    if (tmp == NULL) abort(); // LCOV_EXCL_LINE
 
     // copy elements from source array
     char *loc = tmp;
@@ -432,7 +450,7 @@
     size_t elem_size = list->collection.elem_size;
 
     void *tmp = cxMallocDefault(elem_size);
-    if (tmp == NULL) return 1;
+    if (tmp == NULL) return 1; // LCOV_EXCL_LINE
 
     void *ip = invoke_list_func(at, list, i);
     void *jp = invoke_list_func(at, list, j);
--- a/tests/test_list.c	Fri Oct 10 19:40:24 2025 +0200
+++ b/tests/test_list.c	Sat Oct 11 11:55:46 2025 +0200
@@ -1143,18 +1143,24 @@
     CxIterator it2 = cxListBackwardsIterator(list);
     CxIterator it3 = cxListMutIterator(list);
     CxIterator it4 = cxListMutBackwardsIterator(list);
+    CxIterator it5 = cxListMutIteratorAt(list, 0);
+    CxIterator it6 = cxListMutBackwardsIteratorAt(list, 0);
 
     CX_TEST_DO {
         CX_TEST_ASSERT(!cxIteratorValid(it1));
         CX_TEST_ASSERT(!cxIteratorValid(it2));
         CX_TEST_ASSERT(!cxIteratorValid(it3));
         CX_TEST_ASSERT(!cxIteratorValid(it4));
+        CX_TEST_ASSERT(!cxIteratorValid(it5));
+        CX_TEST_ASSERT(!cxIteratorValid(it6));
 
         int c = 0;
         cx_foreach(void*, data, it1) c++;
         cx_foreach(void*, data, it2) c++;
         cx_foreach(void*, data, it3) c++;
         cx_foreach(void*, data, it4) c++;
+        cx_foreach(void*, data, it5) c++;
+        cx_foreach(void*, data, it6) c++;
         CX_TEST_ASSERT(c == 0);
     }
 }
@@ -1220,6 +1226,13 @@
     cxListFree(al);
 }
 
+CX_TEST(test_null_list_free) {
+    CX_TEST_DO {
+        // cannot really verify, but asan or valgrind would complain
+        cxListFree(NULL);
+    }
+}
+
 CX_TEST(test_list_ll_create) {
     CxTestingAllocator talloc;
     cx_testing_allocator_init(&talloc);
@@ -2415,6 +2428,34 @@
     free(testdata);
 })
 
+CX_TEST(test_list_pointer_list_supports_null) {
+    CxList *list = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_int, CX_STORE_POINTERS);
+    int x = 47;
+    int y = 11;
+    int z = 1337;
+    int *nptr = NULL;
+    cxListAdd(list, &x);
+    cxListAdd(list, &y);
+    cxListAdd(list, nptr);
+    cxListAdd(list, &z);
+    CX_TEST_DO {
+        CX_TEST_ASSERT(cxListSize(list) == 4);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 0) == 47);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 1) == 11);
+        CX_TEST_ASSERT((int *) cxListAt(list, 2) == NULL);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 3) == 1337);
+        CX_TEST_ASSERT(cxListFind(list, nptr) == 2);
+
+        // when we sort the list, NULL is supposed to be smaller than any value
+        cxListSort(list);
+        CX_TEST_ASSERT((int *) cxListAt(list, 0) == NULL);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 1) == 11);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 2) == 47);
+        CX_TEST_ASSERT(*(int *) cxListAt(list, 3) == 1337);
+    }
+    cxListFree(list);
+}
+
 CxTestSuite *cx_test_suite_array_list(void) {
     CxTestSuite *suite = cx_test_suite_new("array_list");
 
@@ -2697,6 +2738,15 @@
     cx_test_register(suite, test_empty_list_at);
     cx_test_register(suite, test_empty_list_find);
     cx_test_register(suite, test_empty_list_compare);
+    cx_test_register(suite, test_null_list_free);
 
     return suite;
 }
+
+CxTestSuite *cx_test_suite_list_corner_cases(void) {
+    CxTestSuite *suite = cx_test_suite_new("list corner cases");
+
+    cx_test_register(suite, test_list_pointer_list_supports_null);
+
+    return suite;
+}
--- a/tests/ucxtest.c	Fri Oct 10 19:40:24 2025 +0200
+++ b/tests/ucxtest.c	Sat Oct 11 11:55:46 2025 +0200
@@ -48,6 +48,7 @@
 CxTestSuite *cx_test_suite_linked_list_defaulted_funcs(void);
 CxTestSuite *cx_test_suite_kv_list(void);
 CxTestSuite *cx_test_suite_kv_list_specifics(void);
+CxTestSuite *cx_test_suite_list_corner_cases(void);
 CxTestSuite *cx_test_suite_tree_low_level(void);
 CxTestSuite *cx_test_suite_tree_high_level(void);
 CxTestSuite *cx_test_suite_properties(void);
@@ -101,6 +102,7 @@
             cx_test_suite_linked_list_defaulted_funcs(),
             cx_test_suite_kv_list(),
             cx_test_suite_kv_list_specifics(),
+            cx_test_suite_list_corner_cases(),
             cx_test_suite_tree_low_level(),
             cx_test_suite_tree_high_level(),
             cx_test_suite_properties(),

mercurial