# HG changeset patch # User Mike Becker # Date 1731016018 -3600 # Node ID 68754c7de906900a7b5e3ebb4c419d847f2fe48e # Parent e8f354a25ac8a093a82f7d2b6709e59916a7b069 major refactoring of attributes resolves #460 resolves #471 resolves #472 diff -r e8f354a25ac8 -r 68754c7de906 src/allocator.c --- a/src/allocator.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/allocator.c Thu Nov 07 22:46:58 2024 +0100 @@ -32,7 +32,7 @@ __attribute__((__malloc__, __alloc_size__(2))) static void *cx_malloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, size_t n ) { return malloc(n); @@ -40,7 +40,7 @@ __attribute__((__warn_unused_result__, __alloc_size__(3))) static void *cx_realloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem, size_t n ) { @@ -49,7 +49,7 @@ __attribute__((__malloc__, __alloc_size__(2, 3))) static void *cx_calloc_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, size_t nelem, size_t n ) { @@ -58,7 +58,7 @@ __attribute__((__nonnull__)) static void cx_free_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem ) { free(mem); diff -r e8f354a25ac8 -r 68754c7de906 src/array_list.c --- a/src/array_list.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/array_list.c Thu Nov 07 22:46:58 2024 +0100 @@ -37,7 +37,7 @@ void *array, size_t capacity, size_t elem_size, - __attribute__((__unused__)) CxArrayReallocator *alloc + cx_attr_unused CxArrayReallocator *alloc ) { return realloc(array, capacity * elem_size); } @@ -54,7 +54,7 @@ void *array, size_t capacity, size_t elem_size, - __attribute__((__unused__)) CxArrayReallocator *alloc + cx_attr_unused CxArrayReallocator *alloc ) { // retrieve the pointer to the actual allocator const CxAllocator *al = alloc->ptr1; @@ -326,6 +326,44 @@ return result < 0 ? (pivot_index - 1) : pivot_index; } +size_t cx_array_binary_search( + 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( + arr, size, elem_size, elem, cmp_func + ); + if (index < size && + cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { + return index; + } else { + return size; + } +} + +size_t cx_array_binary_search_sup( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t inf = cx_array_binary_search_inf( + arr, size, elem_size, elem, cmp_func + ); + if (inf == size) { + // no infimum means, first element is supremum + return 0; + } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { + return inf; + } else { + return inf + 1; + } +} + #ifndef CX_ARRAY_SWAP_SBO_SIZE #define CX_ARRAY_SWAP_SBO_SIZE 128 #endif diff -r e8f354a25ac8 -r 68754c7de906 src/buffer.c --- a/src/buffer.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/buffer.c Thu Nov 07 22:46:58 2024 +0100 @@ -86,6 +86,7 @@ } void cxBufferFree(CxBuffer *buffer) { + if (buffer == NULL) return; if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } diff -r e8f354a25ac8 -r 68754c7de906 src/cx/allocator.h --- a/src/cx/allocator.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/allocator.h Thu Nov 07 22:46:58 2024 +0100 @@ -46,6 +46,8 @@ /** * The allocator's malloc() implementation. */ + cx_attr_nonnull + cx_attr_nodiscard cx_attr_allocsize(2) void *(*malloc)( void *data, size_t n @@ -54,7 +56,8 @@ /** * The allocator's realloc() implementation. */ - __attribute__((__warn_unused_result__)) + cx_attr_nodiscard + cx_attr_allocsize(3) void *(*realloc)( void *data, void *mem, @@ -64,6 +67,8 @@ /** * The allocator's calloc() implementation. */ + cx_attr_nodiscard + cx_attr_allocsize(2, 3) void *(*calloc)( void *data, size_t nelem, @@ -73,7 +78,7 @@ /** * The allocator's free() implementation. */ - __attribute__((__nonnull__)) + cx_attr_nonnull_arg(1) void (*free)( void *data, void *mem @@ -109,12 +114,11 @@ * * A destructor function deallocates possible contents and MAY free the memory * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param memory a pointer to the object to destruct */ -__attribute__((__nonnull__)) typedef void (*cx_destructor_func)(void *memory); /** @@ -122,20 +126,20 @@ * * A destructor function deallocates possible contents and MAY free the memory * pointed to by \p memory. Read the documentation of the respective function - * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that - * particular implementation. + * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in + * that particular implementation. * * @param data an optional pointer to custom data * @param memory a pointer to the object to destruct */ -__attribute__((__nonnull__(2))) typedef void (*cx_destructor_func2)( void *data, void *memory ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * * \par Error handling * \c errno will be set by realloc() on failure. @@ -144,14 +148,16 @@ * @param n the new size in bytes * @return zero on success, non-zero on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cx_reallocate( void **mem, size_t n ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * * The size is calculated by multiplying \p nemb and \p size. * @@ -164,7 +170,8 @@ * @param size the size of each element * @return zero on success, non-zero on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cx_reallocatearray( void **mem, size_t nmemb, @@ -172,7 +179,8 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * * \par Error handling * \c errno will be set by realloc() on failure. @@ -184,7 +192,8 @@ #define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n) /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * * The size is calculated by multiplying \p nemb and \p size. * @@ -197,7 +206,22 @@ * @param size the size of each element * @return zero on success, non-zero on failure */ -#define cx_reallocatearray(mem, nmemb, size) cx_reallocatearray((void**)(mem), nmemb, size) +#define cx_reallocatearray(mem, nmemb, size) \ + cx_reallocatearray((void**)(mem), nmemb, size) + +/** + * Free a block allocated by this allocator. + * + * \note Freeing a block of a different allocator is undefined. + * + * @param allocator the allocator + * @param mem a pointer to the block to free + */ +cx_attr_nonnull_arg(1) +void cxFree( + const CxAllocator *allocator, + void *mem +); /** * Allocate \p n bytes of memory. @@ -206,18 +230,21 @@ * @param n the number of bytes * @return a pointer to the allocated memory */ -__attribute__((__malloc__)) -__attribute__((__nonnull__)) -__attribute__((__alloc_size__(2))) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) void *cxMalloc( const CxAllocator *allocator, size_t n ); /** - * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long. - * This function may return the same pointer that was passed to it, if moving the memory - * was not necessary. + * Re-allocate the previously allocated block in \p mem, making the new block + * \p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. * * \note Re-allocating a block allocated by a different allocator is undefined. * @@ -226,9 +253,10 @@ * @param n the new size in bytes * @return a pointer to the re-allocated memory */ -__attribute__((__warn_unused_result__)) -__attribute__((__nonnull__(1))) -__attribute__((__alloc_size__(3))) +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3) void *cxRealloc( const CxAllocator *allocator, void *mem, @@ -236,12 +264,14 @@ ); /** - * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long. - * This function may return the same pointer that was passed to it, if moving the memory - * was not necessary. + * Re-allocate the previously allocated block in \p mem, making the new block + * \p n bytes long. + * This function may return the same pointer that was passed to it, if moving + * the memory was not necessary. * * The size is calculated by multiplying \p nemb and \p size. - * If that multiplication overflows, this function returns \c NULL and \c errno will be set. + * If that multiplication overflows, this function returns \c NULL and \c errno + * will be set. * * \note Re-allocating a block allocated by a different allocator is undefined. * @@ -251,9 +281,10 @@ * @param size the size of each element * @return a pointer to the re-allocated memory */ -__attribute__((__warn_unused_result__)) -__attribute__((__nonnull__(1))) -__attribute__((__alloc_size__(3, 4))) +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3, 4) void *cxReallocArray( const CxAllocator *allocator, void *mem, @@ -262,7 +293,8 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * This function acts like cxRealloc() using the pointer pointed to by \p mem. * * \note Re-allocating a block allocated by a different allocator is undefined. @@ -275,7 +307,8 @@ * @param n the new size in bytes * @return zero on success, non-zero on failure */ -__attribute__((__nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cxReallocate( const CxAllocator *allocator, void **mem, @@ -283,7 +316,8 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * This function acts like cxRealloc() using the pointer pointed to by \p mem. * * \note Re-allocating a block allocated by a different allocator is undefined. @@ -296,11 +330,14 @@ * @param n the new size in bytes * @return zero on success, non-zero on failure */ -#define cxReallocate(allocator, mem, n) cxReallocate(allocator, (void**)(mem), n) +#define cxReallocate(allocator, mem, n) \ + cxReallocate(allocator, (void**)(mem), n) /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. - * This function acts like cxReallocArray() using the pointer pointed to by \p mem. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by \p mem. * * \note Re-allocating a block allocated by a different allocator is undefined. * @@ -314,7 +351,8 @@ * @param size the size of each element * @return zero on success, non-zero on failure */ -__attribute__((__nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cxReallocateArray( const CxAllocator *allocator, void **mem, @@ -323,8 +361,10 @@ ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. - * This function acts like cxReallocArray() using the pointer pointed to by \p mem. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * This function acts like cxReallocArray() using the pointer pointed to + * by \p mem. * * \note Re-allocating a block allocated by a different allocator is undefined. * @@ -349,29 +389,17 @@ * @param n the size of each element in bytes * @return a pointer to the allocated memory */ -__attribute__((__malloc__)) -__attribute__((__nonnull__)) -__attribute__((__alloc_size__(2, 3))) +cx_attr_nonnull_arg(1) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2, 3) void *cxCalloc( const CxAllocator *allocator, size_t nelem, size_t n ); -/** - * Free a block allocated by this allocator. - * - * \note Freeing a block of a different allocator is undefined. - * - * @param allocator the allocator - * @param mem a pointer to the block to free - */ -__attribute__((__nonnull__(1))) -void cxFree( - const CxAllocator *allocator, - void *mem -); - #ifdef __cplusplus } // extern "C" #endif diff -r e8f354a25ac8 -r 68754c7de906 src/cx/array_list.h --- a/src/cx/array_list.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/array_list.h Thu Nov 07 22:46:58 2024 +0100 @@ -28,7 +28,6 @@ /** * \file array_list.h * \brief Array list implementation. - * \details Also provides several low-level functions for custom array list implementations. * \author Mike Becker * \author Olaf Wintermann * \copyright 2-Clause BSD License @@ -45,7 +44,8 @@ #endif /** - * The maximum item size in an array list that fits into stack buffer when swapped. + * The maximum item size in an array list that fits into stack buffer + * when swapped. */ extern const unsigned cx_array_swap_sbo_size; @@ -94,6 +94,9 @@ * @param alloc a reference to this allocator * @return a pointer to the reallocated memory or \c NULL on failure */ + cx_attr_nodiscard + cx_attr_nonnull_arg(4) + cx_attr_allocsize(2, 3) void *(*realloc)( void *array, size_t capacity, @@ -184,7 +187,7 @@ * if reallocation shall not happen * @return zero on success, non-zero error code on failure */ -__attribute__((__nonnull__(1, 2, 5))) +cx_attr_nonnull_arg(1, 2, 5) enum cx_array_result cx_array_copy( void **target, size_t *size, @@ -262,7 +265,7 @@ * @param reallocator the array reallocator to use * @return zero on success, non-zero error code on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull enum cx_array_result cx_array_insert_sorted( void **target, size_t *size, @@ -342,7 +345,7 @@ * @param cmp_func the compare function * @return the index of the largest lower bound, or \p size */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -365,23 +368,14 @@ * @return the index of the element in the array, or \p size if the element * cannot be found */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search( +cx_attr_nonnull +size_t cx_array_binary_search( 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( - arr, size, elem_size, elem, cmp_func - ); - if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) { - return index; - } else { - return size; - } -} +); /** * Searches the smallest upper bound in a sorted array. @@ -403,24 +397,14 @@ * @param cmp_func the compare function * @return the index of the smallest upper bound, or \p size */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search_sup( +cx_attr_nonnull +size_t cx_array_binary_search_sup( const void *arr, size_t size, size_t elem_size, const void *elem, cx_compare_func cmp_func -) { - size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func); - if (inf == size) { - // no infimum means, first element is supremum - return 0; - } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { - return inf; - } else { - return inf + 1; - } -} +); /** * Swaps two array elements. @@ -430,7 +414,7 @@ * @param idx1 index of first element * @param idx2 index of second element */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_array_swap( void *arr, size_t elem_size, @@ -454,6 +438,9 @@ * @param initial_capacity the initial number of elements the array can store * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListDestroy, 1) CxList *cxArrayListCreate( const CxAllocator *allocator, cx_compare_func comparator, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/buffer.h --- a/src/cx/buffer.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/buffer.h Thu Nov 07 22:46:58 2024 +0100 @@ -51,7 +51,7 @@ #include -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -155,7 +155,7 @@ * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) int cxBufferInit( CxBuffer *buffer, void *space, @@ -165,6 +165,32 @@ ); /** + * Destroys the buffer contents. + * + * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled. + * If you want to free the memory of the entire buffer, use cxBufferFree(). + * + * @param buffer the buffer which contents shall be destroyed + * @see cxBufferInit() + */ +cx_attr_nonnull +void cxBufferDestroy(CxBuffer *buffer); + +/** + * Deallocates the buffer. + * + * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys + * the contents. If you \em only want to destroy the contents, use cxBufferDestroy(). + * + * \remark As with all free() functions, this accepts \c NULL arguments in which + * case it does nothing. + * + * @param buffer the buffer to deallocate + * @see cxBufferCreate() + */ +void cxBufferFree(CxBuffer *buffer); + +/** * Allocates and initializes a fresh buffer. * * \note You may provide \c NULL as argument for \p space. @@ -179,6 +205,9 @@ * @param flags buffer features (see cx_buffer_s.flags) * @return a pointer to the buffer on success, \c NULL if a required allocation failed */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard CxBuffer *cxBufferCreate( void *space, size_t capacity, @@ -187,30 +216,6 @@ ); /** - * Destroys the buffer contents. - * - * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled. - * If you want to free the memory of the entire buffer, use cxBufferFree(). - * - * @param buffer the buffer which contents shall be destroyed - * @see cxBufferInit() - */ -__attribute__((__nonnull__)) -void cxBufferDestroy(CxBuffer *buffer); - -/** - * Deallocates the buffer. - * - * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys - * the contents. If you \em only want to destroy the contents, use cxBufferDestroy(). - * - * @param buffer the buffer to deallocate - * @see cxBufferCreate() - */ -__attribute__((__nonnull__)) -void cxBufferFree(CxBuffer *buffer); - -/** * Shifts the contents of the buffer by the given offset. * * If the offset is positive, the contents are shifted to the right. @@ -243,7 +248,7 @@ * @param shift the shift offset (negative means left shift) * @return 0 on success, non-zero if a required auto-extension fails */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShift( CxBuffer *buffer, off_t shift @@ -258,7 +263,7 @@ * @return 0 on success, non-zero if a required auto-extension fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -276,7 +281,7 @@ * @return always zero * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -302,7 +307,7 @@ * @return 0 on success, non-zero if the position is invalid * */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -318,7 +323,7 @@ * @param buffer the buffer to be cleared * @see cxBufferReset() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferClear(CxBuffer *buffer); /** @@ -330,7 +335,7 @@ * @param buffer the buffer to be cleared * @see cxBufferClear() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferReset(CxBuffer *buffer); /** @@ -340,7 +345,8 @@ * @return non-zero, if the current buffer position has exceeded the last * byte of the buffer's contents. */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cxBufferEof(const CxBuffer *buffer); @@ -353,7 +359,7 @@ * @param capacity the minimum required capacity for this buffer * @return 0 on success or a non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -385,7 +391,7 @@ * @param buffer the CxBuffer to write to * @return the total count of elements written */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferWrite( const void *ptr, size_t size, @@ -406,7 +412,7 @@ * @param buffer the CxBuffer to read from * @return the total number of elements read */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferRead( void *ptr, size_t size, @@ -429,7 +435,7 @@ * @return the byte that has bean written or \c EOF when the end of the stream is * reached and automatic extension is not enabled or not possible */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferPut( CxBuffer *buffer, int c @@ -442,7 +448,8 @@ * @param str the zero-terminated string * @return the number of bytes written */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -456,7 +463,7 @@ * @param buffer the buffer to read from * @return the character or \c EOF, if the end of the buffer is reached */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus diff -r e8f354a25ac8 -r 68754c7de906 src/cx/common.h --- a/src/cx/common.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/common.h Thu Nov 07 22:46:58 2024 +0100 @@ -88,13 +88,166 @@ /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) -// Common Includes +// --------------------------------------------------------------------------- +// Common includes +// --------------------------------------------------------------------------- #include #include #include #include +// --------------------------------------------------------------------------- +// Attribute definitions +// --------------------------------------------------------------------------- + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +/** + * All pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull __attribute__((__nonnull__)) + +/** + * The specified pointer arguments must be non-NULL. + */ +#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__))) + +/** + * The returned value is guaranteed to be non-NULL. + */ +#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__)) + +/** + * The attributed function always returns freshly allocated memory. + */ +#define cx_attr_malloc __attribute__((__malloc__)) + +#ifndef __clang__ +/** + * The pointer returned by the attributed function is supposed to be freed + * by \p freefunc. + * + * @param freefunc the function that shall be used to free the memory + * @param freefunc_arg the index of the pointer argument in \p freefunc + */ +#define cx_attr_dealloc(freefunc, freefunc_arg) \ + __attribute__((__malloc__(freefunc, freefunc_arg))) +#else +/** + * Not supported in clang. + */ +#define cx_attr_dealloc(...) +#endif // __clang__ + +/** + * Shortcut to specify #cxFree() as deallocator. + */ +#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2) + +/** + * Specifies the parameters from which the allocation size is calculated. + */ +#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__))) + + +#ifdef __clang__ +/** + * No support for \c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +/** + * No support for access attribute in clang. + */ +#define cx_attr_access(mode, ...) +#else +#if __GNUC__ < 10 +/** + * No support for access attribute in GCC < 10. + */ +#define cx_attr_access(mode, ...) +#else +/** + * Helper macro to define access macros. + */ +#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__))) +#endif // __GNUC__ < 10 +#if __GNUC__ < 14 +/** + * No support for \c null_terminated_string_arg in clang or GCC below 14. + */ +#define cx_attr_cstr_arg(idx) +#else +/** + * The specified argument is expected to be a zero-terminated string. + * + * @param idx the index of the argument + */ +#define cx_attr_cstr_arg(idx) \ + __attribute__((__null_terminated_string_arg__(idx))) +#endif // __GNUC__ < 14 +#endif // __clang__ + + +/** + * Specifies that the function will only read through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__) + +/** + * Specifies that the function will read and write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__) + +/** + * Specifies that the function will only write through the given pointer. + * + * Takes one or two arguments: the index of the pointer and (optionally) the + * index of another argument specifying the maximum number of accessed bytes. + */ +#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) + +#if __STDC_VERSION__ >= 202300L + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused [[maybe_unused]] + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard [[nodiscard]] + +#else // no C23 + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused __attribute__((__unused__)) + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard __attribute__((__warn_unused_result__)) + +#endif // __STDC_VERSION__ + +// --------------------------------------------------------------------------- +// Useful function pointers +// --------------------------------------------------------------------------- + /** * Function pointer compatible with fwrite-like functions. */ @@ -103,7 +256,7 @@ size_t, size_t, void * -); +) cx_attr_nonnull; /** * Function pointer compatible with fread-like functions. @@ -113,7 +266,11 @@ size_t, size_t, void * -); +) cx_attr_nonnull; + +// --------------------------------------------------------------------------- +// Utility macros +// --------------------------------------------------------------------------- /** * Determines the number of members in a static C array. @@ -126,7 +283,10 @@ */ #define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0])) -// cx_szmul() definition +// --------------------------------------------------------------------------- +// szmul implementation +// --------------------------------------------------------------------------- + #if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) #define CX_SZMUL_BUILTIN @@ -174,24 +334,18 @@ #endif // cx_szmul -// Compiler specific stuff -#ifndef __GNUC__ -/** - * Removes GNU C attributes where they are not supported. - */ -#define __attribute__(x) -#endif +// --------------------------------------------------------------------------- +// Fixes for MSVC incompatibilities +// --------------------------------------------------------------------------- #ifdef _MSC_VER - // fix missing ssize_t definition #include typedef SSIZE_T ssize_t; // fix missing _Thread_local support #define _Thread_local __declspec(thread) - -#endif +#endif // _MSC_VER #endif // UCX_COMMON_H diff -r e8f354a25ac8 -r 68754c7de906 src/cx/compare.h --- a/src/cx/compare.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/compare.h Thu Nov 07 22:46:58 2024 +0100 @@ -42,16 +42,15 @@ extern "C" { #endif -#ifndef CX_COMPARE_FUNC_DEFINED -#define CX_COMPARE_FUNC_DEFINED /** * A comparator function comparing two collection elements. */ +cx_attr_nonnull +cx_attr_nodiscard typedef int(*cx_compare_func)( const void *left, const void *right ); -#endif // CX_COMPARE_FUNC_DEFINED /** * Compares two integers of type int. @@ -61,6 +60,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int(const void *i1, const void *i2); /** @@ -71,6 +72,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longint(const void *i1, const void *i2); /** @@ -81,6 +84,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longlong(const void *i1, const void *i2); /** @@ -91,6 +96,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int16(const void *i1, const void *i2); /** @@ -101,6 +108,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int32(const void *i1, const void *i2); /** @@ -111,6 +120,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int64(const void *i1, const void *i2); /** @@ -121,6 +132,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint(const void *i1, const void *i2); /** @@ -131,6 +144,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_ulongint(const void *i1, const void *i2); /** @@ -141,6 +156,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_ulonglong(const void *i1, const void *i2); /** @@ -151,6 +168,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint16(const void *i1, const void *i2); /** @@ -161,6 +180,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint32(const void *i1, const void *i2); /** @@ -171,6 +192,8 @@ * @return -1, if *i1 is less than *i2, 0 if both are equal, * 1 if *i1 is greater than *i2 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_uint64(const void *i1, const void *i2); /** @@ -181,7 +204,8 @@ * @return -1, if *f1 is less than *f2, 0 if both are equal, * 1 if *f1 is greater than *f2 */ - +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_float(const void *f1, const void *f2); /** @@ -192,10 +216,9 @@ * @return -1, if *d1 is less than *d2, 0 if both are equal, * 1 if *d1 is greater than *d2 */ -int cx_cmp_double( - const void *d1, - const void *d2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_double(const void *d1, const void *d2); /** * Compares the integer representation of two pointers. @@ -205,10 +228,9 @@ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, * 1 if *ptr1 is greater than *ptr2 */ -int cx_cmp_intptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_intptr(const void *ptr1, const void *ptr2); /** * Compares the unsigned integer representation of two pointers. @@ -218,10 +240,9 @@ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, * 1 if *ptr1 is greater than *ptr2 */ -int cx_cmp_uintptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_uintptr(const void *ptr1, const void *ptr2); /** * Compares the pointers specified in the arguments without de-referencing. @@ -231,10 +252,9 @@ * @return -1 if ptr1 is less than ptr2, 0 if both are equal, * 1 if ptr1 is greater than ptr2 */ -int cx_cmp_ptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C" diff -r e8f354a25ac8 -r 68754c7de906 src/cx/hash_key.h --- a/src/cx/hash_key.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/hash_key.h Thu Nov 07 22:46:58 2024 +0100 @@ -70,6 +70,7 @@ * * @param key the key, the hash shall be computed for */ +cx_attr_nonnull void cx_hash_murmur(CxHashKey *key); /** @@ -80,7 +81,8 @@ * @param str the string * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_cstr_arg(1) CxHashKey cx_hash_key_str(const char *str); /** @@ -90,7 +92,8 @@ * @param len the length * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -107,6 +110,8 @@ * @param len the length of object in memory * @return the hash key */ +cx_attr_nodiscard +cx_attr_access_r(1, 2) __attribute__((__warn_unused_result__)) CxHashKey cx_hash_key( const void *obj, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/hash_map.h --- a/src/cx/hash_map.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/hash_map.h Thu Nov 07 22:46:58 2024 +0100 @@ -81,7 +81,10 @@ * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMapDestroy, 1) CxMap *cxHashMapCreate( const CxAllocator *allocator, size_t itemsize, @@ -122,7 +125,7 @@ * @param map the map to rehash * @return zero on success, non-zero if a memory allocation error occurred */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxMapRehash(CxMap *map); diff -r e8f354a25ac8 -r 68754c7de906 src/cx/iterator.h --- a/src/cx/iterator.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/iterator.h Thu Nov 07 22:46:58 2024 +0100 @@ -46,7 +46,7 @@ /** * True iff the iterator points to valid data. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull bool (*valid)(const void *); /** @@ -54,13 +54,15 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current)(const void *); /** * Original implementation in case the function needs to be wrapped. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current_impl)(const void *); /** @@ -68,7 +70,7 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull void (*next)(void *); /** * Indicates whether this iterator may remove elements. @@ -228,7 +230,7 @@ * @param elem_count the number of elements in the array * @return an iterator for the specified array */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxIterator( const void *array, size_t elem_size, @@ -258,7 +260,7 @@ * when removing an element * @return an iterator for the specified array */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxMutIterator( void *array, size_t elem_size, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/json.h --- a/src/cx/json.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/json.h Thu Nov 07 22:46:58 2024 +0100 @@ -173,13 +173,14 @@ // TODO: add support for CxAllocator -__attribute__((__nonnull__)) +cx_attr_nonnull void cxJsonInit(CxJson *json); -__attribute__((__nonnull__)) +cx_attr_nonnull void cxJsonDestroy(CxJson *json); -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(2, 3) int cxJsonFilln(CxJson *json, const char *buf, size_t len); #define cxJsonFill(json, str) _Generic((str), \ @@ -189,7 +190,7 @@ const char*: cx_json_fill_str) \ (json, str) -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_json_fill_cxstr( CxJson *json, cxstring str @@ -197,7 +198,7 @@ return cxJsonFilln(json, str.ptr, str.length); } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_json_fill_mutstr( CxJson *json, cxmutstr str @@ -205,7 +206,8 @@ return cxJsonFilln(json, str.ptr, str.length); } -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cx_json_fill_str( CxJson *json, const char *str @@ -213,78 +215,79 @@ return cxJsonFilln(json, str, strlen(str)); } +void cxJsonValueFree(CxJsonValue *value); -__attribute__((__nonnull__)) +// TODO: if the CxJsonValue was a returned value, we could reference cxJsonValueFree() as deallocator +cx_attr_nonnull int cxJsonNext(CxJson *json, CxJsonValue **value); -void cxJsonValueFree(CxJsonValue *value); - -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsObject(CxJsonValue *value) { return value->type == CX_JSON_OBJECT; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsArray(CxJsonValue *value) { return value->type == CX_JSON_ARRAY; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsString(CxJsonValue *value) { return value->type == CX_JSON_STRING; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsNumber(CxJsonValue *value) { return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsInteger(CxJsonValue *value) { return value->type == CX_JSON_INTEGER; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsLiteral(CxJsonValue *value) { return value->type == CX_JSON_LITERAL; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsBool(CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsTrue(CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsFalse(CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonIsNull(CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; } -__attribute__((__nonnull__, __returns_nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull static inline char *cxJsonAsString(CxJsonValue *value) { return value->value.string.ptr; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline cxstring cxJsonAsCxString(CxJsonValue *value) { return cx_strcast(value->value.string); } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline cxmutstr cxJsonAsCxMutStr(CxJsonValue *value) { return value->value.string; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline double cxJsonAsDouble(CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { return (double) value->value.integer; @@ -293,7 +296,7 @@ } } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int64_t cxJsonAsInteger(CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { return value->value.integer; @@ -302,23 +305,26 @@ } } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxJsonAsBool(CxJsonValue *value) { return value->value.literal == CX_JSON_TRUE; } -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxJsonArrSize(CxJsonValue *value) { return value->value.array.array_size; } -__attribute__((__nonnull__, __returns_nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull CxJsonValue *cxJsonArrGet(CxJsonValue *value, size_t index); // TODO: add cxJsonArrIter() // TODO: implement cxJsonObjGet as a _Generic with support for cxstring -__attribute__((__nonnull__, __returns_nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull +cx_attr_cstr_arg(2) CxJsonValue *cxJsonObjGet(CxJsonValue *value, const char* name); #ifdef __cplusplus diff -r e8f354a25ac8 -r 68754c7de906 src/cx/linked_list.h --- a/src/cx/linked_list.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/linked_list.h Thu Nov 07 22:46:58 2024 +0100 @@ -64,6 +64,9 @@ * @param elem_size the size of each element in bytes * @return the created list */ +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxListDestroy, 1) CxList *cxLinkedListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -104,7 +107,8 @@ * @param index the search index * @return the node found at the specified index */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard void *cx_linked_list_at( const void *start, size_t start_index, @@ -122,7 +126,7 @@ * @param elem a pointer to the element to find * @return the index of the element or a negative value if it could not be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find( const void *start, ptrdiff_t loc_advance, @@ -143,7 +147,7 @@ * @param elem a pointer to the element to find * @return the index of the element or a negative value if it could not be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find_node( void **result, const void *start, @@ -164,7 +168,8 @@ * @param loc_prev the location of the \c prev pointer * @return a pointer to the first node */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_first( const void *node, ptrdiff_t loc_prev @@ -181,7 +186,8 @@ * @param loc_next the location of the \c next pointer * @return a pointer to the last node */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_last( const void *node, ptrdiff_t loc_next @@ -197,7 +203,7 @@ * @param node the successor of the node to find * @return the node or \c NULL if \p node has no predecessor */ -__attribute__((__nonnull__)) +cx_attr_nonnull void *cx_linked_list_prev( const void *begin, ptrdiff_t loc_next, @@ -216,7 +222,7 @@ * @param loc_next the location of a \c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be appended */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_add( void **begin, void **end, @@ -237,7 +243,7 @@ * @param loc_next the location of a \c next pointer within your node struct (required) * @param new_node a pointer to the node that shall be prepended */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_prepend( void **begin, void **end, @@ -254,7 +260,7 @@ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) * @param loc_next the location of a \c next pointer within your node struct (required) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_link( void *left, void *right, @@ -272,7 +278,7 @@ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) * @param loc_next the location of a \c next pointer within your node struct (required) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_unlink( void *left, void *right, @@ -294,7 +300,7 @@ * @param node the node after which to insert (\c NULL if you want to prepend the node to the list) * @param new_node a pointer to the node that shall be inserted */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert( void **begin, void **end, @@ -324,7 +330,7 @@ * @param insert_begin a pointer to the first node of the chain that shall be inserted * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert_chain( void **begin, void **end, @@ -349,7 +355,7 @@ * @param new_node a pointer to the node that shall be inserted * @param cmp_func a compare function that will receive the node pointers */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted( void **begin, void **end, @@ -378,7 +384,7 @@ * @param insert_begin a pointer to the first node of the chain that shall be inserted * @param cmp_func a compare function that will receive the node pointers */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -409,7 +415,7 @@ * @param num the number of nodes to remove * @return the actual number of nodes that were removed (may be less when the list did not have enough nodes) */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) size_t cx_linked_list_remove_chain( void **begin, void **end, @@ -438,7 +444,7 @@ * @param loc_next the location of a \c next pointer within your node struct (required) * @param node the node to remove */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) static inline void cx_linked_list_remove( void **begin, void **end, @@ -482,7 +488,7 @@ * @param loc_data the location of the \c data pointer within your node struct * @param cmp_func the compare function defining the sort order */ -__attribute__((__nonnull__(1, 6))) +cx_attr_nonnull_arg(1, 6) void cx_linked_list_sort( void **begin, void **end, @@ -506,7 +512,7 @@ * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the * right list, positive if the left list is larger than the right list, zero if both lists are equal. */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) int cx_linked_list_compare( const void *begin_left, const void *begin_right, @@ -523,7 +529,7 @@ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one) * @param loc_next the location of a \c next pointer within your node struct (required) */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) void cx_linked_list_reverse( void **begin, void **end, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/list.h --- a/src/cx/list.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/list.h Thu Nov 07 22:46:58 2024 +0100 @@ -52,6 +52,9 @@ * Structure for holding the base data of a list. */ struct cx_list_s { + /** + * Common members for collections. + */ CX_COLLECTION_BASE; /** * The list class definition. @@ -73,12 +76,14 @@ * Implementations SHALL invoke the content destructor functions if provided * and SHALL deallocate the list memory. */ + cx_attr_nonnull void (*destructor)(struct cx_list_s *list); /** * Member function for inserting a single element. * Implementors SHOULD see to performant implementations for corner cases. */ + cx_attr_nonnull int (*insert_element)( struct cx_list_s *list, size_t index, @@ -90,6 +95,7 @@ * Implementors SHOULD see to performant implementations for corner cases. * @see cx_list_default_insert_array() */ + cx_attr_nonnull size_t (*insert_array)( struct cx_list_s *list, size_t index, @@ -102,6 +108,7 @@ * * @see cx_list_default_insert_sorted() */ + cx_attr_nonnull size_t (*insert_sorted)( struct cx_list_s *list, const void *sorted_data, @@ -111,6 +118,7 @@ /** * Member function for inserting an element relative to an iterator position. */ + cx_attr_nonnull int (*insert_iter)( struct cx_iterator_s *iter, const void *elem, @@ -127,6 +135,8 @@ * The function SHALL return the actual number of elements removed, which * might be lower than \p num when going out of bounds. */ + cx_attr_nonnull_arg(1) + cx_attr_access_w(4) size_t (*remove)( struct cx_list_s *list, size_t index, @@ -137,12 +147,14 @@ /** * Member function for removing all elements. */ + cx_attr_nonnull void (*clear)(struct cx_list_s *list); /** * Member function for swapping two elements. * @see cx_list_default_swap() */ + cx_attr_nonnull int (*swap)( struct cx_list_s *list, size_t i, @@ -152,6 +164,8 @@ /** * Member function for element lookup. */ + cx_attr_nonnull + cx_attr_nodiscard void *(*at)( const struct cx_list_s *list, size_t index @@ -160,6 +174,8 @@ /** * Member function for finding and optionally removing an element. */ + cx_attr_nonnull + cx_attr_nodiscard ssize_t (*find_remove)( struct cx_list_s *list, const void *elem, @@ -170,6 +186,7 @@ * Member function for sorting the list in-place. * @see cx_list_default_sort() */ + cx_attr_nonnull void (*sort)(struct cx_list_s *list); /** @@ -177,6 +194,7 @@ * to another list of the same type. * If set to \c NULL, comparison won't be optimized. */ + cx_attr_nonnull int (*compare)( const struct cx_list_s *list, const struct cx_list_s *other @@ -185,11 +203,13 @@ /** * Member function for reversing the order of the items. */ + cx_attr_nonnull void (*reverse)(struct cx_list_s *list); /** * Member function for returning an iterator pointing to the specified index. */ + cx_attr_nonnull struct cx_iterator_s (*iterator)( const struct cx_list_s *list, size_t index, @@ -211,7 +231,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_array( struct cx_list_s *list, size_t index, @@ -235,7 +255,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_sorted( struct cx_list_s *list, const void *sorted_data, @@ -253,7 +273,7 @@ * * @param list the list that shall be sorted */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_list_default_sort(struct cx_list_s *list); /** @@ -268,7 +288,7 @@ * @return zero on success, non-zero when indices are out of bounds or memory * allocation for the temporary buffer fails */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** @@ -285,7 +305,7 @@ * @param list the list * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStoreObjects(CxList *list); /** @@ -300,7 +320,7 @@ * @param list the list * @see cxListStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStorePointers(CxList *list); /** @@ -310,7 +330,7 @@ * @return true, if this list is storing pointers * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxListIsStoringPointers(const CxList *list) { return list->collection.store_pointer; } @@ -321,7 +341,7 @@ * @param list the list * @return the number of currently stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListSize(const CxList *list) { return list->collection.size; } @@ -334,7 +354,7 @@ * @return zero on success, non-zero on memory allocation failure * @see cxListAddArray() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListAdd( CxList *list, const void *elem @@ -358,7 +378,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListAddArray( CxList *list, const void *array, @@ -380,7 +400,7 @@ * @see cxListInsertAfter() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsert( CxList *list, size_t index, @@ -396,7 +416,7 @@ * @param elem a pointer to the element to add * @return zero on success, non-zero on memory allocation failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertSorted( CxList *list, const void *elem @@ -424,7 +444,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertArray( CxList *list, size_t index, @@ -451,7 +471,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertSortedArray( CxList *list, const void *array, @@ -475,7 +495,7 @@ * @see cxListInsert() * @see cxListInsertBefore() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertAfter( CxIterator *iter, const void *elem @@ -498,7 +518,7 @@ * @see cxListInsert() * @see cxListInsertAfter() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertBefore( CxIterator *iter, const void *elem @@ -516,7 +536,7 @@ * @param index the index of the element * @return zero on success, non-zero if the index is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListRemove( CxList *list, size_t index @@ -535,7 +555,8 @@ * @param targetbuf a buffer where to copy the element * @return zero on success, non-zero if the index is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(3) static inline int cxListRemoveAndGet( CxList *list, size_t index, @@ -559,7 +580,7 @@ * @param num the number of elements to remove * @return the actual number of removed elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListRemoveArray( CxList *list, size_t index, @@ -580,7 +601,8 @@ * @param targetbuf a buffer where to copy the elements * @return the actual number of removed elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(4) static inline size_t cxListRemoveArrayAndGet( CxList *list, size_t index, @@ -598,7 +620,7 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListClear(CxList *list) { list->cl->clear(list); } @@ -614,7 +636,7 @@ * @param j the index of the second element * @return zero on success, non-zero if one of the indices is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListSwap( CxList *list, size_t i, @@ -630,9 +652,9 @@ * @param index the index of the element * @return a pointer to the element or \c NULL if the index is out of bounds */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void *cxListAt( - CxList *list, + const CxList *list, size_t index ) { return list->cl->at(list, index); @@ -649,7 +671,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIteratorAt( const CxList *list, size_t index @@ -668,7 +691,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIteratorAt( const CxList *list, size_t index @@ -687,7 +711,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxListMutIteratorAt( CxList *list, size_t index @@ -705,7 +730,8 @@ * @param index the index where the iterator shall point at * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index @@ -721,7 +747,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIterator(const CxList *list) { return list->cl->iterator(list, 0, false); } @@ -736,7 +763,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutIterator(CxList *list) { return cxListMutIteratorAt(list, 0); } @@ -752,7 +780,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIterator(const CxList *list) { return list->cl->iterator(list, list->collection.size - 1, true); } @@ -767,7 +796,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutBackwardsIterator(CxList *list) { return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); } @@ -782,7 +812,8 @@ * @return the index of the element or a negative * value when the element is not found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline ssize_t cxListFind( const CxList *list, const void *elem @@ -800,7 +831,7 @@ * @return the index of the now removed element or a negative * value when the element is not found or could not be removed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline ssize_t cxListFindRemove( CxList *list, const void *elem @@ -815,7 +846,7 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListSort(CxList *list) { list->cl->sort(list); } @@ -825,7 +856,7 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListReverse(CxList *list) { list->cl->reverse(list); } @@ -841,7 +872,8 @@ * @return zero, if both lists are equal element wise, * negative if the first list is smaller, positive if the first list is larger */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cxListCompare( const CxList *list, const CxList *other @@ -857,15 +889,17 @@ * * @param list the list which shall be destroyed */ -__attribute__((__nonnull__)) -void cxListDestroy(CxList *list); +static inline void cxListDestroy(CxList *list) { + if (list == NULL) return; + list->cl->destructor(list); +} /** * A shared instance of an empty list. * - * Writing to that list is undefined. + * Writing to that list is not allowed. */ -extern CxList * const cxEmptyList; +extern CxList *const cxEmptyList; #ifdef __cplusplus diff -r e8f354a25ac8 -r 68754c7de906 src/cx/map.h --- a/src/cx/map.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/map.h Thu Nov 07 22:46:58 2024 +0100 @@ -89,19 +89,19 @@ /** * Deallocates the entire memory. */ - __attribute__((__nonnull__)) + cx_attr_nonnull void (*destructor)(struct cx_map_s *map); /** * Removes all elements. */ - __attribute__((__nonnull__)) + cx_attr_nonnull void (*clear)(struct cx_map_s *map); /** * Add or overwrite an element. */ - __attribute__((__nonnull__)) + cx_attr_nonnull int (*put)( CxMap *map, CxHashKey key, @@ -111,7 +111,8 @@ /** * Returns an element. */ - __attribute__((__nonnull__, __warn_unused_result__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*get)( const CxMap *map, CxHashKey key @@ -120,7 +121,7 @@ /** * Removes an element. */ - __attribute__((__nonnull__)) + cx_attr_nonnull void *(*remove)( CxMap *map, CxHashKey key, @@ -130,7 +131,8 @@ /** * Creates an iterator for this map. */ - __attribute__((__nonnull__, __warn_unused_result__)) + cx_attr_nonnull + cx_attr_nodiscard CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type); }; @@ -151,7 +153,7 @@ /** * A shared instance of an empty map. * - * Writing to that map is undefined. + * Writing to that map is not allowed. */ extern CxMap *const cxEmptyMap; @@ -164,7 +166,7 @@ * @param map the map * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStoreObjects(CxMap *map) { map->collection.store_pointer = false; } @@ -181,7 +183,7 @@ * @param map the map * @see cxMapStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStorePointers(CxMap *map) { map->collection.store_pointer = true; map->collection.elem_size = sizeof(void *); @@ -194,7 +196,7 @@ * @return true, if this map is storing pointers * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxMapIsStoringPointers(const CxMap *map) { return map->collection.store_pointer; } @@ -215,7 +217,7 @@ * * @param map the map to be cleared */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapClear(CxMap *map) { map->cl->clear(map); } @@ -226,7 +228,7 @@ * @param map the map * @return the number of stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxMapSize(const CxMap *map) { return map->collection.size; } @@ -243,7 +245,8 @@ * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorValues(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } @@ -259,7 +262,8 @@ * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorKeys(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } @@ -277,7 +281,8 @@ * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIterator(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -292,7 +297,8 @@ * @param map the map to create the iterator for * @return an iterator for the currently stored values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorValues(CxMap *map); /** @@ -306,7 +312,8 @@ * @param map the map to create the iterator for * @return an iterator for the currently stored keys */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorKeys(CxMap *map); /** @@ -322,7 +329,8 @@ * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIterator(CxMap *map); #ifdef __cplusplus @@ -336,7 +344,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, CxHashKey const &key, @@ -354,7 +362,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxstring const &key, @@ -371,7 +379,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxMapPut( CxMap *map, cxmutstr const &key, @@ -388,7 +396,8 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cxMapPut( CxMap *map, const char *key, @@ -404,7 +413,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, CxHashKey const &key @@ -419,7 +429,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, cxstring const &key @@ -434,7 +445,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapGet( const CxMap *map, cxmutstr const &key @@ -449,7 +461,9 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cxMapGet( const CxMap *map, const char *key @@ -471,7 +485,7 @@ * @see cxMapRemoveAndGet() * @see cxMapDetach() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapRemove( CxMap *map, CxHashKey const &key @@ -493,7 +507,7 @@ * @see cxMapRemoveAndGet() * @see cxMapDetach() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapRemove( CxMap *map, cxstring const &key @@ -515,7 +529,7 @@ * @see cxMapRemoveAndGet() * @see cxMapDetach() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapRemove( CxMap *map, cxmutstr const &key @@ -537,7 +551,8 @@ * @see cxMapRemoveAndGet() * @see cxMapDetach() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline void cxMapRemove( CxMap *map, const char *key @@ -559,7 +574,7 @@ * @see cxMapRemove() * @see cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapDetach( CxMap *map, CxHashKey const &key @@ -581,7 +596,7 @@ * @see cxMapRemove() * @see cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapDetach( CxMap *map, cxstring const &key @@ -603,7 +618,7 @@ * @see cxMapRemove() * @see cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapDetach( CxMap *map, cxmutstr const &key @@ -625,7 +640,8 @@ * @see cxMapRemove() * @see cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline void cxMapDetach( CxMap *map, const char *key @@ -652,7 +668,8 @@ * @see cxMapStorePointers() * @see cxMapDetach() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapRemoveAndGet( CxMap *map, CxHashKey key @@ -679,7 +696,8 @@ * @see cxMapStorePointers() * @see cxMapDetach() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapRemoveAndGet( CxMap *map, cxstring key @@ -706,7 +724,8 @@ * @see cxMapStorePointers() * @see cxMapDetach() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxMapRemoveAndGet( CxMap *map, cxmutstr key @@ -733,7 +752,9 @@ * @see cxMapStorePointers() * @see cxMapDetach() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cxMapRemoveAndGet( CxMap *map, const char *key @@ -751,7 +772,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put( CxMap *map, CxHashKey key, @@ -768,7 +789,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_cxstr( CxMap *map, cxstring key, @@ -785,7 +806,7 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_map_put_mustr( CxMap *map, cxmutstr key, @@ -802,7 +823,8 @@ * @param value the value * @return 0 on success, non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cx_map_put_str( CxMap *map, const char *key, @@ -834,7 +856,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get( const CxMap *map, CxHashKey key @@ -849,7 +872,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_cxstr( const CxMap *map, cxstring key @@ -864,7 +888,8 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_get_mustr( const CxMap *map, cxmutstr key @@ -879,7 +904,9 @@ * @param key the key * @return the value */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cx_map_get_str( const CxMap *map, const char *key @@ -908,7 +935,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_remove( CxMap *map, CxHashKey key @@ -922,7 +949,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_remove_cxstr( CxMap *map, cxstring key @@ -936,7 +963,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_remove_mustr( CxMap *map, cxmutstr key @@ -950,7 +977,8 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline void cx_map_remove_str( CxMap *map, const char *key @@ -987,7 +1015,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_detach( CxMap *map, CxHashKey key @@ -1002,7 +1030,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_detach_cxstr( CxMap *map, cxstring key @@ -1017,7 +1045,7 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cx_map_detach_mustr( CxMap *map, cxmutstr key @@ -1032,7 +1060,8 @@ * @param map the map * @param key the key */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline void cx_map_detach_str( CxMap *map, const char *key @@ -1070,7 +1099,8 @@ * @return the stored pointer or \c NULL if either the key is not present * in the map or the map is not storing pointers */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_remove_and_get( CxMap *map, CxHashKey key @@ -1086,7 +1116,8 @@ * @return the stored pointer or \c NULL if either the key is not present * in the map or the map is not storing pointers */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_remove_and_get_cxstr( CxMap *map, cxstring key @@ -1102,7 +1133,8 @@ * @return the stored pointer or \c NULL if either the key is not present * in the map or the map is not storing pointers */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cx_map_remove_and_get_mustr( CxMap *map, cxmutstr key @@ -1118,7 +1150,9 @@ * @return the stored pointer or \c NULL if either the key is not present * in the map or the map is not storing pointers */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(2) static inline void *cx_map_remove_and_get_str( CxMap *map, const char *key diff -r e8f354a25ac8 -r 68754c7de906 src/cx/mempool.h --- a/src/cx/mempool.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/mempool.h Thu Nov 07 22:46:58 2024 +0100 @@ -76,15 +76,24 @@ typedef struct cx_mempool_s CxMempool; /** + * Destroys a memory pool and frees the managed memory. + * + * @param pool the memory pool to destroy + */ +void cxMempoolDestroy(CxMempool *pool); + +/** * Creates an array-based memory pool with a shared destructor function. * * This destructor MUST NOT free the memory. * * @param capacity the initial capacity of the pool - * @param destr the destructor function to use for allocated memory + * @param destr optional destructor function to use for allocated memory * @return the created memory pool or \c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMempoolDestroy, 1) CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); /** @@ -93,18 +102,7 @@ * @param capacity the initial capacity of the pool * @return the created memory pool or \c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) -static inline CxMempool *cxBasicMempoolCreate(size_t capacity) { - return cxMempoolCreate(capacity, NULL); -} - -/** - * Destroys a memory pool and frees the managed memory. - * - * @param pool the memory pool to destroy - */ -__attribute__((__nonnull__)) -void cxMempoolDestroy(CxMempool *pool); +#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL) /** * Sets the destructor function for a specific allocated memory object. @@ -115,13 +113,24 @@ * @param memory the object allocated in the pool * @param fnc the destructor function */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxMempoolSetDestructor( void *memory, cx_destructor_func fnc ); /** + * Removes the destructor function for a specific allocated memory object. + * + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +void cxMempoolRemoveDestructor(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. @@ -130,11 +139,11 @@ * If that allocation fails, this function will return non-zero. * * @param pool the pool - * @param memory the object allocated in the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) * @param destr the destructor function * @return zero on success, non-zero on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxMempoolRegister( CxMempool *pool, void *memory, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/printf.h --- a/src/cx/printf.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/printf.h Thu Nov 07 22:46:58 2024 +0100 @@ -40,6 +40,14 @@ #include "string.h" #include +/** + * Attribute for printf-like functions. + * @param fmt_idx index of the format string parameter + * @param arg_idx index of the first formatting argument + */ +#define cx_attr_printf(fmt_idx, arg_idx) \ + __attribute__((__format__(printf, fmt_idx, arg_idx))) + #ifdef __cplusplus extern "C" { #endif @@ -60,7 +68,9 @@ * @param ... additional arguments * @return the total number of bytes written */ -__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4))) +cx_attr_nonnull_arg(1, 2, 3) +cx_attr_printf(3, 4) +cx_attr_cstr_arg(3) int cx_fprintf( void *stream, cx_write_func wfc, @@ -79,7 +89,8 @@ * @return the total number of bytes written * @see cx_fprintf() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(3) int cx_vfprintf( void *stream, cx_write_func wfc, @@ -99,7 +110,9 @@ * @return the formatted string * @see cx_strfree_a() */ -__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3))) +cx_attr_nonnull_arg(1, 2) +cx_attr_printf(2, 3) +cx_attr_cstr_arg(2) cxmutstr cx_asprintf_a( const CxAllocator *allocator, const char *fmt, @@ -132,7 +145,8 @@ * @return the formatted string * @see cx_asprintf_a() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) cxmutstr cx_vasprintf_a( const CxAllocator *allocator, const char *fmt, @@ -196,8 +210,16 @@ * @param ... additional arguments * @return the length of produced string */ -__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5))) -int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ); +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_printf(4, 5) +cx_attr_cstr_arg(4) +int cx_sprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + ... +); /** @@ -231,8 +253,15 @@ * @param ap argument list * @return the length of produced string */ -__attribute__((__nonnull__)) -int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(4) +int cx_vsprintf_a( + CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + va_list ap +); /** @@ -279,7 +308,17 @@ * @return the length of produced string */ __attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6))) -int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ); +cx_attr_nonnull_arg(1, 2, 4, 5) +cx_attr_printf(5, 6) +cx_attr_cstr_arg(5) +int cx_sprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + ... +); /** * An \c sprintf like function which allocates a new string when the buffer is not large enough. @@ -324,8 +363,16 @@ * @param ap argument list * @return the length of produced string */ -__attribute__((__nonnull__)) -int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(5) +int cx_vsprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +); #ifdef __cplusplus diff -r e8f354a25ac8 -r 68754c7de906 src/cx/properties.h --- a/src/cx/properties.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/properties.h Thu Nov 07 22:46:58 2024 +0100 @@ -227,7 +227,7 @@ * @param value the value * @return zero on success, non-zero when sinking the k/v-pair failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull typedef int(*cx_properties_sink_func)( CxProperties *prop, CxPropertiesSink *sink, @@ -273,7 +273,7 @@ * @param target a string buffer where the read data shall be stored * @return zero on success, non-zero when reading data failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull typedef int(*cx_properties_read_func)( CxProperties *prop, CxPropertiesSource *src, @@ -287,7 +287,7 @@ * @param src the source * @return zero when initialization was successful, non-zero otherwise */ -__attribute__((__nonnull__)) +cx_attr_nonnull typedef int(*cx_properties_read_init_func)( CxProperties *prop, CxPropertiesSource *src @@ -299,7 +299,7 @@ * @param prop the properties interface that wants to read from the source * @param src the source */ -__attribute__((__nonnull__)) +cx_attr_nonnull typedef void(*cx_properties_read_clean_func)( CxProperties *prop, CxPropertiesSource *src @@ -345,7 +345,7 @@ * @param config the properties configuration * @see cxPropertiesInitDefault() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); /** @@ -359,7 +359,7 @@ * * @param prop the properties interface */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxPropertiesDestroy(CxProperties *prop); /** @@ -388,7 +388,8 @@ * @param len the length of the data * @return non-zero when a memory allocation was necessary but failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(2, 3) int cxPropertiesFilln( CxProperties *prop, const char *buf, @@ -422,7 +423,7 @@ * @param str the string * @return non-zero when a memory allocation was necessary but failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_properties_fill_cxstr( CxProperties *prop, cxstring str @@ -437,7 +438,7 @@ * @param str the string * @return non-zero when a memory allocation was necessary but failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cx_properties_fill_mutstr( CxProperties *prop, cxmutstr str @@ -452,7 +453,8 @@ * @param str the string * @return non-zero when a memory allocation was necessary but failed */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) static inline int cx_properties_fill_str( CxProperties *prop, const char *str @@ -467,7 +469,7 @@ * @param buf a pointer to stack memory * @param capacity the capacity of the stack memory */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxPropertiesUseStack( CxProperties *prop, char *buf, @@ -494,7 +496,8 @@ * @return the status code as defined above * @see cxPropertiesFill() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxPropertiesStatus cxPropertiesNext( CxProperties *prop, cxstring *key, @@ -513,7 +516,8 @@ * @return the sink * @see cxPropertiesLoad() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxPropertiesSink cxPropertiesMapSink(CxMap *map); /** @@ -523,7 +527,7 @@ * @return the properties source * @see cxPropertiesLoad() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxPropertiesSource cxPropertiesStringSource(cxstring str); /** @@ -534,7 +538,9 @@ * @return the properties source * @see cxPropertiesLoad() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1, 2) CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); /** @@ -547,7 +553,9 @@ * @return the properties source * @see cxPropertiesLoad() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) CxPropertiesSource cxPropertiesCstrSource(const char *str); /** @@ -559,7 +567,9 @@ * @return the properties source * @see cxPropertiesLoad() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_r(1) CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); @@ -578,7 +588,7 @@ * @param source the source * @return the status of the last operation */ -__attribute__((__nonnull__)) +cx_attr_nonnull CxPropertiesStatus cxPropertiesLoad( CxProperties *prop, CxPropertiesSink sink, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/streams.h --- a/src/cx/streams.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/streams.h Thu Nov 07 22:46:58 2024 +0100 @@ -61,7 +61,10 @@ * iterations. * @return the total number of bytes copied */ -__attribute__((__nonnull__(1, 2, 3, 4))) +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_access_w(5) size_t cx_stream_bncopy( void *src, void *dest, @@ -100,7 +103,9 @@ * @param n the maximum number of bytes that shall be copied. * @return total number of bytes copied */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(1) +cx_attr_access_w(2) size_t cx_stream_ncopy( void *src, void *dest, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/string.h --- a/src/cx/string.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/string.h Thu Nov 07 22:46:58 2024 +0100 @@ -172,7 +172,9 @@ * * @see cx_mutstrn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) cxmutstr cx_mutstr(char *cstring); /** @@ -191,7 +193,8 @@ * * @see cx_mutstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_rw(1, 2) cxmutstr cx_mutstrn( char *cstring, size_t length @@ -212,7 +215,9 @@ * * @see cx_strn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) cxstring cx_str(const char *cstring); @@ -232,7 +237,8 @@ * * @see cx_str() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) cxstring cx_strn( const char *cstring, size_t length @@ -248,7 +254,7 @@ * @param str the mutable string to cast * @return an immutable copy of the string pointer */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strcast(cxmutstr str); /** @@ -262,7 +268,6 @@ * * @param str the string to free */ -__attribute__((__nonnull__)) void cx_strfree(cxmutstr *str); /** @@ -277,7 +282,7 @@ * @param alloc the allocator * @param str the string to free */ -__attribute__((__nonnull__)) +cx_attr_nonnull_arg(1) void cx_strfree_a( const CxAllocator *alloc, cxmutstr *str @@ -293,7 +298,7 @@ * @param ... all strings * @return the accumulated length of all strings */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard size_t cx_strlen( size_t count, ... @@ -317,7 +322,8 @@ * @param ... all other strings * @return the concatenated string */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strcat_ma( const CxAllocator *alloc, cxmutstr str, @@ -393,7 +399,7 @@ * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strsubs( cxstring string, size_t start @@ -418,7 +424,7 @@ * @see cx_strsubs_m() * @see cx_strsubsl_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strsubsl( cxstring string, size_t start, @@ -440,7 +446,7 @@ * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strsubs_m( cxmutstr string, size_t start @@ -465,7 +471,7 @@ * @see cx_strsubs() * @see cx_strsubsl() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strsubsl_m( cxmutstr string, size_t start, @@ -484,7 +490,7 @@ * * @see cx_strchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strchr( cxstring string, int chr @@ -502,7 +508,7 @@ * * @see cx_strchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strchr_m( cxmutstr string, int chr @@ -520,7 +526,7 @@ * * @see cx_strrchr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strrchr( cxstring string, int chr @@ -538,7 +544,7 @@ * * @see cx_strrchr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strrchr_m( cxmutstr string, int chr @@ -560,7 +566,7 @@ * contained * @see cx_strstr_m() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strstr( cxstring haystack, cxstring needle @@ -582,7 +588,7 @@ * contained * @see cx_strstr() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strstr_m( cxmutstr haystack, cxstring needle @@ -600,7 +606,9 @@ * @param output a pre-allocated array of at least \p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) size_t cx_strsplit( cxstring string, cxstring delim, @@ -627,7 +635,9 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) size_t cx_strsplit_a( const CxAllocator *allocator, cxstring string, @@ -649,7 +659,9 @@ * @param output a pre-allocated array of at least \p limit length * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(4, 3) size_t cx_strsplit_m( cxmutstr string, cxstring delim, @@ -676,7 +688,9 @@ * written to * @return the actual number of split items */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_access_w(5) size_t cx_strsplit_ma( const CxAllocator *allocator, cxmutstr string, @@ -693,7 +707,7 @@ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger * than \p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard int cx_strcmp( cxstring s1, cxstring s2 @@ -707,7 +721,7 @@ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger * than \p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard int cx_strcasecmp( cxstring s1, cxstring s2 @@ -723,7 +737,8 @@ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger * than \p s2, zero if both strings equal */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cx_strcmp_p( const void *s1, const void *s2 @@ -739,7 +754,8 @@ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger * than \p s2, zero if both strings equal ignoring case */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cx_strcasecmp_p( const void *s1, const void *s2 @@ -758,7 +774,8 @@ * @return a duplicate of the string * @see cx_strdup() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strdup_a( const CxAllocator *allocator, cxstring string @@ -816,7 +833,7 @@ * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxstring cx_strtrim(cxstring string); /** @@ -828,7 +845,7 @@ * @param string the string that shall be trimmed * @return the trimmed string */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard cxmutstr cx_strtrim_m(cxmutstr string); /** @@ -839,7 +856,7 @@ * @return \c true, if and only if the string has the specified prefix, * \c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strprefix( cxstring string, cxstring prefix @@ -853,7 +870,7 @@ * @return \c true, if and only if the string has the specified suffix, * \c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strsuffix( cxstring string, cxstring suffix @@ -867,7 +884,7 @@ * @return \c true, if and only if the string has the specified prefix, * \c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strcaseprefix( cxstring string, cxstring prefix @@ -881,7 +898,7 @@ * @return \c true, if and only if the string has the specified suffix, * \c false otherwise */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard bool cx_strcasesuffix( cxstring string, cxstring suffix @@ -926,7 +943,8 @@ * @param replmax maximum number of replacements * @return the resulting string after applying the replacements */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, @@ -1004,7 +1022,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok( cxstring str, cxstring delim, @@ -1019,7 +1037,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok_m( cxmutstr str, cxstring delim, @@ -1036,7 +1054,9 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -1054,7 +1074,9 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_access_w(2) bool cx_strtok_next_m( CxStrtokCtx *ctx, cxmutstr *token @@ -1067,7 +1089,8 @@ * @param delim array of more delimiters * @param count number of elements in the array */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_r(2, 3) void cx_strtok_delim( CxStrtokCtx *ctx, const cxstring *delim, diff -r e8f354a25ac8 -r 68754c7de906 src/cx/test.h --- a/src/cx/test.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/test.h Thu Nov 07 22:46:58 2024 +0100 @@ -96,6 +96,7 @@ typedef struct CxTestSuite CxTestSuite; /** Pointer to a test function. */ +cx_attr_nonnull typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func); /** Type for the internal list of test cases. */ @@ -137,6 +138,10 @@ * @param name optional name of the suite * @return a new test suite */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) +cx_attr_malloc static inline CxTestSuite* cx_test_suite_new(const char *name) { CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); if (suite != NULL) { @@ -154,6 +159,7 @@ * @param suite the test suite to destroy */ static inline void cx_test_suite_free(CxTestSuite* suite) { + if (suite == NULL) return; CxTestSet *l = suite->tests; while (l != NULL) { CxTestSet *e = l; @@ -170,6 +176,7 @@ * @param test the test function to register * @return zero on success or non-zero on failure */ +cx_attr_nonnull static inline int cx_test_register(CxTestSuite* suite, CxTest test) { CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); if (t) { @@ -196,6 +203,7 @@ * @param out_target the target buffer or file to write the output to * @param out_writer the write function writing to \p out_target */ +cx_attr_nonnull static inline void cx_test_run(CxTestSuite *suite, void *out_target, cx_write_func out_writer) { if (suite->name == NULL) { diff -r e8f354a25ac8 -r 68754c7de906 src/cx/tree.h --- a/src/cx/tree.h Thu Nov 07 20:22:56 2024 +0100 +++ b/src/cx/tree.h Thu Nov 07 22:46:58 2024 +0100 @@ -209,7 +209,7 @@ * Releases internal memory of the given tree iterator. * @param iter the iterator */ - __attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { free(iter->stack); iter->stack = NULL; @@ -219,7 +219,7 @@ * Releases internal memory of the given tree visitor. * @param visitor the visitor */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { struct cx_tree_visitor_queue_s *q = visitor->queue_next; while (q != NULL) { @@ -262,7 +262,7 @@ * @param loc_next offset in the node struct for the next pointer * @see cx_tree_unlink() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_link( void *restrict parent, void *restrict node, @@ -287,7 +287,7 @@ * @param loc_next offset in the node struct for the next pointer * @see cx_tree_link() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_unlink( void *node, ptrdiff_t loc_parent, @@ -328,6 +328,7 @@ * positive if one of the children might contain the data, * negative if neither the node, nor the children contains the data */ +cx_attr_nonnull typedef int (*cx_tree_search_data_func)(const void *node, const void *data); @@ -357,6 +358,7 @@ * positive if one of the children might contain the data, * negative if neither the node, nor the children contains the data */ +cx_attr_nonnull typedef int (*cx_tree_search_func)(const void *node, const void *new_node); /** @@ -383,7 +385,8 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search_data( const void *root, size_t depth, @@ -418,7 +421,8 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search( const void *root, size_t depth, @@ -449,6 +453,7 @@ * @return the new tree iterator * @see cxTreeIteratorDispose() */ +cx_attr_nodiscard CxTreeIterator cx_tree_iterator( void *root, bool visit_on_exit, @@ -474,6 +479,7 @@ * @return the new tree visitor * @see cxTreeVisitorDispose() */ +cx_attr_nodiscard CxTreeVisitor cx_tree_visitor( void *root, ptrdiff_t loc_children, @@ -490,6 +496,7 @@ * \note the function may leave the node pointers in the struct uninitialized. * The caller is responsible to set them according to the intended use case. */ +cx_attr_nonnull_arg(1) typedef void *(*cx_tree_node_create_func)(const void *, void *); /** @@ -538,7 +545,8 @@ * @return the number of nodes created and added * @see cx_tree_add() */ -__attribute__((__nonnull__(1, 3, 4, 6, 7))) +cx_attr_nonnull_arg(1, 3, 4, 6, 7) +cx_attr_access_w(6) size_t cx_tree_add_iter( struct cx_iterator_base_s *iter, size_t num, @@ -591,7 +599,8 @@ * @return the number of array elements successfully processed * @see cx_tree_add() */ -__attribute__((__nonnull__(1, 4, 5, 7, 8))) +cx_attr_nonnull_arg(1, 4, 5, 7, 8) +cx_attr_access_w(7) size_t cx_tree_add_array( const void *src, size_t num, @@ -653,7 +662,8 @@ * @return zero when a new node was created and added to the tree, * non-zero otherwise */ -__attribute__((__nonnull__(1, 2, 3, 5, 6))) +cx_attr_nonnull_arg(1, 2, 3, 5, 6) +cx_attr_access_w(5) int cx_tree_add( const void *src, cx_tree_search_func sfunc, @@ -829,6 +839,7 @@ * Implementations SHALL NOT simply invoke \p insert_many as this comes * with too much overhead. */ + cx_attr_nonnull int (*insert_element)( struct cx_tree_s *tree, const void *data @@ -840,6 +851,7 @@ * Implementations SHALL avoid to perform a full search in the tree for * every element even though the source data MAY be unsorted. */ + cx_attr_nonnull size_t (*insert_many)( struct cx_tree_s *tree, struct cx_iterator_base_s *iter, @@ -849,6 +861,7 @@ /** * Member function for finding a node. */ + cx_attr_nonnull void *(*find)( struct cx_tree_s *tree, const void *subtree, @@ -862,461 +875,6 @@ */ typedef struct cx_tree_s CxTree; -/** - * Creates a new tree structure based on the specified layout. - * - * The specified \p allocator will be used for creating the tree struct - * and SHALL be used by \p create_func to allocate memory for the nodes. - * - * \note This function will also register an advanced destructor which - * will free the nodes with the allocator's free() method. - * - * @param allocator the allocator that shall be used - * @param create_func a function that creates new nodes - * @param search_func a function that compares two nodes - * @param search_data_func a function that compares a node with data - * @param loc_parent offset in the node struct for the parent pointer - * @param loc_children offset in the node struct for the children linked list - * @param loc_last_child optional offset in the node struct for the pointer to - * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev optional offset in the node struct for the prev pointer - * @param loc_next offset in the node struct for the next pointer - * @return the new tree - * @see cxTreeCreateSimple() - * @see cxTreeCreateWrapped() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxTree *cxTreeCreate( - const CxAllocator *allocator, - cx_tree_node_create_func create_func, - cx_tree_search_func search_func, - cx_tree_search_data_func search_data_func, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); - -/** - * Creates a new tree structure based on a default layout. - * - * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first - * member (or at least respect the default offsets specified in the tree - * struct) and they MUST be allocated with the specified allocator. - * - * \note This function will also register an advanced destructor which - * will free the nodes with the allocator's free() method. - * - * @param allocator the allocator that shall be used - * @param create_func a function that creates new nodes - * @param search_func a function that compares two nodes - * @param search_data_func a function that compares a node with data - * @return the new tree - * @see cxTreeCreate() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTree *cxTreeCreateSimple( - const CxAllocator *allocator, - cx_tree_node_create_func create_func, - cx_tree_search_func search_func, - cx_tree_search_data_func search_data_func -) { - return cxTreeCreate( - allocator, - create_func, - search_func, - search_data_func, - cx_tree_node_base_layout - ); -} - -/** - * Creates a new tree structure based on an existing tree. - * - * The specified \p allocator will be used for creating the tree struct. - * - * \attention This function will create an incompletely defined tree structure - * where neither the create function, the search function, nor a destructor - * will be set. If you wish to use any of this functionality for the wrapped - * tree, you need to specify those functions afterwards. - * - * @param allocator the allocator that was used for nodes of the wrapped tree - * @param root the root node of the tree that shall be wrapped - * @param loc_parent offset in the node struct for the parent pointer - * @param loc_children offset in the node struct for the children linked list - * @param loc_last_child optional offset in the node struct for the pointer to - * the last child in the linked list (negative if there is no such pointer) - * @param loc_prev optional offset in the node struct for the prev pointer - * @param loc_next offset in the node struct for the next pointer - * @return the new tree - * @see cxTreeCreate() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -CxTree *cxTreeCreateWrapped( - const CxAllocator *allocator, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); - -/** - * Inserts data into the tree. - * - * \remark For this function to work, the tree needs specified search and - * create functions, which might not be available for wrapped trees - * (see #cxTreeCreateWrapped()). - * - * @param tree the tree - * @param data the data to insert - * @return zero on success, non-zero on failure - */ -__attribute__((__nonnull__)) -static inline int cxTreeInsert( - CxTree *tree, - const void *data -) { - return tree->cl->insert_element(tree, data); -} - -/** - * Inserts elements provided by an iterator efficiently into the tree. - * - * \remark For this function to work, the tree needs specified search and - * create functions, which might not be available for wrapped trees - * (see #cxTreeCreateWrapped()). - * - * @param tree the tree - * @param iter the iterator providing the elements - * @param n the maximum number of elements to insert - * @return the number of elements that could be successfully inserted - */ -__attribute__((__nonnull__)) -static inline size_t cxTreeInsertIter( - CxTree *tree, - struct cx_iterator_base_s *iter, - size_t n -) { - return tree->cl->insert_many(tree, iter, n); -} - -/** - * Inserts an array of data efficiently into the tree. - * - * \remark For this function to work, the tree needs specified search and - * create functions, which might not be available for wrapped trees - * (see #cxTreeCreateWrapped()). - * - * @param tree the tree - * @param data the array of data to insert - * @param elem_size the size of each element in the array - * @param n the number of elements in the array - * @return the number of elements that could be successfully inserted - */ -__attribute__((__nonnull__)) -static inline size_t cxTreeInsertArray( - CxTree *tree, - const void *data, - size_t elem_size, - size_t n -) { - if (n == 0) return 0; - if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; - CxIterator iter = cxIterator(data, elem_size, n); - return cxTreeInsertIter(tree, cxIteratorRef(iter), n); -} - -/** - * Searches the data in the specified tree. - * - * \remark For this function to work, the tree needs a specified \c search_data - * function, which might not be available wrapped trees - * (see #cxTreeCreateWrapped()). - * - * @param tree the tree - * @param data the data to search for - * @return the first matching node, or \c NULL when the data cannot be found - */ -__attribute__((__nonnull__)) -static inline void *cxTreeFind( - CxTree *tree, - const void *data -) { - return tree->cl->find(tree, tree->root, data, 0); -} - -/** - * Searches the data in the specified subtree. - * - * When \p max_depth is zero, the depth is not limited. - * The \p subtree_root itself is on depth 1 and its children have depth 2. - * - * \note When \p subtree_root is not part of the \p tree, the behavior is - * undefined. - * - * \remark For this function to work, the tree needs a specified \c search_data - * function, which might not be the case for wrapped trees - * (see #cxTreeCreateWrapped()). - * - * @param tree the tree - * @param data the data to search for - * @param subtree_root the node where to start - * @param max_depth the maximum search depth - * @return the first matching node, or \c NULL when the data cannot be found - */ -__attribute__((__nonnull__)) -static inline void *cxTreeFindInSubtree( - CxTree *tree, - const void *data, - void *subtree_root, - size_t max_depth -) { - return tree->cl->find(tree, subtree_root, data, max_depth); -} - -/** - * Determines the size of the specified subtree. - * - * @param tree the tree - * @param subtree_root the root node of the subtree - * @return the number of nodes in the specified subtree - */ -__attribute__((__nonnull__)) -size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); - -/** - * Determines the depth of the specified subtree. - * - * @param tree the tree - * @param subtree_root the root node of the subtree - * @return the tree depth including the \p subtree_root - */ -__attribute__((__nonnull__)) -size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); - -/** - * Determines the depth of the entire tree. - * - * @param tree the tree - * @return the tree depth, counting the root as one - */ -__attribute__((__nonnull__)) -size_t cxTreeDepth(CxTree *tree); - -/** - * Creates a depth-first iterator for the specified tree starting in \p node. - * - * If the node is not part of the tree, the behavior is undefined. - * - * @param tree the tree to iterate - * @param node the node where to start - * @param visit_on_exit true, if the iterator shall visit a node again when - * leaving the subtree - * @return a tree iterator (depth-first) - * @see cxTreeVisit() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeIterator cxTreeIterateSubtree( - CxTree *tree, - void *node, - bool visit_on_exit -) { - return cx_tree_iterator( - node, visit_on_exit, - tree->loc_children, tree->loc_next - ); -} - -/** - * Creates a breadth-first iterator for the specified tree starting in \p node. - * - * If the node is not part of the tree, the behavior is undefined. - * - * @param tree the tree to iterate - * @param node the node where to start - * @return a tree visitor (a.k.a. breadth-first iterator) - * @see cxTreeIterate() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { - return cx_tree_visitor( - node, tree->loc_children, tree->loc_next - ); -} - -/** - * Creates a depth-first iterator for the specified tree. - * - * @param tree the tree to iterate - * @param visit_on_exit true, if the iterator shall visit a node again when - * leaving the subtree - * @return a tree iterator (depth-first) - * @see cxTreeVisit() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeIterator cxTreeIterate( - CxTree *tree, - bool visit_on_exit -) { - return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); -} - -/** - * Creates a breadth-first iterator for the specified tree. - * - * @param tree the tree to iterate - * @return a tree visitor (a.k.a. breadth-first iterator) - * @see cxTreeIterate() - */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { - return cxTreeVisitSubtree(tree, tree->root); -} - -/** - * Sets the (new) parent of the specified child. - * - * If the \p child is not already member of the tree, this function behaves - * as #cxTreeAddChildNode(). - * - * @param tree the tree - * @param parent the (new) parent of the child - * @param child the node to add - * @see cxTreeAddChildNode() - */ -__attribute__((__nonnull__)) -void cxTreeSetParent( - CxTree *tree, - void *parent, - void *child -); - -/** - * Adds a new node to the tree. - * - * If the \p child is already member of the tree, the behavior is undefined. - * Use #cxTreeSetParent() if you want to move a subtree to another location. - * - * \attention The node may be externally created, but MUST obey the same rules - * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use - * the same allocator). - * - * @param tree the tree - * @param parent the parent of the node to add - * @param child the node to add - * @see cxTreeSetParent() - */ -__attribute__((__nonnull__)) -void cxTreeAddChildNode( - CxTree *tree, - void *parent, - void *child -); - -/** - * Creates a new node and adds it to the tree. - * - * With this function you can decide where exactly the new node shall be added. - * If you specified an appropriate search function, you may want to consider - * leaving this task to the tree by using #cxTreeInsert(). - * - * Be aware that adding nodes at arbitrary locations in the tree might cause - * wrong or undesired results when subsequently invoking #cxTreeInsert() and - * the invariant imposed by the search function does not hold any longer. - * - * @param tree the tree - * @param parent the parent node of the new node - * @param data the data that will be submitted to the create function - * @return zero when the new node was created, non-zero on allocation failure - * @see cxTreeInsert() - */ -__attribute__((__nonnull__)) -int cxTreeAddChild( - CxTree *tree, - void *parent, - const void *data -); - -/** - * A function that is invoked when a node needs to be re-linked to a new parent. - * - * When a node is re-linked, sometimes the contents need to be updated. - * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode() - * so that those updates can be applied when re-linking the children of the - * removed node. - * - * @param node the affected node - * @param old_parent the old parent of the node - * @param new_parent the new parent of the node - */ -typedef void (*cx_tree_relink_func)( - void *node, - const void *old_parent, - const void *new_parent -); - -/** - * Removes a node and re-links its children to its former parent. - * - * If the node is not part of the tree, the behavior is undefined. - * - * \note The destructor function, if any, will \em not be invoked. That means - * you will need to free the removed node by yourself, eventually. - * - * @param tree the tree - * @param node the node to remove (must not be the root node) - * @param relink_func optional callback to update the content of each re-linked - * node - * @return zero on success, non-zero if \p node is the root node of the tree - */ -__attribute__((__nonnull__(1,2))) -int cxTreeRemoveNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); - -/** - * Removes a node and it's subtree from the tree. - * - * If the node is not part of the tree, the behavior is undefined. - * - * \note The destructor function, if any, will \em not be invoked. That means - * you will need to free the removed subtree by yourself, eventually. - * - * @param tree the tree - * @param node the node to remove - */ -__attribute__((__nonnull__)) -void cxTreeRemoveSubtree(CxTree *tree, void *node); - -/** - * Destroys a node and re-links its children to its former parent. - * - * If the node is not part of the tree, the behavior is undefined. - * - * It is guaranteed that the simple destructor is invoked before - * the advanced destructor. - * - * \attention This function will not free the memory of the node with the - * tree's allocator, because that is usually done by the advanced destructor - * and would therefore result in a double-free. - * - * @param tree the tree - * @param node the node to destroy (must not be the root node) - * @param relink_func optional callback to update the content of each re-linked - * node - * @return zero on success, non-zero if \p node is the root node of the tree - */ -__attribute__((__nonnull__(1,2))) -int cxTreeDestroyNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); /** * Destroys a node and it's subtree. @@ -1339,7 +897,7 @@ * @param node the node to remove * @see cxTreeDestroy() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxTreeDestroySubtree(CxTree *tree, void *node); @@ -1378,14 +936,475 @@ * * @param tree the tree to destroy */ -__attribute__((__nonnull__)) static inline void cxTreeDestroy(CxTree *tree) { + if (tree == NULL) return; if (tree->root != NULL) { - cxTreeDestroySubtree(tree, tree->root); + cxTreeClear(tree); } cxFree(tree->allocator, tree); } +/** + * Creates a new tree structure based on the specified layout. + * + * The specified \p allocator will be used for creating the tree struct + * and SHALL be used by \p create_func to allocate memory for the nodes. + * + * \note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator the allocator that shall be used + * @param create_func a function that creates new nodes + * @param search_func a function that compares two nodes + * @param search_data_func a function that compares a node with data + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreateSimple() + * @see cxTreeCreateWrapped() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeDestroy, 1) +CxTree *cxTreeCreate( + const CxAllocator *allocator, + cx_tree_node_create_func create_func, + cx_tree_search_func search_func, + cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Creates a new tree structure based on a default layout. + * + * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first + * member (or at least respect the default offsets specified in the tree + * struct) and they MUST be allocated with the specified allocator. + * + * \note This function will also register an advanced destructor which + * will free the nodes with the allocator's free() method. + * + * @param allocator the allocator that shall be used + * @param create_func a function that creates new nodes + * @param search_func a function that compares two nodes + * @param search_data_func a function that compares a node with data + * @return the new tree + * @see cxTreeCreate() + */ +#define cxTreeCreateSimple(\ + allocator, create_func, search_func, search_data_func \ +) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ +cx_tree_node_base_layout) + +/** + * Creates a new tree structure based on an existing tree. + * + * The specified \p allocator will be used for creating the tree struct. + * + * \attention This function will create an incompletely defined tree structure + * where neither the create function, the search function, nor a destructor + * will be set. If you wish to use any of this functionality for the wrapped + * tree, you need to specify those functions afterwards. + * + * @param allocator the allocator that was used for nodes of the wrapped tree + * @param root the root node of the tree that shall be wrapped + * @param loc_parent offset in the node struct for the parent pointer + * @param loc_children offset in the node struct for the children linked list + * @param loc_last_child optional offset in the node struct for the pointer to + * the last child in the linked list (negative if there is no such pointer) + * @param loc_prev optional offset in the node struct for the prev pointer + * @param loc_next offset in the node struct for the next pointer + * @return the new tree + * @see cxTreeCreate() + */ +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeDestroy, 1) +CxTree *cxTreeCreateWrapped( + const CxAllocator *allocator, + void *root, + ptrdiff_t loc_parent, + ptrdiff_t loc_children, + ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +); + +/** + * Inserts data into the tree. + * + * \remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to insert + * @return zero on success, non-zero on failure + */ +cx_attr_nonnull +static inline int cxTreeInsert( + CxTree *tree, + const void *data +) { + return tree->cl->insert_element(tree, data); +} + +/** + * Inserts elements provided by an iterator efficiently into the tree. + * + * \remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param iter the iterator providing the elements + * @param n the maximum number of elements to insert + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertIter( + CxTree *tree, + struct cx_iterator_base_s *iter, + size_t n +) { + return tree->cl->insert_many(tree, iter, n); +} + +/** + * Inserts an array of data efficiently into the tree. + * + * \remark For this function to work, the tree needs specified search and + * create functions, which might not be available for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the array of data to insert + * @param elem_size the size of each element in the array + * @param n the number of elements in the array + * @return the number of elements that could be successfully inserted + */ +cx_attr_nonnull +static inline size_t cxTreeInsertArray( + CxTree *tree, + const void *data, + size_t elem_size, + size_t n +) { + if (n == 0) return 0; + if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; + CxIterator iter = cxIterator(data, elem_size, n); + return cxTreeInsertIter(tree, cxIteratorRef(iter), n); +} + +/** + * Searches the data in the specified tree. + * + * \remark For this function to work, the tree needs a specified \c search_data + * function, which might not be available wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @return the first matching node, or \c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFind( + CxTree *tree, + const void *data +) { + return tree->cl->find(tree, tree->root, data, 0); +} + +/** + * Searches the data in the specified subtree. + * + * When \p max_depth is zero, the depth is not limited. + * The \p subtree_root itself is on depth 1 and its children have depth 2. + * + * \note When \p subtree_root is not part of the \p tree, the behavior is + * undefined. + * + * \remark For this function to work, the tree needs a specified \c search_data + * function, which might not be the case for wrapped trees + * (see #cxTreeCreateWrapped()). + * + * @param tree the tree + * @param data the data to search for + * @param subtree_root the node where to start + * @param max_depth the maximum search depth + * @return the first matching node, or \c NULL when the data cannot be found + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline void *cxTreeFindInSubtree( + CxTree *tree, + const void *data, + void *subtree_root, + size_t max_depth +) { + return tree->cl->find(tree, subtree_root, data, max_depth); +} + +/** + * Determines the size of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the number of nodes in the specified subtree + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the specified subtree. + * + * @param tree the tree + * @param subtree_root the root node of the subtree + * @return the tree depth including the \p subtree_root + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); + +/** + * Determines the depth of the entire tree. + * + * @param tree the tree + * @return the tree depth, counting the root as one + */ +cx_attr_nonnull +cx_attr_nodiscard +size_t cxTreeDepth(CxTree *tree); + +/** + * Creates a depth-first iterator for the specified tree starting in \p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterateSubtree( + CxTree *tree, + void *node, + bool visit_on_exit +) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a breadth-first iterator for the specified tree starting in \p node. + * + * If the node is not part of the tree, the behavior is undefined. + * + * @param tree the tree to iterate + * @param node the node where to start + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +/** + * Creates a depth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @param visit_on_exit true, if the iterator shall visit a node again when + * leaving the subtree + * @return a tree iterator (depth-first) + * @see cxTreeVisit() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterate( + CxTree *tree, + bool visit_on_exit +) { + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); +} + +/** + * Creates a breadth-first iterator for the specified tree. + * + * @param tree the tree to iterate + * @return a tree visitor (a.k.a. breadth-first iterator) + * @see cxTreeIterate() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); +} + +/** + * Sets the (new) parent of the specified child. + * + * If the \p child is not already member of the tree, this function behaves + * as #cxTreeAddChildNode(). + * + * @param tree the tree + * @param parent the (new) parent of the child + * @param child the node to add + * @see cxTreeAddChildNode() + */ +cx_attr_nonnull +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +); + +/** + * Adds a new node to the tree. + * + * If the \p child is already member of the tree, the behavior is undefined. + * Use #cxTreeSetParent() if you want to move a subtree to another location. + * + * \attention The node may be externally created, but MUST obey the same rules + * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * the same allocator). + * + * @param tree the tree + * @param parent the parent of the node to add + * @param child the node to add + * @see cxTreeSetParent() + */ +cx_attr_nonnull +void cxTreeAddChildNode( + CxTree *tree, + void *parent, + void *child +); + +/** + * Creates a new node and adds it to the tree. + * + * With this function you can decide where exactly the new node shall be added. + * If you specified an appropriate search function, you may want to consider + * leaving this task to the tree by using #cxTreeInsert(). + * + * Be aware that adding nodes at arbitrary locations in the tree might cause + * wrong or undesired results when subsequently invoking #cxTreeInsert() and + * the invariant imposed by the search function does not hold any longer. + * + * @param tree the tree + * @param parent the parent node of the new node + * @param data the data that will be submitted to the create function + * @return zero when the new node was created, non-zero on allocation failure + * @see cxTreeInsert() + */ +cx_attr_nonnull +int cxTreeAddChild( + CxTree *tree, + void *parent, + const void *data +); + +/** + * A function that is invoked when a node needs to be re-linked to a new parent. + * + * When a node is re-linked, sometimes the contents need to be updated. + * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode() + * so that those updates can be applied when re-linking the children of the + * removed node. + * + * @param node the affected node + * @param old_parent the old parent of the node + * @param new_parent the new parent of the node + */ +cx_attr_nonnull +typedef void (*cx_tree_relink_func)( + void *node, + const void *old_parent, + const void *new_parent +); + +/** + * Removes a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * \note The destructor function, if any, will \em not be invoked. That means + * you will need to free the removed node by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if \p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +int cxTreeRemoveNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + +/** + * Removes a node and it's subtree from the tree. + * + * If the node is not part of the tree, the behavior is undefined. + * + * \note The destructor function, if any, will \em not be invoked. That means + * you will need to free the removed subtree by yourself, eventually. + * + * @param tree the tree + * @param node the node to remove + */ +cx_attr_nonnull +void cxTreeRemoveSubtree(CxTree *tree, void *node); + +/** + * Destroys a node and re-links its children to its former parent. + * + * If the node is not part of the tree, the behavior is undefined. + * + * It is guaranteed that the simple destructor is invoked before + * the advanced destructor. + * + * \attention This function will not free the memory of the node with the + * tree's allocator, because that is usually done by the advanced destructor + * and would therefore result in a double-free. + * + * @param tree the tree + * @param node the node to destroy (must not be the root node) + * @param relink_func optional callback to update the content of each re-linked + * node + * @return zero on success, non-zero if \p node is the root node of the tree + */ +cx_attr_nonnull_arg(1, 2) +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + #ifdef __cplusplus } // extern "C" #endif diff -r e8f354a25ac8 -r 68754c7de906 src/list.c --- a/src/list.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/list.c Thu Nov 07 22:46:58 2024 +0100 @@ -212,33 +212,33 @@ // -static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) { +static void cx_emptyl_noop(cx_attr_unused CxList *list) { // this is a noop, but MUST be implemented } static void *cx_emptyl_at( - __attribute__((__unused__)) const struct cx_list_s *list, - __attribute__((__unused__)) size_t index + cx_attr_unused const struct cx_list_s *list, + cx_attr_unused size_t index ) { return NULL; } static ssize_t cx_emptyl_find_remove( - __attribute__((__unused__)) struct cx_list_s *list, - __attribute__((__unused__)) const void *elem, - __attribute__((__unused__)) bool remove + cx_attr_unused struct cx_list_s *list, + cx_attr_unused const void *elem, + cx_attr_unused bool remove ) { return -1; } -static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_emptyl_iterator( const struct cx_list_s *list, size_t index, - __attribute__((__unused__)) bool backwards + cx_attr_unused bool backwards ) { CxIterator iter = {0}; iter.src_handle.c = list; @@ -419,10 +419,6 @@ return 0; } -void cxListDestroy(CxList *list) { - list->cl->destructor(list); -} - int cxListCompare( const CxList *list, const CxList *other diff -r e8f354a25ac8 -r 68754c7de906 src/map.c --- a/src/map.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/map.c Thu Nov 07 22:46:58 2024 +0100 @@ -31,24 +31,24 @@ // -static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) { +static void cx_empty_map_noop(cx_attr_unused CxMap *map) { // this is a noop, but MUST be implemented } static void *cx_empty_map_get( - __attribute__((__unused__)) const CxMap *map, - __attribute__((__unused__)) CxHashKey key + cx_attr_unused const CxMap *map, + cx_attr_unused CxHashKey key ) { return NULL; } -static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_empty_map_iterator( const struct cx_map_s *map, - __attribute__((__unused__)) enum cx_map_iterator_type type + cx_attr_unused enum cx_map_iterator_type type ) { CxIterator iter = {0}; iter.src_handle.c = map; diff -r e8f354a25ac8 -r 68754c7de906 src/mempool.c --- a/src/mempool.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/mempool.c Thu Nov 07 22:46:58 2024 +0100 @@ -138,6 +138,7 @@ } void cxMempoolDestroy(CxMempool *pool) { + if (pool == NULL) return; struct cx_mempool_memory_s *mem; for (size_t i = 0; i < pool->size; i++) { mem = pool->data[i]; @@ -158,6 +159,10 @@ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } +void cxMempoolRemoveDestructor(void *ptr) { + *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; +} + struct cx_mempool_foreign_mem_s { cx_destructor_func destr; void* mem; diff -r e8f354a25ac8 -r 68754c7de906 src/properties.c --- a/src/properties.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/properties.c Thu Nov 07 22:46:58 2024 +0100 @@ -284,7 +284,7 @@ } static int cx_properties_sink_map( - __attribute__((__unused__)) CxProperties *prop, + cx_attr_unused CxProperties *prop, CxPropertiesSink *sink, cxstring key, cxstring value @@ -322,7 +322,7 @@ } static int cx_properties_read_file( - __attribute__((__unused__)) CxProperties *prop, + cx_attr_unused CxProperties *prop, CxPropertiesSource *src, cxstring *target ) { @@ -332,7 +332,7 @@ } static int cx_properties_read_init_file( - __attribute__((__unused__)) CxProperties *prop, + cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { src->data_ptr = malloc(src->data_size); @@ -341,7 +341,7 @@ } static void cx_properties_read_clean_file( - __attribute__((__unused__)) CxProperties *prop, + cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { free(src->data_ptr); diff -r e8f354a25ac8 -r 68754c7de906 src/string.c --- a/src/string.c Thu Nov 07 20:22:56 2024 +0100 +++ b/src/string.c Thu Nov 07 22:46:58 2024 +0100 @@ -65,6 +65,7 @@ } void cx_strfree(cxmutstr *str) { + if (str == NULL) return; free(str->ptr); str->ptr = NULL; str->length = 0; @@ -74,6 +75,7 @@ const CxAllocator *alloc, cxmutstr *str ) { + if (str == NULL) return; cxFree(alloc, str->ptr); str->ptr = NULL; str->length = 0; diff -r e8f354a25ac8 -r 68754c7de906 tests/test_allocator.c --- a/tests/test_allocator.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_allocator.c Thu Nov 07 22:46:58 2024 +0100 @@ -98,7 +98,7 @@ } CX_TEST(test_allocator_default_free) { - void *test = malloc(16); + void *test = cxMalloc(cxDefaultAllocator, 16); CX_TEST_DO { // we cannot assert sth. but valgrind will detect an error cxFree(cxDefaultAllocator, test); @@ -181,9 +181,9 @@ } static void *test_allocator_mock_failing_realloc( - __attribute__((__unused__))void *p, - __attribute__((__unused__))void *d, - __attribute__((__unused__))size_t n + cx_attr_unused void *p, + cx_attr_unused void *d, + cx_attr_unused size_t n ) { return NULL; } diff -r e8f354a25ac8 -r 68754c7de906 tests/test_buffer.c --- a/tests/test_buffer.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_buffer.c Thu Nov 07 22:46:58 2024 +0100 @@ -588,7 +588,7 @@ static size_t mock_write_limited_rate( const void *ptr, size_t size, - __attribute__((unused)) size_t nitems, + cx_attr_unused size_t nitems, CxBuffer *buffer ) { // simulate limited target drain capacity diff -r e8f354a25ac8 -r 68754c7de906 tests/test_hash_map.c --- a/tests/test_hash_map.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_hash_map.c Thu Nov 07 22:46:58 2024 +0100 @@ -265,7 +265,7 @@ } static void test_advanced_destructor( - __attribute__((__unused__)) void *unused, + cx_attr_unused void *unused, void *data ) { strcpy(data, "OK"); diff -r e8f354a25ac8 -r 68754c7de906 tests/test_list.c --- a/tests/test_list.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_list.c Thu Nov 07 22:46:58 2024 +0100 @@ -1165,7 +1165,7 @@ } #define roll_out_test_combos(name, body) \ static CX_TEST_SUBROUTINE(test_list_verify_##name, CxList *list, \ - __attribute__((__unused__)) bool isptrlist) body \ + cx_attr_unused bool isptrlist) body \ roll_out_test_invokers(name) static void set_default_class_funcs(CxList *list, cx_list_class *defaulted_cl) { @@ -1188,7 +1188,7 @@ set_default_class_funcs(list, &defaulted_cl) #define roll_out_test_combos_with_defaulted_funcs(name, body) \ static CX_TEST_SUBROUTINE(test_list_verify_##name, CxList *list, \ - __attribute__((__unused__)) bool isptrlist) body \ + cx_attr_unused bool isptrlist) body \ roll_out_test_invokers(name) \ CX_TEST(test_list_llm_##name) { \ set_up_combo \ @@ -1387,7 +1387,7 @@ }) static unsigned test_remove_array_destr_ctr; -static void test_remove_array_destr(__attribute__((__unused__)) void *d) { +static void test_remove_array_destr(cx_attr_unused void *d) { test_remove_array_destr_ctr++; } @@ -1767,7 +1767,7 @@ destr_test_ctr++; } -static void advanced_destr_test_fun(__attribute__((__unused__)) void *u, void *data) { +static void advanced_destr_test_fun(cx_attr_unused void *u, void *data) { simple_destr_test_fun(data); } diff -r e8f354a25ac8 -r 68754c7de906 tests/test_mempool.c --- a/tests/test_mempool.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_mempool.c Thu Nov 07 22:46:58 2024 +0100 @@ -81,7 +81,7 @@ static unsigned test_mempool_destructor_called; -static void test_mempool_destructor(__attribute__((__unused__)) void *mem) { +static void test_mempool_destructor(cx_attr_unused void *mem) { test_mempool_destructor_called++; } @@ -119,10 +119,10 @@ cxFree(pool->allocator, mem1); CX_TEST_ASSERT(pool->size == 0); - cxMalloc(pool->allocator, 16); - cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); mem1 = cxMalloc(pool->allocator, 16); - cxMalloc(pool->allocator, 16); + mem1 = cxMalloc(pool->allocator, 16); + mem2 = cxMalloc(pool->allocator, 16); mem2 = cxMalloc(pool->allocator, 16); CX_TEST_ASSERT(pool->size == 5); diff -r e8f354a25ac8 -r 68754c7de906 tests/test_tree.c --- a/tests/test_tree.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/test_tree.c Thu Nov 07 22:46:58 2024 +0100 @@ -1957,8 +1957,8 @@ static void test_tree_remove_node_relink_mock( void *node, - __attribute__((__unused__)) const void *oldp, - __attribute__((__unused__)) const void *newp + cx_attr_unused const void *oldp, + cx_attr_unused const void *newp ) { tree_node_file * n = node; // this function fakes the relink logic in below test @@ -2175,7 +2175,7 @@ w->destructor_data = alloc; cxTreeDestroySubtree(w, w->root); CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); - cxFree(alloc, w); + cxTreeDestroy(w); CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); } cx_testing_allocator_destroy(&talloc); diff -r e8f354a25ac8 -r 68754c7de906 tests/util_allocator.c --- a/tests/util_allocator.c Thu Nov 07 20:22:56 2024 +0100 +++ b/tests/util_allocator.c Thu Nov 07 22:46:58 2024 +0100 @@ -29,6 +29,11 @@ #include "util_allocator.h" #include "cx/test.h" +#if !defined(__clang__) && __GNUC__ > 11 +// this utility is explicitly designed to track UAF +#pragma GCC diagnostic ignored "-Wuse-after-free" +#endif + static void cx_testing_allocator_track(CxTestingAllocator *alloc, void *ptr) { for (size_t i = 0; i < alloc->tracked_count; i++) { if (alloc->tracked[i] == ptr) return; // is already tracked @@ -83,16 +88,9 @@ data->alloc_failed++; } else { data->free_total++; -#if !defined(__clang__) && __GNUC__ > 11 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" -#endif if (!cx_testing_allocator_untrack(data, mem)) { data->free_failed++; } -#if !defined(__clang__) && __GNUC__ > 11 -#pragma GCC diagnostic pop -#endif cx_testing_allocator_track(data, ptr); } return ptr;