Sun, 30 Nov 2025 18:17:49 +0100
update ucx to version 3.2
--- a/ucx/array_list.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/array_list.c Sun Nov 30 18:17:49 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; @@ -102,16 +104,28 @@ // LOW LEVEL ARRAY LIST FUNCTIONS -static size_t cx_array_align_capacity( - size_t cap, - size_t alignment, - size_t max +/** + * Intelligently calculates a new capacity, reserving some more + * elements than required to prevent too many allocations. + * + * @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_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( @@ -176,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 ); @@ -269,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 @@ -361,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 @@ -384,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 @@ -410,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) { @@ -501,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) { @@ -588,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, @@ -633,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 @@ -654,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, @@ -679,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; } } @@ -781,14 +802,13 @@ // guarantee enough capacity if (arl->capacity < list->collection.size + n) { - size_t new_capacity = list->collection.size + n; - new_capacity = new_capacity - (new_capacity % 16) + 16; + 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; } @@ -832,7 +852,7 @@ &arl->reallocator )) { // array list implementation is "all or nothing" - return 0; + return 0; // LCOV_EXCL_LINE } else { return n; } @@ -857,7 +877,7 @@ &arl->reallocator )) { // array list implementation is "all or nothing" - return 0; + return 0; // LCOV_EXCL_LINE } else { return n; } @@ -884,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) { @@ -894,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; @@ -1129,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, @@ -1166,6 +1194,7 @@ cx_arl_sort, cx_arl_compare, cx_arl_reverse, + cx_arl_change_capacity, cx_arl_iterator, };
--- a/ucx/buffer.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/buffer.c Sun Nov 30 18:17:49 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/ucx/cx/array_list.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/array_list.h Sun Nov 30 18:17:49 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/ucx/cx/buffer.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/buffer.h Sun Nov 30 18:17:49 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/ucx/cx/collection.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/collection.h Sun Nov 30 18:17:49 2025 +0100 @@ -154,6 +154,14 @@ #define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0) /** + * Sets the compare function for a collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param func (@c cx_compare_func) the compare function that shall be used by @c c + */ +#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func) + +/** * Sets a simple destructor function for this collection. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
--- a/ucx/cx/common.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/common.h Sun Nov 30 18:17:49 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/ucx/cx/hash_key.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/hash_key.h Sun Nov 30 18:17:49 2025 +0100 @@ -227,12 +227,14 @@ /** * Compare function for hash keys. * - * @param left the first key - * @param right the second key + * The pointers are untyped to be compatible with the cx_compare_func signature. + * + * @param left (@c CxHashKey*) the first key + * @param right (@c CxHashKey*) the second key * @return zero when the keys equal, non-zero when they differ */ cx_attr_nodiscard cx_attr_nonnull -CX_EXPORT int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right); +CX_EXPORT int cx_hash_key_cmp(const void *left, const void *right); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/json.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/json.h Sun Nov 30 18:17:49 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/ucx/cx/linked_list.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/linked_list.h Sun Nov 30 18:17:49 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/ucx/cx/list.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/list.h Sun Nov 30 18:17:49 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); @@ -961,6 +967,226 @@ CX_EXPORT void cxListFree(CxList *list); +/** + * Performs a deep clone of one list into another. + * + * If the destination list already contains elements, the cloned elements + * are appended to that list. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination list also uses this + * destructor function. + * + * @param dst the destination list + * @param src the source list + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListCloneSimple() + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxListClone(CxList *dst, const CxList *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are not present in another list. + * + * If the @p minuend does not contain duplicates, this is equivalent to adding + * the set difference to the destination list. + * + * This function is optimized for the case when both the @p minuend and the + * @p subtrahend are sorted. + * + * @param dst the destination list + * @param minuend the list to subtract elements from + * @param subtrahend the elements that shall be subtracted + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListDifferenceSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are also present in another list. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * + * If the destination list already contains elements, the intersection is appended + * to that list. + * + * @param dst the destination list + * @param src the list to clone the elements from + * @param other the list to check the elements for existence + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListIntersectionSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a deep clone of one list into another, skipping duplicates. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * In that case, the union will also be sorted. + * + * If the destination list already contains elements, the union is appended + * to that list. In that case the destination is not necessarily sorted. + * + * @param dst the destination list + * @param src the primary source list + * @param other the other list, where elements are only cloned from + * when they are not in @p src + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListUnionSimple() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a shallow clone of one list into another. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the destination list already contains elements, the cloned elements + * are appended to that list. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination list also uses this + * destructor function. + * + * @param dst the destination list + * @param src the source list + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListClone() + */ +cx_attr_nonnull +CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src); + +/** + * Clones elements from a list only if they are not present in another list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the @p minuend does not contain duplicates, this is equivalent to adding + * the set difference to the destination list. + * + * This function is optimized for the case when both the @p minuend and the + * @p subtrahend are sorted. + * + * @param dst the destination list + * @param minuend the list to subtract elements from + * @param subtrahend the elements that shall be subtracted + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListDifference() + */ +cx_attr_nonnull +CX_EXPORT int cxListDifferenceSimple(CxList *dst, + const CxList *minuend, const CxList *subtrahend); + +/** + * Clones elements from a list only if they are also present in another list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * + * If the destination list already contains elements, the intersection is appended + * to that list. + * + * @param dst the destination list + * @param src the list to clone the elements from + * @param other the list to check the elements for existence + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListIntersection() + */ +cx_attr_nonnull +CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other); + +/** + * Performs a deep clone of one list into another, skipping duplicates. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * In that case, the union will also be sorted. + * + * If the destination list already contains elements, the union is appended + * to that list. In that case the destination is not necessarily sorted. + * + * @param dst the destination list + * @param src the primary source list + * @param other the other list, where elements are only cloned from + * when they are not in @p src + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListUnion() + */ +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/ucx/cx/map.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/map.h Sun Nov 30 18:17:49 2025 +0100 @@ -41,6 +41,11 @@ #include "string.h" #include "hash_key.h" +#ifndef UCX_LIST_H +// forward-declare CxList +typedef struct cx_list_s CxList; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -404,12 +409,23 @@ * * @param map (@c CxMap*) the map * @param key (any supported key type) the key - * @return (@c void*) the value + * @return (@c void*) the value or @c NULL when no value with that @p key exists * @see CX_HASH_KEY() */ #define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key)) /** + * Checks if a map contains a specific key. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @retval true if the key exists in the map + * @retval false if the key does not exist in the map + * @see CX_HASH_KEY() + */ +#define cxMapContains(map, key) (cxMapGet(map, key) != NULL) + +/** * Removes a key/value-pair from the map by using the key. * * Invokes the destructor functions, if any, on the removed element if and only if the @@ -464,6 +480,246 @@ */ #define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf) + +/** + * Performs a deep clone of one map into another. + * + * If the destination map already contains entries, the cloned entries + * are added to that map, possibly overwriting existing elements when + * the keys already exist. + * + * When elements in the destination map need to be replaced, any destructor + * function is called on the replaced elements before replacing them. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination map also uses this + * destructor function. + * + * @param dst the destination map + * @param src the source map + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapClone(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map if their key is not present in another map. + * + * @param dst the destination map + * @param minuend the map to subtract the entries from + * @param subtrahend the map containing the elements to be subtracted + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries of a map if their key is not present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map only if their key is present in another map. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param other the map to check for existence of the keys + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries of a map only if their key is present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries into a map if their key does not exist yet. + * + * If you want to calculate the union of two maps into a fresh new map, + * you can proceed as follows: + * 1. Clone the first map into a fresh, empty map. + * 2. Use this function to clone the second map into the result from step 1. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a shallow clone of one map into another. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If the destination map already contains entries, the cloned entries + * are added to that map, possibly overwriting existing elements when + * the keys already exist. + * + * When elements in the destination map need to be replaced, any destructor + * function is called on the replaced elements before replacing them. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination map also uses this + * destructor function. + * + * @param dst the destination map + * @param src the source map + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxMapClone() + */ +cx_attr_nonnull +CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src); + +/** + * Clones entries of a map if their key is not present in another map. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * @param dst the destination map + * @param minuend the map to subtract the entries from + * @param subtrahend the map containing the elements to be subtracted + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend); + +/** + * Clones entries of a map if their key is not present in a list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxMapListDifference() + */ +cx_attr_nonnull +CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys); + + +/** + * Clones entries of a map only if their key is present in another map. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param other the map to check for existence of the keys + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other); + +/** + * Clones entries of a map only if their key is present in a list. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys); + +/** + * Clones entries into a map if their key does not exist yet. + * + * This function uses the default allocator, if needed, and performs + * shallow clones with @c memcpy(). + * + * If you want to calculate the union of two maps into a fresh new map, + * you can proceed as follows: + * 1. Clone the first map into a fresh, empty map. + * 2. Use this function to clone the second map into the result from step 1. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull +CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src); + #ifdef __cplusplus } // extern "C" #endif
--- a/ucx/cx/string.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/string.h Sun Nov 30 18:17:49 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/ucx/cx/tree.h Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/cx/tree.h Sun Nov 30 18:17:49 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/ucx/hash_key.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/hash_key.c Sun Nov 30 18:17:49 2025 +0100 @@ -159,7 +159,9 @@ return key; } -int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right) { +int cx_hash_key_cmp(const void *l, const void *r) { + const CxHashKey *left = l; + const CxHashKey *right = r; int d; d = cx_vcmp_uint64(left->hash, right->hash); if (d != 0) return d;
--- a/ucx/hash_map.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/hash_map.c Sun Nov 30 18:17:49 2025 +0100 @@ -86,7 +86,7 @@ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; const CxAllocator *allocator = map->collection.allocator; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; @@ -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; @@ -203,7 +203,7 @@ ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; @@ -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/ucx/json.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/json.c Sun Nov 30 18:17:49 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/ucx/kv_list.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/kv_list.c Sun Nov 30 18:17:49 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/ucx/linked_list.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/linked_list.c Sun Nov 30 18:17:49 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/ucx/list.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/list.c Sun Nov 30 18:17:49 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, }; @@ -804,3 +814,318 @@ if (list == NULL) return; list->cl->deallocate(list); } + +static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) { + cx_destructor_func destr_bak = list->collection.simple_destructor; + cx_destructor_func2 destr2_bak = list->collection.advanced_destructor; + list->collection.simple_destructor = NULL; + list->collection.advanced_destructor = NULL; + if (n == 1) { + cxListRemove(list, list->collection.size - 1); + } else { + cxListRemoveArray(list,list->collection.size - n, n); + } + list->collection.simple_destructor = destr_bak; + list->collection.advanced_destructor = destr2_bak; +} + +static void* cx_list_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { + size_t elem_size = *(size_t*)data; + if (dst == NULL) dst = cxMalloc(al, elem_size); + if (dst != NULL) memcpy(dst, src, elem_size); + return dst; +} + +#define use_simple_clone_func(list) cx_list_simple_clone_func, NULL, (void*)&((list)->collection.elem_size) + +int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // remember the original size + size_t orig_size = dst->collection.size; + + // first, try to allocate the memory in the new list + CxIterator empl_iter = cxListEmplaceArray(dst, src->collection.size); + + // get an iterator over the source elements + CxIterator src_iter = cxListIterator(src); + + // now clone the elements + size_t cloned = empl_iter.elem_count; + for (size_t i = 0 ; i < empl_iter.elem_count; i++) { + void *src_elem = cxIteratorCurrent(src_iter); + void **dest_memory = cxIteratorCurrent(empl_iter); + void *target = cxCollectionStoresPointers(dst) ? NULL : dest_memory; + void *dest_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dest_ptr == NULL) { + cloned = i; + break; + } + if (cxCollectionStoresPointers(dst)) { + *dest_memory = dest_ptr; + } + cxIteratorNext(src_iter); + cxIteratorNext(empl_iter); + } + + // if we could not clone everything, free the allocated memory + // (disable the destructors!) + if (cloned < src->collection.size) { + cx_list_pop_uninitialized_elements(dst, + dst->collection.size - cloned - orig_size); + return 1; + } + + // set the sorted flag when we know it's sorted + if (orig_size == 0 && src->collection.sorted) { + dst->collection.sorted = true; + } + + return 0; +} + +int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(minuend) && cxCollectionSorted(subtrahend)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator min_iter = cxListIterator(minuend); + CxIterator sub_iter = cxListIterator(subtrahend); + while (cxIteratorValid(min_iter)) { + void *min_elem = cxIteratorCurrent(min_iter); + void *sub_elem; + int d; + if (cxIteratorValid(sub_iter)) { + sub_elem = cxIteratorCurrent(sub_iter); + cx_compare_func cmp = subtrahend->collection.cmpfunc; + d = cmp(sub_elem, min_elem); + } else { + // no more elements in the subtrahend, + // i.e., the min_elem is larger than any elem of the subtrahend + d = 1; + } + if (d == 0) { + // is contained, so skip it + cxIteratorNext(min_iter); + } else if (d < 0) { + // subtrahend is smaller than minuend, + // check the next element + cxIteratorNext(sub_iter); + } else { + // subtrahend is larger than the dst element, + // clone the minuend and advance + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, min_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(min_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator min_iter = cxListIterator(minuend); + cx_foreach(void *, elem, min_iter) { + if (cxListContains(subtrahend, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListIntersection(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) { + void *src_elem = cxIteratorCurrent(src_iter); + void *other_elem = cxIteratorCurrent(other_iter); + int d = src->collection.cmpfunc(src_elem, other_elem); + if (d == 0) { + // is contained, clone it + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } else if (d < 0) { + // the other element is larger, skip the source element + cxIteratorNext(src_iter); + } else { + // the source element is larger, try to find it in the other list + cxIteratorNext(other_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator src_iter = cxListIterator(src); + cx_foreach(void *, elem, src_iter) { + if (!cxListContains(other, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListUnion(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) { + void *src_elem = NULL, *other_elem = NULL; + int d; + if (!cxIteratorValid(src_iter)) { + other_elem = cxIteratorCurrent(other_iter); + d = 1; + } else if (!cxIteratorValid(other_iter)) { + src_elem = cxIteratorCurrent(src_iter); + d = -1; + } else { + src_elem = cxIteratorCurrent(src_iter); + other_elem = cxIteratorCurrent(other_iter); + d = src->collection.cmpfunc(src_elem, other_elem); + } + void *clone_from; + if (d < 0) { + // source element is smaller clone it + clone_from = src_elem; + cxIteratorNext(src_iter); + } else if (d == 0) { + // both elements are equal, clone from the source, skip other + clone_from = src_elem; + cxIteratorNext(src_iter); + cxIteratorNext(other_iter); + } else { + // the other element is smaller, clone it + clone_from = other_elem; + cxIteratorNext(other_iter); + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, clone_from, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + if (cxListClone(dst, src, clone_func, clone_allocator, data)) { + return 1; + } + CxIterator other_iter = cxListIterator(other); + cx_foreach(void *, elem, other_iter) { + if (cxListContains(src, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListCloneSimple(CxList *dst, const CxList *src) { + return cxListClone(dst, src, use_simple_clone_func(src)); +} + +int cxListDifferenceSimple(CxList *dst, const CxList *minuend, const CxList *subtrahend) { + return cxListDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +} + +int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other) { + return cxListIntersection(dst, src, other, use_simple_clone_func(src)); +} + +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/ucx/map.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/map.c Sun Nov 30 18:17:49 2025 +0100 @@ -29,6 +29,8 @@ #include "cx/map.h" #include <string.h> +#include "cx/list.h" + // <editor-fold desc="empty map implementation"> static void cx_empty_map_noop(cx_attr_unused CxMap *map) { @@ -127,3 +129,200 @@ if (map == NULL) return; map->cl->deallocate(map); } + +static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) { + cx_destructor_func destr_bak = map->collection.simple_destructor; + cx_destructor_func2 destr2_bak = map->collection.advanced_destructor; + map->collection.simple_destructor = NULL; + map->collection.advanced_destructor = NULL; + cxMapRemove(map, key); + map->collection.simple_destructor = destr_bak; + map->collection.advanced_destructor = destr2_bak; +} + +static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) { + size_t elem_size = *(size_t*)data; + if (dst == NULL) dst = cxMalloc(al, elem_size); + if (dst != NULL) memcpy(dst, src, elem_size); + return dst; +} + +#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size) + +int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + CxMapIterator src_iter = cxMapIterator(src); + for (size_t i = 0; i < cxMapSize(src); i++) { + const CxMapEntry *entry = cxIteratorCurrent(src_iter); + void **dst_mem = cxMapEmplace(dst, *(entry->key)); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void *dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } + return 0; +} + +int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(minuend); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(subtrahend, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxMapContains(other, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(dst, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapCloneSimple(CxMap *dst, const CxMap *src) { + return cxMapClone(dst, src, use_simple_clone_func(src)); +} + +int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) { + return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend)); +} + +int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListDifference(dst, src, keys, use_simple_clone_func(src)); +} + +int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) { + return cxMapIntersection(dst, src, other, use_simple_clone_func(src)); +} + +int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) { + return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src)); +} + +int cxMapUnionSimple(CxMap *dst, const CxMap *src) { + return cxMapUnion(dst, src, use_simple_clone_func(src)); +}
--- a/ucx/mempool.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/mempool.c Sun Nov 30 18:17:49 2025 +0100 @@ -116,6 +116,9 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); @@ -124,11 +127,11 @@ if (mem->destructor) { mem->destructor(mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -179,18 +182,18 @@ } static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -247,6 +250,9 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory2_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s)); @@ -255,11 +261,11 @@ if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -310,18 +316,18 @@ } static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory2_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -376,13 +382,16 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { if (ptr == pool->data[i]) { - if (pool->destr) { - pool->destr(ptr); + if (destr != NULL) { + destr(ptr); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, ptr); + if (destr2 != NULL) { + destr2(pool->destr2_data, ptr); } cxFree(pool->base_allocator, ptr); size_t last_index = pool->size - 1; @@ -427,15 +436,15 @@ } static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { void *mem = pool->data[i]; - if (has_destr) { - pool->destr(mem); + if (destr != NULL) { + destr(mem); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem); } cxFree(pool->base_allocator, mem); }
--- a/ucx/printf.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/printf.c Sun Nov 30 18:17:49 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/ucx/properties.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/properties.c Sun Nov 30 18:17:49 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/ucx/string.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/string.c Sun Nov 30 18:17:49 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/ucx/tree.c Sun Nov 30 18:15:46 2025 +0100 +++ b/ucx/tree.c Sun Nov 30 18:17:49 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) {