add cx_sprintf() variants - fixes #353

Tue, 16 Jan 2024 23:13:01 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 16 Jan 2024 23:13:01 +0100
changeset 810
85859399a0cc
parent 809
b7e2e1d7ed22
child 811
b02ad05c7f2d

add cx_sprintf() variants - fixes #353

CHANGELOG file | annotate | diff | comparison | revisions
src/cx/printf.h file | annotate | diff | comparison | revisions
src/printf.c file | annotate | diff | comparison | revisions
tests/test_printf.c file | annotate | diff | comparison | revisions
     1.1 --- a/CHANGELOG	Tue Jan 16 23:12:43 2024 +0100
     1.2 +++ b/CHANGELOG	Tue Jan 16 23:13:01 2024 +0100
     1.3 @@ -4,6 +4,7 @@
     1.4   * adds cxListFindRemove()
     1.5   * adds cxBufferReset()
     1.6   * adds cx_cmp_ptr()
     1.7 + * adds cx_sprintf() and several more variants
     1.8   * adds runtime constants to read out the actual SBO sizes
     1.9   * adds improved version of UCX 2 Test framework (now a self-contained header)
    1.10   * the cx_compare_func symbol is now also declared by compare.h
     2.1 --- a/src/cx/printf.h	Tue Jan 16 23:12:43 2024 +0100
     2.2 +++ b/src/cx/printf.h	Tue Jan 16 23:13:01 2024 +0100
     2.3 @@ -164,6 +164,170 @@
     2.4  #define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
     2.5      (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
     2.6  
     2.7 +
     2.8 +/**
     2.9 + * An \c sprintf like function which reallocates the string when the buffer is not large enough.
    2.10 + *
    2.11 + * \note The resulting string is guaranteed to be zero-terminated.
    2.12 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
    2.13 + * the length returned by this function plus one.
    2.14 + *
    2.15 + * @param str a pointer to the string buffer
    2.16 + * @param len the current length of the buffer
    2.17 + * @param fmt the format string
    2.18 + * @param ... additional arguments
    2.19 + * @return the length of produced string
    2.20 + */
    2.21 +#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
    2.22 +
    2.23 +/**
    2.24 + * An \c sprintf like function which reallocates the string when the buffer is not large enough.
    2.25 + *
    2.26 + * \note The resulting string is guaranteed to be zero-terminated.
    2.27 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
    2.28 + * the length returned by this function plus one.
    2.29 + *
    2.30 + * \attention The original buffer MUST have been allocated with the same allocator!
    2.31 + *
    2.32 + * @param alloc the allocator to use
    2.33 + * @param str a pointer to the string buffer
    2.34 + * @param len the current length of the buffer
    2.35 + * @param fmt the format string
    2.36 + * @param ... additional arguments
    2.37 + * @return the length of produced string
    2.38 + */
    2.39 +__attribute__((__nonnull__(1, 2, 4), __format__(printf, 4, 5)))
    2.40 +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... );
    2.41 +
    2.42 +
    2.43 +/**
    2.44 + * An \c sprintf like function which reallocates the string when the buffer is not large enough.
    2.45 + *
    2.46 + * \note The resulting string is guaranteed to be zero-terminated.
    2.47 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
    2.48 + * the length returned by this function plus one.
    2.49 + *
    2.50 + * @param str a pointer to the string buffer
    2.51 + * @param len the current length of the buffer
    2.52 + * @param fmt the format string
    2.53 + * @param ap argument list
    2.54 + * @return the length of produced string
    2.55 + */
    2.56 +#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
    2.57 +
    2.58 +/**
    2.59 + * An \c sprintf like function which reallocates the string when the buffer is not large enough.
    2.60 + *
    2.61 + * \note The resulting string is guaranteed to be zero-terminated.
    2.62 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
    2.63 + * the length returned by this function plus one.
    2.64 + *
    2.65 + * \attention The original buffer MUST have been allocated with the same allocator!
    2.66 + *
    2.67 + * @param alloc the allocator to use
    2.68 + * @param str a pointer to the string buffer
    2.69 + * @param len the current length of the buffer
    2.70 + * @param fmt the format string
    2.71 + * @param ap argument list
    2.72 + * @return the length of produced string
    2.73 + */
    2.74 +__attribute__((__nonnull__))
    2.75 +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap);
    2.76 +
    2.77 +
    2.78 +/**
    2.79 + * An \c sprintf like function which allocates a new string when the buffer is not large enough.
    2.80 + *
    2.81 + * The location of the resulting string will \em always be stored to \p str. When the buffer
    2.82 + * was sufficiently large, \p buf itself will be stored to the location of \p str.
    2.83 + *
    2.84 + * \note The resulting string is guaranteed to be zero-terminated.
    2.85 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
    2.86 + * the length returned by this function plus one.
    2.87 + * 
    2.88 + * \remark When a new string needed to be allocated, the contents of \p buf will be
    2.89 + * poisoned after the call, because this function tries to produce the string in \p buf, first.
    2.90 + *
    2.91 + * @param buf a pointer to the buffer
    2.92 + * @param len the length of the buffer
    2.93 + * @param str a pointer to the location
    2.94 + * @param fmt the format string
    2.95 + * @param ... additional arguments
    2.96 + * @return the length of produced string
    2.97 + */
    2.98 +#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
    2.99 +
   2.100 +/**
   2.101 + * An \c sprintf like function which allocates a new string when the buffer is not large enough.
   2.102 + *
   2.103 + * The location of the resulting string will \em always be stored to \p str. When the buffer
   2.104 + * was sufficiently large, \p buf itself will be stored to the location of \p str.
   2.105 + *
   2.106 + * \note The resulting string is guaranteed to be zero-terminated.
   2.107 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
   2.108 + * the length returned by this function plus one.
   2.109 + *
   2.110 + * \remark When a new string needed to be allocated, the contents of \p buf will be
   2.111 + * poisoned after the call, because this function tries to produce the string in \p buf, first.
   2.112 + *
   2.113 + * @param alloc the allocator to use
   2.114 + * @param buf a pointer to the buffer
   2.115 + * @param len the length of the buffer
   2.116 + * @param str a pointer to the location
   2.117 + * @param fmt the format string
   2.118 + * @param ... additional arguments
   2.119 + * @return the length of produced string
   2.120 + */
   2.121 +__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
   2.122 +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... );
   2.123 +
   2.124 +/**
   2.125 + * An \c sprintf like function which allocates a new string when the buffer is not large enough.
   2.126 + *
   2.127 + * The location of the resulting string will \em always be stored to \p str. When the buffer
   2.128 + * was sufficiently large, \p buf itself will be stored to the location of \p str.
   2.129 + *
   2.130 + * \note The resulting string is guaranteed to be zero-terminated.
   2.131 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
   2.132 + * the length returned by this function plus one.
   2.133 + *
   2.134 + * \remark When a new string needed to be allocated, the contents of \p buf will be
   2.135 + * poisoned after the call, because this function tries to produce the string in \p buf, first.
   2.136 + *
   2.137 + * @param buf a pointer to the buffer
   2.138 + * @param len the length of the buffer
   2.139 + * @param str a pointer to the location
   2.140 + * @param fmt the format string
   2.141 + * @param ap argument list
   2.142 + * @return the length of produced string
   2.143 + */
   2.144 +#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
   2.145 +
   2.146 +/**
   2.147 + * An \c sprintf like function which allocates a new string when the buffer is not large enough.
   2.148 + *
   2.149 + * The location of the resulting string will \em always be stored to \p str. When the buffer
   2.150 + * was sufficiently large, \p buf itself will be stored to the location of \p str.
   2.151 + *
   2.152 + * \note The resulting string is guaranteed to be zero-terminated.
   2.153 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be
   2.154 + * the length returned by this function plus one.
   2.155 + *
   2.156 + * \remark When a new string needed to be allocated, the contents of \p buf will be
   2.157 + * poisoned after the call, because this function tries to produce the string in \p buf, first.
   2.158 + *
   2.159 + * @param alloc the allocator to use
   2.160 + * @param buf a pointer to the buffer
   2.161 + * @param len the length of the buffer
   2.162 + * @param str a pointer to the location
   2.163 + * @param fmt the format string
   2.164 + * @param ap argument list
   2.165 + * @return the length of produced string
   2.166 + */
   2.167 +__attribute__((__nonnull__))
   2.168 +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap);
   2.169 +
   2.170 +
   2.171  #ifdef __cplusplus
   2.172  } // extern "C"
   2.173  #endif
     3.1 --- a/src/printf.c	Tue Jan 16 23:12:43 2024 +0100
     3.2 +++ b/src/printf.c	Tue Jan 16 23:13:01 2024 +0100
     3.3 @@ -127,3 +127,73 @@
     3.4      return s;
     3.5  }
     3.6  
     3.7 +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... ) {
     3.8 +    va_list ap;
     3.9 +    va_start(ap, fmt);
    3.10 +    int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
    3.11 +    va_end(ap);
    3.12 +    return ret;
    3.13 +}
    3.14 +
    3.15 +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap) {
    3.16 +    va_list ap2;
    3.17 +    va_copy(ap2, ap);
    3.18 +    int ret = vsnprintf(*str, len, fmt, ap);
    3.19 +    if (ret < 0 || ((unsigned)ret) < len) {
    3.20 +        va_end(ap2);
    3.21 +        return ret;
    3.22 +    } else {
    3.23 +        unsigned newlen = ret + 1;
    3.24 +        char *ptr = cxRealloc(alloc, *str, newlen);
    3.25 +        if (ptr) {
    3.26 +            int newret = vsnprintf(ptr, newlen, fmt, ap2);
    3.27 +            va_end(ap2);
    3.28 +            if (newret < 0) {
    3.29 +                cxFree(alloc, ptr);
    3.30 +                return ret;
    3.31 +            } else {
    3.32 +                *str = ptr;
    3.33 +                return newret;
    3.34 +            }
    3.35 +        } else {
    3.36 +            va_end(ap2);
    3.37 +            return ret;
    3.38 +        }
    3.39 +    }
    3.40 +}
    3.41 +
    3.42 +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... ) {
    3.43 +    va_list ap;
    3.44 +    va_start(ap, fmt);
    3.45 +    int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
    3.46 +    va_end(ap);
    3.47 +    return ret;
    3.48 +}
    3.49 +
    3.50 +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap) {
    3.51 +    va_list ap2;
    3.52 +    va_copy(ap2, ap);
    3.53 +    int ret = vsnprintf(buf, len, fmt, ap);
    3.54 +    *str = buf;
    3.55 +    if (ret < 0 || ((unsigned)ret) < len) {
    3.56 +        va_end(ap2);
    3.57 +        return ret;
    3.58 +    } else {
    3.59 +        unsigned newlen = ret + 1;
    3.60 +        char *ptr = cxMalloc(alloc, newlen);
    3.61 +        if (ptr) {
    3.62 +            int newret = vsnprintf(ptr, newlen, fmt, ap2);
    3.63 +            va_end(ap2);
    3.64 +            if (newret < 0) {
    3.65 +                cxFree(alloc, ptr);
    3.66 +                return ret;
    3.67 +            } else {
    3.68 +                *str = ptr;
    3.69 +                return newret;
    3.70 +            }
    3.71 +        } else {
    3.72 +            va_end(ap2);
    3.73 +            return ret;
    3.74 +        }
    3.75 +    }
    3.76 +}
     4.1 --- a/tests/test_printf.c	Tue Jan 16 23:12:43 2024 +0100
     4.2 +++ b/tests/test_printf.c	Tue Jan 16 23:13:01 2024 +0100
     4.3 @@ -294,6 +294,107 @@
     4.4      free(expected);
     4.5  }
     4.6  
     4.7 +CX_TEST(test_sprintf_no_realloc) {
     4.8 +    char *buf = malloc(16);
     4.9 +    CxTestingAllocator talloc;
    4.10 +    cx_testing_allocator_init(&talloc);
    4.11 +    CxAllocator *alloc = &talloc.base;
    4.12 +    CX_TEST_DO {
    4.13 +        char *oldbuf = buf;
    4.14 +        size_t len = cx_sprintf_a(alloc, &buf, 16, "Test %d %s", 47, "string");
    4.15 +        CX_TEST_ASSERT(oldbuf == buf);
    4.16 +        CX_TEST_ASSERT(len == 14);
    4.17 +        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15));
    4.18 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    4.19 +    }
    4.20 +    cx_testing_allocator_destroy(&talloc);
    4.21 +    free(buf);
    4.22 +}
    4.23 +
    4.24 +CX_TEST(test_sprintf_realloc) {
    4.25 +    CxTestingAllocator talloc;
    4.26 +    cx_testing_allocator_init(&talloc);
    4.27 +    CxAllocator *alloc = &talloc.base;
    4.28 +    char *buf = cxMalloc(alloc, 8);
    4.29 +    CX_TEST_DO {
    4.30 +        size_t len = cx_sprintf_a(alloc, &buf, 8, "Test %d %s", 47, "foobar");
    4.31 +        CX_TEST_ASSERT(len == 14);
    4.32 +        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 foobar", 15));
    4.33 +        cxFree(alloc, buf);
    4.34 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    4.35 +    }
    4.36 +    cx_testing_allocator_destroy(&talloc);
    4.37 +}
    4.38 +
    4.39 +CX_TEST(test_sprintf_realloc_to_fit_terminator) {
    4.40 +    CxTestingAllocator talloc;
    4.41 +    cx_testing_allocator_init(&talloc);
    4.42 +    CxAllocator *alloc = &talloc.base;
    4.43 +    // make it so that only the zero-terminator does not fit
    4.44 +    char *buf = cxMalloc(alloc, 14);
    4.45 +    CX_TEST_DO {
    4.46 +        size_t len = cx_sprintf_a(alloc, &buf, 14, "Test %d %s", 13, "string");
    4.47 +        CX_TEST_ASSERT(len == 14);
    4.48 +        CX_TEST_ASSERT(0 == memcmp(buf, "Test 13 string", 15));
    4.49 +        cxFree(alloc, buf);
    4.50 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    4.51 +    }
    4.52 +    cx_testing_allocator_destroy(&talloc);
    4.53 +}
    4.54 +
    4.55 +CX_TEST(test_sprintf_s_no_alloc) {
    4.56 +    char buf[16];
    4.57 +    CxTestingAllocator talloc;
    4.58 +    cx_testing_allocator_init(&talloc);
    4.59 +    CxAllocator *alloc = &talloc.base;
    4.60 +    CX_TEST_DO {
    4.61 +        char *str;
    4.62 +        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Test %d %s", 47, "string");
    4.63 +        CX_TEST_ASSERT(str == buf);
    4.64 +        CX_TEST_ASSERT(len == 14);
    4.65 +        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15));
    4.66 +        CX_TEST_ASSERT(0 == memcmp(str, "Test 47 string", 15));
    4.67 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    4.68 +    }
    4.69 +    cx_testing_allocator_destroy(&talloc);
    4.70 +}
    4.71 +
    4.72 +CX_TEST(test_sprintf_s_alloc) {
    4.73 +    char buf[16];
    4.74 +    memcpy(buf, "0123456789abcdef", 16);
    4.75 +    CxTestingAllocator talloc;
    4.76 +    cx_testing_allocator_init(&talloc);
    4.77 +    CxAllocator *alloc = &talloc.base;
    4.78 +    CX_TEST_DO {
    4.79 +        char *str;
    4.80 +        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 4711, "larger string");
    4.81 +        CX_TEST_ASSERT(str != buf);
    4.82 +        CX_TEST_ASSERT(len == 24);
    4.83 +        CX_TEST_ASSERT(0 == memcmp(str, "Hello 4711 larger string", 25));
    4.84 +        cxFree(alloc, str);
    4.85 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    4.86 +    }
    4.87 +    cx_testing_allocator_destroy(&talloc);
    4.88 +}
    4.89 +
    4.90 +CX_TEST(test_sprintf_s_alloc_to_fit_terminator) {
    4.91 +    char buf[16];
    4.92 +    memcpy(buf, "0123456789abcdef", 16);
    4.93 +    CxTestingAllocator talloc;
    4.94 +    cx_testing_allocator_init(&talloc);
    4.95 +    CxAllocator *alloc = &talloc.base;
    4.96 +    CX_TEST_DO {
    4.97 +        char *str;
    4.98 +        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 112, "string");
    4.99 +        CX_TEST_ASSERT(str != buf);
   4.100 +        CX_TEST_ASSERT(len == 16);
   4.101 +        CX_TEST_ASSERT(0 == memcmp(str, "Hello 112 string", 17)); // include terminator
   4.102 +        cxFree(alloc, str);
   4.103 +        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
   4.104 +    }
   4.105 +    cx_testing_allocator_destroy(&talloc);
   4.106 +}
   4.107 +
   4.108  CxTestSuite *cx_test_suite_printf(void) {
   4.109      CxTestSuite *suite = cx_test_suite_new("printf");
   4.110  
   4.111 @@ -303,6 +404,12 @@
   4.112      cx_test_register(suite, test_fprintf);
   4.113      cx_test_register(suite, test_asprintf);
   4.114      cx_test_register(suite, test_asprintf_large_string);
   4.115 +    cx_test_register(suite, test_sprintf_no_realloc);
   4.116 +    cx_test_register(suite, test_sprintf_realloc);
   4.117 +    cx_test_register(suite, test_sprintf_realloc_to_fit_terminator);
   4.118 +    cx_test_register(suite, test_sprintf_s_no_alloc);
   4.119 +    cx_test_register(suite, test_sprintf_s_alloc);
   4.120 +    cx_test_register(suite, test_sprintf_s_alloc_to_fit_terminator);
   4.121  
   4.122      return suite;
   4.123  }

mercurial