update ucx to version 3.2

Sun, 30 Nov 2025 18:25:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 30 Nov 2025 18:25:55 +0100
changeset 645
0c85c4cd0dd8
parent 644
e96e92e3508f
child 646
f64ebb0c5fa9

update ucx to version 3.2

src/ucx/array_list.c file | annotate | diff | comparison | revisions
src/ucx/buffer.c file | annotate | diff | comparison | revisions
src/ucx/cx/array_list.h file | annotate | diff | comparison | revisions
src/ucx/cx/buffer.h file | annotate | diff | comparison | revisions
src/ucx/cx/common.h file | annotate | diff | comparison | revisions
src/ucx/cx/json.h file | annotate | diff | comparison | revisions
src/ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
src/ucx/cx/list.h file | annotate | diff | comparison | revisions
src/ucx/cx/string.h file | annotate | diff | comparison | revisions
src/ucx/cx/tree.h file | annotate | diff | comparison | revisions
src/ucx/hash_map.c file | annotate | diff | comparison | revisions
src/ucx/json.c file | annotate | diff | comparison | revisions
src/ucx/kv_list.c file | annotate | diff | comparison | revisions
src/ucx/linked_list.c file | annotate | diff | comparison | revisions
src/ucx/list.c file | annotate | diff | comparison | revisions
src/ucx/printf.c file | annotate | diff | comparison | revisions
src/ucx/properties.c file | annotate | diff | comparison | revisions
src/ucx/string.c file | annotate | diff | comparison | revisions
src/ucx/tree.c file | annotate | diff | comparison | revisions
--- a/src/ucx/array_list.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/array_list.c	Sun Nov 30 18:25:55 2025 +0100
@@ -42,10 +42,11 @@
         cx_attr_unused CxArrayReallocator *alloc
 ) {
     size_t n;
+    // LCOV_EXCL_START
     if (cx_szmul(new_capacity, elem_size, &n)) {
         errno = EOVERFLOW;
         return NULL;
-    }
+    } // LCOV_EXCL_STOP
     return cxReallocDefault(array, n);
 }
 
@@ -66,10 +67,11 @@
 ) {
     // check for overflow
     size_t n;
+    // LCOV_EXCL_START
     if (cx_szmul(new_capacity, elem_size, &n)) {
         errno = EOVERFLOW;
         return NULL;
-    }
+    } // LCOV_EXCL_STOP
 
     // retrieve the pointer to the actual allocator
     const CxAllocator *al = alloc->allocator;
@@ -103,23 +105,27 @@
 // LOW LEVEL ARRAY LIST FUNCTIONS
 
 /**
- * Increases the capacity until it is a multiple of a some alignment or reaches the maximum.
+ * Intelligently calculates a new capacity, reserving some more
+ * elements than required to prevent too many allocations.
  *
- * @param cap the required capacity (must not be larger than @p max)
- * @param alignment the alignment
- * @param max the maximum capacity
- * @return the aligned capacity
+ * @param current_capacity the current capacity of the array
+ * @param needed_capacity the required capacity of the array
+ * @return the new capacity
  */
-static size_t cx_array_align_capacity(
-        size_t cap,
-        size_t alignment,
-        size_t max
+static size_t cx_array_grow_capacity(
+    size_t current_capacity,
+    size_t needed_capacity
 ) {
-    if (cap > max - alignment) {
-        return cap;
-    } else {
-        return cap - (cap % alignment) + alignment;
+    if (current_capacity >= needed_capacity) {
+        return current_capacity;
     }
+    size_t cap = needed_capacity;
+    size_t alignment;
+    if (cap < 128) alignment = 16;
+    else if (cap < 1024) alignment = 64;
+    else if (cap < 8192) alignment = 512;
+    else alignment = 1024;
+    return cap - (cap % alignment) + alignment;
 }
 
 int cx_array_reserve(
@@ -184,10 +190,6 @@
 
     // reallocate if possible
     if (newcap > oldcap) {
-        // calculate new capacity (next number divisible by 16)
-        newcap = cx_array_align_capacity(newcap, 16, max_size);
-
-        // perform reallocation
         void *newmem = reallocator->realloc(
                 *array, oldcap, newcap, elem_size, reallocator
         );
@@ -277,22 +279,18 @@
     }
 
     // check if resize is required
-    size_t minsize = index + elem_count;
-    size_t newsize = oldsize < minsize ? minsize : oldsize;
+    const size_t minsize = index + elem_count;
+    const size_t newsize = oldsize < minsize ? minsize : oldsize;
 
-    // reallocate if possible
-    size_t newcap = oldcap;
-    if (newsize > oldcap) {
-        // check, if we need to repair the src pointer
+    // reallocate if necessary
+    const size_t newcap = cx_array_grow_capacity(oldcap, newsize);
+    if (newcap > oldcap) {
+        // check if we need to repair the src pointer
         uintptr_t targetaddr = (uintptr_t) *target;
         uintptr_t srcaddr = (uintptr_t) src;
         bool repairsrc = targetaddr <= srcaddr
                          && srcaddr < targetaddr + oldcap * elem_size;
 
-        // calculate new capacity (next number divisible by 16)
-        newcap = cx_array_align_capacity(newsize, 16, max_size);
-        assert(newcap > newsize);
-
         // perform reallocation
         void *newmem = reallocator->realloc(
                 *target, oldcap, newcap, elem_size, reallocator
@@ -369,22 +367,23 @@
     if (elem_count == 0) return 0;
 
     // overflow check
+    // LCOV_EXCL_START
     if (elem_count > SIZE_MAX - *size) {
         errno = EOVERFLOW;
         return 1;
     }
+    // LCOV_EXCL_STOP
 
     // store some counts
-    size_t old_size = *size;
-    size_t old_capacity = *capacity;
+    const size_t old_size = *size;
+    const size_t old_capacity = *capacity;
     // the necessary capacity is the worst case assumption, including duplicates
-    size_t needed_capacity = old_size + elem_count;
+    const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count);
 
     // if we need more than we have, try a reallocation
     if (needed_capacity > old_capacity) {
-        size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX);
         void *new_mem = reallocator->realloc(
-                *target, old_capacity, new_capacity, elem_size, reallocator
+                *target, old_capacity, needed_capacity, elem_size, reallocator
         );
         if (new_mem == NULL) {
             // give it up right away, there is no contract
@@ -392,7 +391,7 @@
             return 1;  // LCOV_EXCL_LINE
         }
         *target = new_mem;
-        *capacity = new_capacity;
+        *capacity = needed_capacity;
     }
 
     // now we have guaranteed that we can insert everything
@@ -418,21 +417,23 @@
     // while there are both source and buffered elements left,
     // copy them interleaving
     while (si < elem_count && bi < new_size) {
-        // determine how many source elements can be inserted
+        // determine how many source elements can be inserted.
+        // the first element that shall not be inserted is the smallest element
+        // that is strictly larger than the first buffered element
+        // (located at the index of the infimum plus one).
+        // the infimum is guaranteed to exist:
+        // - if all src elements are larger,
+        //   there is no buffer, and this loop is skipped
+        // - if any src element is smaller or equal, the infimum exists
+        // - when all src elements that are smaller are copied, the second part
+        //   of this loop body will copy the remaining buffer (emptying it)
+        // Therefore, the buffer can never contain an element that is smaller
+        // than any element in the source and the infimum exists.
         size_t copy_len, bytes_copied;
-        copy_len = cx_array_binary_search_sup(
-                src,
-                elem_count - si,
-                elem_size,
-                bptr,
-                cmp_func
+        copy_len = cx_array_binary_search_inf(
+            src, elem_count - si, elem_size, bptr, cmp_func
         );
-        // binary search gives us the smallest index;
-        // we also want to include equal elements here
-        while (si + copy_len < elem_count
-                && cmp_func(bptr, src+copy_len*elem_size) == 0) {
-            copy_len++;
-        }
+        copy_len++;
 
         // copy the source elements
         if (copy_len > 0) {
@@ -509,26 +510,17 @@
             // duplicates allowed or nothing inserted yet: simply copy everything
             memcpy(dest, src, elem_size * (elem_count - si));
         } else {
-            if (dest != *target) {
-                // skip all source elements that equal the last element
-                char *last = dest - elem_size;
-                while (si < elem_count) {
-                    if (last != NULL && cmp_func(last, src) == 0) {
-                        src += elem_size;
-                        si++;
-                        (*size)--;
-                    } else {
-                        break;
-                    }
-                }
-            }
-            // we must check the elements in the chunk one by one
+            // we must check the remaining source elements one by one
+            // to skip the duplicates.
+            // Note that no source element can equal the last element in the
+            // destination, because that would have created an insertion point
+            // and a buffer, s.t. the above loop already handled the duplicates
             while (si < elem_count) {
                 // find a chain of elements that can be copied
                 size_t copy_len = 1, skip_len = 0;
                 {
                     const char *left_src = src;
-                    while (si + copy_len < elem_count) {
+                    while (si + copy_len + skip_len < elem_count) {
                         const char *right_src = left_src + elem_size;
                         int d = cmp_func(left_src,  right_src);
                         if (d < 0) {
@@ -596,7 +588,8 @@
         cmp_func, sorted_data, elem_size, elem_count, reallocator, false);
 }
 
-size_t cx_array_binary_search_inf(
+// implementation that finds ANY index
+static size_t cx_array_binary_search_inf_impl(
         const void *arr,
         size_t size,
         size_t elem_size,
@@ -641,13 +634,6 @@
         result = cmp_func(elem, arr_elem);
         if (result == 0) {
             // found it!
-            // check previous elements;
-            // when they are equal, report the smallest index
-            arr_elem -= elem_size;
-            while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) {
-                pivot_index--;
-                arr_elem -= elem_size;
-            }
             return pivot_index;
         } else if (result < 0) {
             // element is smaller than pivot, continue search left
@@ -662,6 +648,24 @@
     return result < 0 ? (pivot_index - 1) : pivot_index;
 }
 
+size_t cx_array_binary_search_inf(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func cmp_func
+) {
+    size_t index = cx_array_binary_search_inf_impl(
+        arr, size, elem_size, elem, cmp_func);
+    // in case of equality, report the largest index
+    const char *e = ((const char *) arr) + (index + 1) * elem_size;
+    while (index + 1 < size && cmp_func(e, elem) == 0) {
+        e += elem_size;
+        index++;
+    }
+    return index;
+}
+
 size_t cx_array_binary_search(
         const void *arr,
         size_t size,
@@ -687,16 +691,25 @@
         const void *elem,
         cx_compare_func cmp_func
 ) {
-    size_t inf = cx_array_binary_search_inf(
+    size_t index = cx_array_binary_search_inf_impl(
             arr, size, elem_size, elem, cmp_func
     );
-    if (inf == size) {
-        // no infimum means, first element is supremum
+    const char *e = ((const char *) arr) + index * elem_size;
+    if (index == size) {
+        // no infimum means the first element is supremum
         return 0;
-    } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
-        return inf;
+    } else if (cmp_func(e, elem) == 0) {
+        // found an equal element, search the smallest index
+        e -= elem_size; // e now contains the element at index-1
+        while (index > 0 && cmp_func(e, elem) == 0) {
+            e -= elem_size;
+            index--;
+        }
+        return index;
     } else {
-        return inf + 1;
+        // we already have the largest index of the infimum (by design)
+        // the next element is the supremum (or there is no supremum)
+        return index + 1;
     }
 }
 
@@ -789,14 +802,13 @@
 
     // guarantee enough capacity
     if (arl->capacity < list->collection.size + n) {
-        size_t new_capacity = list->collection.size + n;
-        new_capacity = cx_array_align_capacity(new_capacity, 16, SIZE_MAX);
+        const size_t new_capacity = cx_array_grow_capacity(arl->capacity,list->collection.size + n);
         if (cxReallocateArray(
                 list->collection.allocator,
                 &arl->data, new_capacity,
                 list->collection.elem_size)
         ) {
-            return 0;
+            return 0; // LCOV_EXCL_LINE
         }
         arl->capacity = new_capacity;
     }
@@ -840,7 +852,7 @@
             &arl->reallocator
     )) {
         // array list implementation is "all or nothing"
-        return 0;
+        return 0;  // LCOV_EXCL_LINE
     } else {
         return n;
     }
@@ -865,7 +877,7 @@
             &arl->reallocator
     )) {
         // array list implementation is "all or nothing"
-        return 0;
+        return 0;  // LCOV_EXCL_LINE
     } else {
         return n;
     }
@@ -892,7 +904,7 @@
     if (iter->index < list->collection.size) {
         if (cx_arl_insert_element(list,
                 iter->index + 1 - prepend, elem) == NULL) {
-            return 1;
+            return 1; // LCOV_EXCL_LINE
         }
         iter->elem_count++;
         if (prepend != 0) {
@@ -902,7 +914,7 @@
         return 0;
     } else {
         if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) {
-            return 1;
+            return 1;  // LCOV_EXCL_LINE
         }
         iter->elem_count++;
         iter->index = list->collection.size;
@@ -1137,6 +1149,14 @@
     }
 }
 
+static int cx_arl_change_capacity(
+        struct cx_list_s *list,
+        size_t new_capacity
+) {
+    cx_array_list *arl = (cx_array_list *)list;
+    return cxReallocateArray(list->collection.allocator,
+        &arl->data, new_capacity, list->collection.elem_size);
+}
 
 static struct cx_iterator_s cx_arl_iterator(
         const struct cx_list_s *list,
@@ -1174,6 +1194,7 @@
         cx_arl_sort,
         cx_arl_compare,
         cx_arl_reverse,
+        cx_arl_change_capacity,
         cx_arl_iterator,
 };
 
--- a/src/ucx/buffer.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/buffer.c	Sun Nov 30 18:25:55 2025 +0100
@@ -35,7 +35,7 @@
 #ifdef _WIN32
 #include <Windows.h>
 #include <sysinfoapi.h>
-static unsigned long system_page_size() {
+static unsigned long system_page_size(void) {
     static unsigned long ps = 0;
     if (ps == 0) {
         SYSTEM_INFO sysinfo;
@@ -44,16 +44,27 @@
     }
     return ps;
 }
-#define SYSTEM_PAGE_SIZE system_page_size()
 #else
 #include <unistd.h>
-#define SYSTEM_PAGE_SIZE sysconf(_SC_PAGESIZE)
+static unsigned long system_page_size(void) {
+    static unsigned long ps = 0;
+    if (ps == 0) {
+        long sc = sysconf(_SC_PAGESIZE);
+        if (sc < 0) {
+            // fallback for systems which do not report a value here
+            ps = 4096; // LCOV_EXCL_LINE
+        } else {
+            ps = (unsigned long) sc;
+        }
+    }
+    return ps;
+}
 #endif
 
 static int buffer_copy_on_write(CxBuffer* buffer) {
     if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0;
     void *newspace = cxMalloc(buffer->allocator, buffer->capacity);
-    if (NULL == newspace) return -1;
+    if (NULL == newspace) return -1;  // LCOV_EXCL_LINE
     memcpy(newspace, buffer->space, buffer->size);
     buffer->space = newspace;
     buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE;
@@ -78,9 +89,7 @@
     buffer->flags = flags;
     if (!space) {
         buffer->bytes = cxMalloc(allocator, capacity);
-        if (buffer->bytes == NULL) {
-            return -1; // LCOV_EXCL_LINE
-        }
+        if (buffer->bytes == NULL) return -1; // LCOV_EXCL_LINE
         buffer->flags |= CX_BUFFER_FREE_CONTENTS;
     } else {
         buffer->bytes = space;
@@ -122,7 +131,7 @@
         allocator = cxDefaultAllocator;
     }
     CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
-    if (buf == NULL) return NULL;
+    if (buf == NULL) return NULL; // LCOV_EXCL_LINE
     if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
         return buf;
     } else {
@@ -183,6 +192,35 @@
 
 }
 
+size_t cxBufferPop(CxBuffer *buffer, size_t size, size_t nitems) {
+    size_t len;
+    if (cx_szmul(size, nitems, &len)) {
+        // LCOV_EXCL_START
+        errno = EOVERFLOW;
+        return 0;
+        // LCOV_EXCL_STOP
+    }
+    if (len == 0) return 0;
+    if (len > buffer->size) {
+        if (size == 1) {
+            // simple case: everything can be discarded
+            len = buffer->size;
+        } else {
+            // complicated case: misaligned bytes must stay
+            size_t misalignment = buffer->size % size;
+            len = buffer->size - misalignment;
+        }
+    }
+    buffer->size -= len;
+
+    // adjust position, if required
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+
+    return len / size;
+}
+
 void cxBufferClear(CxBuffer *buffer) {
     if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) {
         memset(buffer->bytes, 0, buffer->size);
@@ -200,36 +238,10 @@
     return buffer->pos >= buffer->size;
 }
 
-int cxBufferMinimumCapacity(
-        CxBuffer *buffer,
-        size_t newcap
-) {
+int cxBufferReserve(CxBuffer *buffer, size_t newcap) {
     if (newcap <= buffer->capacity) {
         return 0;
     }
-
-    unsigned long pagesize = SYSTEM_PAGE_SIZE;
-    // if page size is larger than 64 KB - for some reason - truncate to 64 KB
-    if (pagesize > 65536) pagesize = 65536;
-    if (newcap < pagesize) {
-        // when smaller as one page, map to the next power of two
-        newcap--;
-        newcap |= newcap >> 1;
-        newcap |= newcap >> 2;
-        newcap |= newcap >> 4;
-        // last operation only needed for pages larger 4096 bytes
-        // but if/else would be more expensive than just doing this
-        newcap |= newcap >> 8;
-        newcap++;
-    } else {
-        // otherwise, map to a multiple of the page size
-        newcap -= newcap % pagesize;
-        newcap += pagesize;
-        // note: if newcap is already page aligned,
-        // this gives a full additional page (which is good)
-    }
-
-
     const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
     if (buffer->flags & force_copy_flags) {
         void *newspace = cxMalloc(buffer->allocator, newcap);
@@ -249,6 +261,38 @@
     }
 }
 
+static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) {
+    unsigned long pagesize = system_page_size();
+    // if page size is larger than 64 KB - for some reason - truncate to 64 KB
+    if (pagesize > 65536) pagesize = 65536;
+    if (mincap < pagesize) {
+        // when smaller as one page, map to the next power of two
+        mincap--;
+        mincap |= mincap >> 1;
+        mincap |= mincap >> 2;
+        mincap |= mincap >> 4;
+        // last operation only needed for pages larger 4096 bytes
+        // but if/else would be more expensive than just doing this
+        mincap |= mincap >> 8;
+        mincap++;
+    } else {
+        // otherwise, map to a multiple of the page size
+        mincap -= mincap % pagesize;
+        mincap += pagesize;
+        // note: if newcap is already page aligned,
+        // this gives a full additional page (which is good)
+    }
+    return mincap;
+}
+
+int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) {
+    if (newcap <= buffer->capacity) {
+        return 0;
+    }
+    newcap = cx_buffer_calculate_minimum_capacity(newcap);
+    return cxBufferReserve(buffer, newcap);
+}
+
 void cxBufferShrink(
         CxBuffer *buffer,
         size_t reserve
@@ -351,8 +395,17 @@
     bool perform_flush = false;
     if (required > buffer->capacity) {
         if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
-            if (buffer->flush != NULL && required > buffer->flush->threshold) {
-                perform_flush = true;
+            if (buffer->flush != NULL) {
+                size_t newcap = cx_buffer_calculate_minimum_capacity(required);
+                if (newcap > buffer->flush->threshold) {
+                    newcap = buffer->flush->threshold;
+                }
+                if (cxBufferReserve(buffer, newcap)) {
+                    return total_flushed; // LCOV_EXCL_LINE
+                }
+                if (required > newcap) {
+                    perform_flush = true;
+                }
             } else {
                 if (cxBufferMinimumCapacity(buffer, required)) {
                     return total_flushed; // LCOV_EXCL_LINE
--- a/src/ucx/cx/array_list.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/array_list.h	Sun Nov 30 18:25:55 2025 +0100
@@ -245,6 +245,9 @@
  * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
  * architecture. If set to zero, the native word width is used.
  *
+ * @note This function will reserve the minimum required capacity to hold
+ * the additional elements and does not perform an overallocation.
+ *
  * @param array a pointer to the target array
  * @param size a pointer to the size of the array
  * @param capacity a pointer to the capacity of the array
@@ -280,6 +283,9 @@
  * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
  * architecture. If set to zero, the native word width is used.
  *
+ * @note When this function does reallocate the array, it may allocate more
+ * space than required to avoid further allocations in the near future.
+ *
  * @param target a pointer to the target array
  * @param size a pointer to the size of the target array
  * @param capacity a pointer to the capacity of the target array
@@ -293,6 +299,7 @@
  * @retval zero success
  * @retval non-zero failure
  * @see cx_array_reallocator()
+ * @see cx_array_reserve()
  */
 cx_attr_nonnull_arg(1, 2, 3, 6)
 CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width,
@@ -669,6 +676,9 @@
  * in @p arr that is less or equal to @p elem with respect to @p cmp_func.
  * When no such element exists, @p size is returned.
  *
+ * When such an element exists more than once, the largest index of all those
+ * elements is returned.
+ *
  * If @p elem is contained in the array, this is identical to
  * #cx_array_binary_search().
  *
@@ -691,6 +701,9 @@
 /**
  * Searches an item in a sorted array.
  *
+ * When such an element exists more than once, the largest index of all those
+ * elements is returned.
+ *
  * If the array is not sorted with respect to the @p cmp_func, the behavior
  * is undefined.
  *
@@ -715,6 +728,9 @@
  * in @p arr that is greater or equal to @p elem with respect to @p cmp_func.
  * When no such element exists, @p size is returned.
  *
+ * When such an element exists more than once, the smallest index of all those
+ * elements is returned.
+ *
  * If @p elem is contained in the array, this is identical to
  * #cx_array_binary_search().
  *
--- a/src/ucx/cx/buffer.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/buffer.h	Sun Nov 30 18:25:55 2025 +0100
@@ -118,7 +118,7 @@
     /**
      * The maximum number of blocks to flush in one cycle.
      *
-     * @attention while it is guaranteed that cxBufferFlush() will not flush
+     * @attention While it is guaranteed that cxBufferFlush() will not flush
      * more blocks, this is not necessarily the case for cxBufferWrite().
      * After performing a flush cycle, cxBufferWrite() will retry the write
      * operation and potentially trigger another flush cycle, until the
@@ -388,6 +388,20 @@
 CX_EXPORT int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence);
 
 /**
+ * Discards items from the end of the buffer.
+ *
+ * When the current position points to a byte that gets discarded,
+ * the position is set to the buffer size.
+ *
+ * @param buffer the buffer
+ * @param size the size of one item
+ * @param nitems the number of items to discard
+ * @return the actual number of discarded items
+ */
+cx_attr_nonnull
+CX_EXPORT size_t cxBufferPop(CxBuffer *buffer, size_t size, size_t nitems);
+
+/**
  * Clears the buffer by resetting the position and deleting the data.
  *
  * The data is deleted by zeroing it with a call to memset().
@@ -425,11 +439,30 @@
 cx_attr_nonnull cx_attr_nodiscard
 CX_EXPORT bool cxBufferEof(const CxBuffer *buffer);
 
+/**
+ * Ensures that the buffer has the required capacity.
+ *
+ * If the current capacity is not sufficient, the buffer will be extended.
+ *
+ * This function will reserve no more bytes than requested, in contrast to
+ * cxBufferMinimumCapacity(), which may reserve more bytes to improve the
+ * number of future necessary reallocations.
+ *
+ * @param buffer the buffer
+ * @param capacity the required capacity for this buffer
+ * @retval zero the capacity was already sufficient or successfully increased
+ * @retval non-zero on allocation failure
+ * @see cxBufferShrink()
+ * @see cxBufferMinimumCapacity()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity);
 
 /**
  * Ensures that the buffer has a minimum capacity.
  *
- * If the current capacity is not sufficient, the buffer will be extended.
+ * If the current capacity is not sufficient, the buffer will be generously
+ * extended.
  *
  * The new capacity will be a power of two until the system's page size is reached.
  * Then, the new capacity will be a multiple of the page size.
@@ -438,6 +471,7 @@
  * @param capacity the minimum required capacity for this buffer
  * @retval zero the capacity was already sufficient or successfully increased
  * @retval non-zero on allocation failure
+ * @see cxBufferReserve()
  * @see cxBufferShrink()
  */
 cx_attr_nonnull
@@ -457,6 +491,7 @@
  *
  * @param buffer the buffer
  * @param reserve the number of bytes that shall remain reserved
+ * @see cxBufferReserve()
  * @see cxBufferMinimumCapacity()
  */
 cx_attr_nonnull
--- a/src/ucx/cx/common.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/common.h	Sun Nov 30 18:25:55 2025 +0100
@@ -284,6 +284,9 @@
  */
 #define CX_INLINE __attribute__((always_inline)) static inline
 #else
+/**
+ * Declares a function to be inlined.
+ */
 #define CX_INLINE static inline
 #endif
 /**
--- a/src/ucx/cx/json.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/json.h	Sun Nov 30 18:25:55 2025 +0100
@@ -556,7 +556,7 @@
  * @retval non-zero internal allocation error
  * @see cxJsonFill()
  */
-cx_attr_nonnull cx_attr_access_r(2, 3)
+cx_attr_nonnull_arg(1) cx_attr_access_r(2, 3)
 CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len);
 
 
@@ -641,28 +641,27 @@
 /**
  * Creates a new JSON string.
  *
+ * Internal function - use cxJsonCreateString() instead.
+ *
  * @param allocator the allocator to use
  * @param str the string data
  * @return the new JSON value or @c NULL if allocation fails
- * @see cxJsonCreateString()
  * @see cxJsonObjPutString()
- * @see cxJsonArrAddStrings()
+ * @see cxJsonArrAddCxStrings()
  */
-cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2)
-CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
+cx_attr_nodiscard
+CX_EXPORT CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str);
 
 /**
  * Creates a new JSON string.
  *
- * @param allocator the allocator to use
- * @param str the string data
- * @return the new JSON value or @c NULL if allocation fails
- * @see cxJsonCreateCxString()
- * @see cxJsonObjPutCxString()
+ * @param allocator (@c CxAllocator*) the allocator to use
+ * @param str the string
+ * @return (@c CxJsonValue*) the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutString()
  * @see cxJsonArrAddCxStrings()
  */
-cx_attr_nodiscard
-CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
+#define cxJsonCreateString(allocator, str) cx_json_create_string(allocator, cx_strcast(str))
 
 /**
  * Creates a new JSON literal.
@@ -760,10 +759,7 @@
 /**
  * Adds or replaces a value within a JSON object.
  *
- * The value will be directly added and not copied.
- *
- * @note If a value with the specified @p name already exists,
- * it will be (recursively) freed with its own allocator.
+ * Internal function - use cxJsonObjPut().
  *
  * @param obj the JSON object
  * @param name the name of the value
@@ -772,11 +768,29 @@
  * @retval non-zero allocation failure
  */
 cx_attr_nonnull
-CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+CX_EXPORT int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+
+/**
+ * Adds or replaces a value within a JSON object.
+ *
+ * The value will be directly added and not copied.
+ *
+ * @note If a value with the specified @p name already exists,
+ * it will be (recursively) freed with its own allocator.
+ *
+ * @param obj (@c CxJsonValue*) the JSON object
+ * @param name (any string) the name of the value
+ * @param child (@c CxJsonValue*) the value
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+#define cxJsonObjPut(obj, name, child) cx_json_obj_put(obj, cx_strcast(name), child)
 
 /**
  * Creates a new JSON object and adds it to an existing object.
  *
+ * Internal function - use cxJsonObjPutObj().
+ *
  * @param obj the target JSON object
  * @param name the name of the new value
  * @return the new value or @c NULL if allocation fails
@@ -784,11 +798,24 @@
  * @see cxJsonCreateObj()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
+CX_EXPORT CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON object and adds it to an existing object.
+ *
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateObj()
+ */
+#define cxJsonObjPutObj(obj, name) cx_json_obj_put_obj(obj, cx_strcast(name))
 
 /**
  * Creates a new JSON array and adds it to an object.
  *
+ * Internal function - use cxJsonObjPutArr().
+ *
  * @param obj the target JSON object
  * @param name the name of the new value
  * @return the new value or @c NULL if allocation fails
@@ -796,11 +823,24 @@
  * @see cxJsonCreateArr()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
+CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON array and adds it to an object.
+ *
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateArr()
+ */
+#define cxJsonObjPutArr(obj, name) cx_json_obj_put_arr(obj, cx_strcast(name))
 
 /**
  * Creates a new JSON number and adds it to an object.
  *
+ * Internal function - use cxJsonObjPutNumber().
+ *
  * @param obj the target JSON object
  * @param name the name of the new value
  * @param num the numeric value
@@ -809,11 +849,25 @@
  * @see cxJsonCreateNumber()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
+CX_EXPORT CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num);
+
+/**
+ * Creates a new JSON number and adds it to an object.
+ *
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @param num (@c double) the numeric value
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateNumber()
+ */
+#define cxJsonObjPutNumber(obj, name, num) cx_json_obj_put_number(obj, cx_strcast(name), num)
 
 /**
  * Creates a new JSON number, based on an integer, and adds it to an object.
  *
+ * Internal function - use cxJsonObjPutInteger().
+ *
  * @param obj the target JSON object
  * @param name the name of the new value
  * @param num the numeric value
@@ -822,12 +876,24 @@
  * @see cxJsonCreateInteger()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
+CX_EXPORT CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num);
+
+/**
+ * Creates a new JSON number, based on an integer, and adds it to an object.
+ *
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @param num (@c int64_t) the numeric value
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateInteger()
+ */
+#define cxJsonObjPutInteger(obj, name, num) cx_json_obj_put_integer(obj, cx_strcast(name), num)
 
 /**
  * Creates a new JSON string and adds it to an object.
  *
- * The string data is copied.
+ * Internal function - use cxJsonObjPutString()
  *
  * @param obj the target JSON object
  * @param name the name of the new value
@@ -836,27 +902,28 @@
  * @see cxJsonObjPut()
  * @see cxJsonCreateString()
  */
-cx_attr_nonnull cx_attr_cstr_arg(3)
-CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
+cx_attr_nonnull
+CX_EXPORT CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str);
 
 /**
  * Creates a new JSON string and adds it to an object.
  *
  * The string data is copied.
  *
- * @param obj the target JSON object
- * @param name the name of the new value
- * @param str the string data
- * @return the new value or @c NULL if allocation fails
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @param str (any string) the string data
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
  * @see cxJsonObjPut()
- * @see cxJsonCreateCxString()
+ * @see cxJsonCreateString()
  */
-cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
+#define cxJsonObjPutString(obj, name, str) cx_json_obj_put_string(obj, cx_strcast(name), cx_strcast(str))
 
 /**
  * Creates a new JSON literal and adds it to an object.
  *
+ * Internal function - use cxJsonObjPutLiteral().
+ *
  * @param obj the target JSON object
  * @param name the name of the new value
  * @param lit the type of literal
@@ -865,7 +932,19 @@
  * @see cxJsonCreateLiteral()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+CX_EXPORT CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+
+/**
+ * Creates a new JSON literal and adds it to an object.
+ *
+ * @param obj (@c CxJsonValue*) the target JSON object
+ * @param name (any string) the name of the new value
+ * @param lit (@c CxJsonLiteral) the type of literal
+ * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateLiteral()
+ */
+#define cxJsonObjPutLiteral(obj, name, lit) cx_json_obj_put_literal(obj, cx_strcast(name), lit)
 
 /**
  * Recursively deallocates the memory of a JSON value.
@@ -1188,6 +1267,20 @@
 CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value);
 
 /**
+ * Returns the size of a JSON object.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the size of the object, i.e., the number of key/value pairs
+ * @see cxJsonIsObject()
+ */
+cx_attr_nonnull
+CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) {
+    return value->value.object.values_size;
+}
+
+/**
  * Returns an iterator over the JSON object members.
  *
  * The iterator yields values of type @c CxJsonObjValue* which
--- a/src/ucx/cx/linked_list.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/linked_list.h	Sun Nov 30 18:25:55 2025 +0100
@@ -62,7 +62,14 @@
      */
     off_t loc_data;
     /**
+     * Location of extra data (optional).
+     * Negative when no extra data is requested.
+     * @see cx_linked_list_extra_data()
+     */
+    off_t loc_extra;
+    /**
      * Additional bytes to allocate @em behind the payload (e.g. for metadata).
+     * @see cx_linked_list_extra_data()
      */
     size_t extra_data_len;
     /**
@@ -112,6 +119,23 @@
         cxLinkedListCreate(NULL, NULL, elem_size)
 
 /**
+ * Instructs the linked list to reserve extra data in each node.
+ *
+ * The extra data will be aligned and placed behind the element data.
+ * The exact location in the node is stored in the @c loc_extra field
+ * of the linked list.
+ *
+ * You should usually not use this function except when you are creating an
+ * own linked-list implementation that is based on the UCX linked list and
+ * needs to store extra data in each node.
+ *
+ * @param list the list (must be a linked list)
+ * @param len the length of the extra data
+ */
+cx_attr_nonnull
+CX_EXPORT void cx_linked_list_extra_data(cx_linked_list *list, size_t len);
+
+/**
  * Finds the node at a certain index.
  *
  * This function can be used to start at an arbitrary position within the list.
--- a/src/ucx/cx/list.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/list.h	Sun Nov 30 18:25:55 2025 +0100
@@ -170,6 +170,12 @@
     void (*reverse)(struct cx_list_s *list);
 
     /**
+     * Optional member function for changing the capacity.
+     * If the list does not support overallocation, this can be set to @c NULL.
+     */
+    int (*change_capacity)(struct cx_list_s *list, size_t new_capacity);
+
+    /**
      * Member function for returning an iterator pointing to the specified index.
      */
     struct cx_iterator_s (*iterator)(const struct cx_list_s *list, size_t index, bool backward);
@@ -1147,6 +1153,40 @@
 cx_attr_nonnull
 CX_EXPORT int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other);
 
+/**
+ * Asks the list to reserve enough memory for a given total number of elements.
+ *
+ * List implementations are free to choose if reserving memory upfront makes
+ * sense.
+ * For example, array-based implementations usually do support reserving memory
+ * for additional elements while linked lists usually don't.
+ *
+ * @note When the requested capacity is smaller than the current size,
+ * this function returns zero without performing any action.
+ *
+ * @param list the list
+ * @param capacity the expected total number of elements
+ * @retval zero on success or when overallocation is not supported
+ * @retval non-zero when an allocation error occurred
+ * @see cxListShrink()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListReserve(CxList *list, size_t capacity);
+
+/**
+ * Advises the list to free any overallocated memory.
+ *
+ * Lists that do not support overallocation simply return zero.
+ *
+ * This function usually returns zero, except for very special and custom
+ * list and/or allocator implementations where freeing memory can fail.
+ *
+ * @param list the list
+ * @return usually zero
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListShrink(CxList *list);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
--- a/src/ucx/cx/string.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/string.h	Sun Nov 30 18:25:55 2025 +0100
@@ -256,7 +256,7 @@
 }
 cx_attr_nodiscard
 CX_CPPDECL cxstring cx_strcast(const unsigned char *str) {
-    return cx_str(static_cast<const char*>(str));
+    return cx_str(reinterpret_cast<const char*>(str));
 }
 extern "C" {
 #else
--- a/src/ucx/cx/tree.h	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/cx/tree.h	Sun Nov 30 18:25:55 2025 +0100
@@ -88,6 +88,7 @@
     ptrdiff_t loc_next;
     /**
      * The total number of distinct nodes that have been passed so far.
+     * This includes the current node.
      */
     size_t counter;
     /**
@@ -185,6 +186,7 @@
     ptrdiff_t loc_next;
     /**
      * The total number of distinct nodes that have been passed so far.
+     * This includes the currently visited node.
      */
     size_t counter;
     /**
@@ -1094,7 +1096,7 @@
  * @see cxTreeIterate()
  */
 cx_attr_nonnull cx_attr_nodiscard
-CxTreeVisitor cxTreeVisit(CxTree *tree);
+CX_EXPORT CxTreeVisitor cxTreeVisit(CxTree *tree);
 
 /**
  * Sets the (new) parent of the specified child.
--- a/src/ucx/hash_map.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/hash_map.c	Sun Nov 30 18:25:55 2025 +0100
@@ -117,7 +117,7 @@
                 allocator,
                 sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
         );
-        if (e == NULL) return NULL;
+        if (e == NULL) return NULL; // LCOV_EXCL_LINE
 
         // write the value
         if (value == NULL) {
@@ -130,10 +130,10 @@
 
         // copy the key
         void *kd = cxMalloc(allocator, key.len);
-        if (kd == NULL) {
+        if (kd == NULL) { // LCOV_EXCL_START
             cxFree(allocator, e);
             return NULL;
-        }
+        } // LCOV_EXCL_STOP
         memcpy(kd, key.data, key.len);
         e->key.data = kd;
         e->key.len = key.len;
@@ -447,15 +447,16 @@
 
         size_t new_bucket_count = (map->collection.size * 5) >> 1;
         if (new_bucket_count < hash_map->bucket_count) {
+            // LCOV_EXCL_START
             errno = EOVERFLOW;
             return 1;
-        }
+        } // LCOV_EXCL_STOP
         struct cx_hash_map_element_s **new_buckets = cxCalloc(
                 map->collection.allocator,
                 new_bucket_count, sizeof(struct cx_hash_map_element_s *)
         );
 
-        if (new_buckets == NULL) return 1;
+        if (new_buckets == NULL) return 1; // LCOV_EXCL_LINE
 
         // iterate through the elements and assign them to their new slots
         for (size_t slot = 0; slot < hash_map->bucket_count; slot++) {
--- a/src/ucx/json.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/json.c	Sun Nov 30 18:25:55 2025 +0100
@@ -94,7 +94,7 @@
     size_t newcap = obj->values_capacity;
     if (newcap > oldcap) {
         if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) {
-            return 1;
+            return 1; // LCOV_EXCL_LINE
         }
     }
 
@@ -437,7 +437,7 @@
             } else if (c == 'u') {
                 char utf8buf[4];
                 unsigned utf8len = unescape_unicode_string(
-                    cx_strn(str.ptr + i - 1, str.length + 1 - i),
+                    cx_strn(str.ptr + i - 1, str.length - i),
                     utf8buf
                 );
                 if(utf8len > 0) {
@@ -456,7 +456,7 @@
             } else {
                 // TODO: discuss the behavior for unrecognized escape sequences
                 //       most parsers throw an error here - we just ignore it
-                result.ptr[result.length++] = '\\';
+                result.ptr[result.length++] = '\\'; // LCOV_EXCL_LINE
             }
 
             result.ptr[result.length++] = c;
@@ -640,6 +640,7 @@
     if (cxBufferEof(&json->buffer)) {
         // reinitialize the buffer
         cxBufferDestroy(&json->buffer);
+        if (buf == NULL) buf = ""; // buffer must not be initialized with NULL
         cxBufferInit(&json->buffer, (char*) buf, size,
             NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE);
         json->buffer.size = size;
@@ -734,7 +735,8 @@
                     }
                 } else {
                     if (cx_strtod(token.content, &vbuf->value.number)) {
-                        return_rec(CX_JSON_FORMAT_ERROR_NUMBER);
+                        // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod()
+                        return_rec(CX_JSON_FORMAT_ERROR_NUMBER);  // LCOV_EXCL_LINE
                     }
                 }
                 return_rec(CX_JSON_NO_ERROR);
@@ -815,19 +817,19 @@
     } else {
         // should be unreachable
         assert(false);
-        return_rec(-1);
+        return_rec(-1); // LCOV_EXCL_LINE
     }
 }
 
 CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) {
-    // check if buffer has been filled
+    // initialize output value
+    *value = &cx_json_value_nothing;
+
+    // check if the buffer has been filled
     if (json->buffer.space == NULL) {
         return CX_JSON_NULL_DATA;
     }
 
-    // initialize output value
-    *value = &cx_json_value_nothing;
-
     // parse data
     CxJsonStatus result;
     do {
@@ -943,11 +945,7 @@
     return v;
 }
 
-CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) {
-    return cxJsonCreateCxString(allocator, cx_str(str));
-}
-
-CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) {
+CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) {
     if (allocator == NULL) allocator = cxDefaultAllocator;
     CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
     if (v == NULL) return NULL;
@@ -1020,7 +1018,7 @@
     CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
     if (values == NULL) return -1;
     for (size_t i = 0; i < count; i++) {
-        values[i] = cxJsonCreateCxString(arr->allocator, str[i]);
+        values[i] = cxJsonCreateString(arr->allocator, str[i]);
         if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
     }
     int ret = cxJsonArrAddValues(arr, values, count);
@@ -1050,61 +1048,56 @@
     );
 }
 
-int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
+int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
     cxmutstr k = cx_strdup_a(obj->allocator, name);
     if (k.ptr == NULL) return -1;
     CxJsonObjValue kv = {k, child};
     if (json_add_objvalue(obj, kv)) {
+        // LCOV_EXCL_START
         cx_strfree_a(obj->allocator, &k);
         return 1;
+        // LCOV_EXCL_STOP
     } else {
         return 0;
     }
 }
 
-CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) {
+CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) {
     CxJsonValue* v = cxJsonCreateObj(obj->allocator);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
 }
 
-CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) {
+CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) {
     CxJsonValue* v = cxJsonCreateArr(obj->allocator);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
 }
 
-CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) {
+CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num) {
     CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
 }
 
-CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) {
+CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num) {
     CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
 }
 
-CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) {
+CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str) {
     CxJsonValue* v = cxJsonCreateString(obj->allocator, str);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
 }
 
-CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) {
-    CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str);
-    if (v == NULL) return NULL;
-    if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
-    return v;
-}
-
-CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
+CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
     CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;}
@@ -1286,9 +1279,6 @@
                                       ? look_idx
                                       : value->value.object.indices[look_idx];
                 CxJsonObjValue *member = &value->value.object.values[elem_idx];
-                if (settings->sort_members) {
-                    depth++;depth--;
-                }
 
                 // possible indentation
                 if (settings->pretty) {
@@ -1350,7 +1340,9 @@
                 if (cx_json_write_rec(
                         target, element,
                         wfunc, settings, depth)
-                ) return 1;
+                ) {
+                    return 1; // LCOV_EXCL_LINE
+                }
 
                 if (iter.index < iter.elem_count - 1) {
                     const char *arr_value_sep = ", ";
--- a/src/ucx/kv_list.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/kv_list.c	Sun Nov 30 18:25:55 2025 +0100
@@ -98,7 +98,7 @@
 }
 
 static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) {
-    return (CxHashKey*)((char*)node_data + list->list.base.collection.elem_size);
+    return (CxHashKey*)((char*)node_data - list->list.loc_data + list->list.loc_extra);
 }
 
 static void cx_kvl_deallocate(struct cx_list_s *list) {
@@ -509,6 +509,15 @@
     return iter;
 }
 
+static int cx_kvl_change_capacity(struct cx_list_s *list,
+        cx_attr_unused size_t cap) {
+    // since our backing list is a linked list, we don't need to do much here,
+    // but rehashing the map is quite useful
+    cx_kv_list *kv_list = (cx_kv_list*)list;
+    cxMapRehash(&kv_list->map->map_base.base);
+    return 0;
+}
+
 static cx_list_class cx_kv_list_class = {
     cx_kvl_deallocate,
     cx_kvl_insert_element,
@@ -524,6 +533,7 @@
     cx_kvl_sort,
     NULL,
     cx_kvl_reverse,
+    cx_kvl_change_capacity,
     cx_kvl_iterator,
 };
 
@@ -549,7 +559,7 @@
     CxList *list = cxLinkedListCreate(allocator, comparator, elem_size);
     if (list == NULL) return NULL; // LCOV_EXCL_LINE
     cx_linked_list *ll = (cx_linked_list*)list;
-    ll->extra_data_len = sizeof(CxHashKey);
+    cx_linked_list_extra_data(ll, sizeof(CxHashKey));
     CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0);
     if (map == NULL) { // LCOV_EXCL_START
         cxListFree(list);
--- a/src/ucx/linked_list.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/linked_list.c	Sun Nov 30 18:25:55 2025 +0100
@@ -31,6 +31,15 @@
 #include <string.h>
 #include <assert.h>
 
+#if __STDC_VERSION__ < 202311L
+// we cannot simply include stdalign.h
+// because Solaris is not entirely C11 complaint
+#ifndef __alignof_is_defined
+#define alignof _Alignof
+#define __alignof_is_defined 1
+#endif
+#endif
+
 // LOW LEVEL LINKED LIST FUNCTIONS
 
 #define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
@@ -516,7 +525,7 @@
     void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
     void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
                     cxMallocDefault(sizeof(void *) * length) : sbo;
-    if (sorted == NULL) abort();
+    if (sorted == NULL) abort(); // LCOV_EXCL_LINE
     void *rc, *lc;
 
     lc = ls;
@@ -692,8 +701,13 @@
 }
 
 static void *cx_ll_malloc_node(const cx_linked_list *list) {
-    return cxZalloc(list->base.collection.allocator,
-                    list->loc_data + list->base.collection.elem_size + list->extra_data_len);
+    size_t n;
+    if (list->extra_data_len == 0) {
+        n = list->loc_data + list->base.collection.elem_size;
+    } else {
+        n = list->loc_extra + list->extra_data_len;
+    }
+    return cxZalloc(list->base.collection.allocator, n);
 }
 
 static int cx_ll_insert_at(
@@ -1215,7 +1229,7 @@
         return result;
     } else {
         if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) {
-            return 1;
+            return 1; // LCOV_EXCL_LINE
         }
         iter->elem_count++;
         iter->index = list->collection.size;
@@ -1252,6 +1266,7 @@
         cx_ll_sort,
         cx_ll_compare,
         cx_ll_reverse,
+        NULL, // no overallocation supported
         cx_ll_iterator,
 };
 
@@ -1266,12 +1281,22 @@
 
     cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
     if (list == NULL) return NULL;
-    list->extra_data_len = 0;
     list->loc_prev = 0;
     list->loc_next = sizeof(void*);
     list->loc_data = sizeof(void*)*2;
+    list->loc_extra = -1;
+    list->extra_data_len = 0;
     cx_list_init((CxList*)list, &cx_linked_list_class,
             allocator, comparator, elem_size);
 
     return (CxList *) list;
 }
+
+void cx_linked_list_extra_data(cx_linked_list *list, size_t len) {
+    list->extra_data_len = len;
+
+    off_t loc_extra = list->loc_data + list->base.collection.elem_size;
+    size_t alignment = alignof(void*);
+    size_t padding = alignment - (loc_extra % alignment);
+    list->loc_extra = loc_extra + padding;
+}
--- a/src/ucx/list.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/list.c	Sun Nov 30 18:25:55 2025 +0100
@@ -185,6 +185,14 @@
     return ptr == NULL ? NULL : *ptr;
 }
 
+static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) {
+    if (list->climpl->change_capacity == NULL) {
+        return 0;
+    } else {
+        return list->climpl->change_capacity(list, cap);
+    }
+}
+
 static struct cx_iterator_s cx_pl_iterator(
         const struct cx_list_s *list,
         size_t index,
@@ -211,6 +219,7 @@
         cx_pl_sort,
         cx_pl_compare,
         cx_pl_reverse,
+        cx_pl_change_capacity,
         cx_pl_iterator,
 };
 // </editor-fold>
@@ -267,6 +276,7 @@
         cx_emptyl_noop,
         NULL,
         cx_emptyl_noop,
+        NULL,
         cx_emptyl_iterator,
 };
 
@@ -1020,7 +1030,7 @@
         CxIterator src_iter = cxListIterator(src);
         CxIterator other_iter = cxListIterator(other);
         while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) {
-            void *src_elem, *other_elem;
+            void *src_elem = NULL, *other_elem = NULL;
             int d;
             if (!cxIteratorValid(src_iter)) {
                 other_elem = cxIteratorCurrent(other_iter);
@@ -1102,3 +1112,20 @@
 int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other) {
     return cxListUnion(dst, src, other, use_simple_clone_func(src));
 }
+
+int cxListReserve(CxList *list, size_t capacity) {
+    if (list->cl->change_capacity == NULL) {
+        return 0;
+    }
+    if (capacity <= cxCollectionSize(list)) {
+        return 0;
+    }
+    return list->cl->change_capacity(list, capacity);
+}
+
+int cxListShrink(CxList *list) {
+    if (list->cl->change_capacity == NULL) {
+        return 0;
+    }
+    return list->cl->change_capacity(list, cxCollectionSize(list));
+}
--- a/src/ucx/printf.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/printf.c	Sun Nov 30 18:25:55 2025 +0100
@@ -61,8 +61,10 @@
     va_copy(ap2, ap);
     int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
     if (ret < 0) {
+        // LCOV_EXCL_START
         va_end(ap2);
         return ret;
+        // LCOV_EXCL_STOP
     } else if (ret < CX_PRINTF_SBO_SIZE) {
         va_end(ap2);
         return (int) wfc(buf, 1, ret, stream);
@@ -121,8 +123,10 @@
         if (s.ptr) {
             ret = vsnprintf(s.ptr, len, fmt, ap2);
             if (ret < 0) {
+                // LCOV_EXCL_START
                 cxFree(a, s.ptr);
                 s.ptr = NULL;
+                // LCOV_EXCL_STOP
             } else {
                 s.length = (size_t) ret;
             }
@@ -162,7 +166,7 @@
         if (ptr) {
             int newret = vsnprintf(ptr, newlen, fmt, ap2);
             if (newret < 0) {
-                cxFree(alloc, ptr);
+                cxFree(alloc, ptr); // LCOV_EXCL_LINE
             } else {
                 *len = newlen;
                 *str = ptr;
@@ -207,7 +211,7 @@
         if (ptr) {
             int newret = vsnprintf(ptr, newlen, fmt, ap2);
             if (newret < 0) {
-                cxFree(alloc, ptr);
+                cxFree(alloc, ptr); // LCOV_EXCL_LINE
             } else {
                 *len = newlen;
                 *str = ptr;
--- a/src/ucx/properties.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/properties.c	Sun Nov 30 18:25:55 2025 +0100
@@ -120,7 +120,7 @@
             // still not enough data, copy input buffer to internal buffer
             if (cxBufferAppend(input.ptr, 1,
                 input.length, &prop->buffer) < input.length) {
-                return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+                return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
             }
             // reset the input buffer (make way for a re-fill)
             cxBufferReset(&prop->input);
@@ -360,7 +360,7 @@
     // initialize reader
     if (source.read_init_func != NULL) {
         if (source.read_init_func(prop, &source)) {
-            return CX_PROPERTIES_READ_INIT_FAILED;
+            return CX_PROPERTIES_READ_INIT_FAILED;  // LCOV_EXCL_LINE
         }
     }
 
@@ -371,10 +371,10 @@
     while (true) {
         // read input
         cxstring input;
-        if (source.read_func(prop, &source, &input)) {
+        if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START
             status = CX_PROPERTIES_READ_FAILED;
             break;
-        }
+        } // LCOV_EXCL_STOP
 
         // no more data - break
         if (input.length == 0) {
@@ -401,7 +401,7 @@
             if (kv_status == CX_PROPERTIES_NO_ERROR) {
                 found = true;
                 if (sink.sink_func(prop, &sink, key, value)) {
-                    kv_status = CX_PROPERTIES_SINK_FAILED;
+                    kv_status = CX_PROPERTIES_SINK_FAILED;  // LCOV_EXCL_LINE
                 }
             }
         } while (kv_status == CX_PROPERTIES_NO_ERROR);
--- a/src/ucx/string.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/string.c	Sun Nov 30 18:25:55 2025 +0100
@@ -91,7 +91,7 @@
         cxstring src
 ) {
     if (cxReallocate(alloc, &dest->ptr, src.length + 1)) {
-        return 1;
+        return 1; // LCOV_EXCL_LINE
     }
 
     memcpy(dest->ptr, src.ptr, src.length);
@@ -137,7 +137,7 @@
     size_t slen = str.length;
     for (size_t i = 0; i < count; i++) {
         cxstring s = va_arg(ap, cxstring);
-        if (slen > SIZE_MAX - str.length) overflow = true;
+        if (slen > SIZE_MAX - s.length) overflow = true;
         slen += s.length;
     }
     va_end(ap);
@@ -156,10 +156,10 @@
     } else {
         newstr = cxRealloc(alloc, str.ptr, slen + 1);
     }
-    if (newstr == NULL) {
+    if (newstr == NULL) { // LCOV_EXCL_START
         va_end(ap2);
         return (cxmutstr) {NULL, 0};
-    }
+    } // LCOV_EXCL_STOP
     str.ptr = newstr;
 
     // concatenate strings
@@ -521,10 +521,12 @@
             cxMalloc(allocator, string.length + 1),
             string.length
     };
+    // LCOV_EXCL_START
     if (result.ptr == NULL) {
         result.length = 0;
         return result;
     }
+    // LCOV_EXCL_STOP
     memcpy(result.ptr, string.ptr, string.length);
     result.ptr[string.length] = '\0';
     return result;
--- a/src/ucx/tree.c	Sat Nov 29 19:03:52 2025 +0100
+++ b/src/ucx/tree.c	Sun Nov 30 18:25:55 2025 +0100
@@ -566,7 +566,7 @@
         ptrdiff_t loc_next
 ) {
     *cnode = cfunc(src, cdata);
-    if (*cnode == NULL) return 1;
+    if (*cnode == NULL) return 1;  // LCOV_EXCL_LINE
     cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);
 
     void *match = NULL;
@@ -627,7 +627,7 @@
 
         // create the new node
         void *new_node = cfunc(elem, cdata);
-        if (new_node == NULL) return processed;
+        if (new_node == NULL) return processed;  // LCOV_EXCL_LINE
         cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);
 
         // start searching from current node
@@ -731,7 +731,7 @@
     void *node;
     if (tree->root == NULL) {
         node = tree->node_create(data, tree);
-        if (node == NULL) return 1;
+        if (node == NULL) return 1;  // LCOV_EXCL_LINE
         cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
         tree->root = node;
         tree->size = 1;
@@ -758,7 +758,7 @@
         // use the first element from the iter to create the root node
         void **eptr = iter->current(iter);
         void *node = tree->node_create(*eptr, tree);
-        if (node == NULL) return 0;
+        if (node == NULL) return 0;  // LCOV_EXCL_LINE
         cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
         tree->root = node;
         ins = 1;
@@ -780,7 +780,7 @@
         const void *data,
         size_t depth
 ) {
-    if (tree->root == NULL) return NULL;
+    if (tree->root == NULL) return NULL;  // LCOV_EXCL_LINE
 
     void *found;
     if (0 == cx_tree_search_data(
@@ -819,7 +819,7 @@
     assert(search_data_func != NULL);
 
     CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
-    if (tree == NULL) return NULL;
+    if (tree == NULL) return NULL;  // LCOV_EXCL_LINE
 
     tree->cl = &cx_tree_default_class;
     tree->allocator = allocator;
@@ -857,7 +857,7 @@
     assert(root != NULL);
 
     CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
-    if (tree == NULL) return NULL;
+    if (tree == NULL) return NULL;  // LCOV_EXCL_LINE
 
     tree->cl = &cx_tree_default_class;
     // set the allocator anyway, just in case...
@@ -893,7 +893,7 @@
 
 int cxTreeAddChild(CxTree *tree, void *parent, const void *data) {
     void *node = tree->node_create(data, tree);
-    if (node == NULL) return 1;
+    if (node == NULL) return 1; // LCOV_EXCL_LINE
     cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
     cx_tree_link(parent, node, cx_tree_node_layout(tree));
     tree->size++;
@@ -1071,6 +1071,7 @@
         cxFreeDefault(q);
         q = next;
     }
+    visitor->queue_next = visitor->queue_last = NULL;
 }
 
 CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit) {

mercurial