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
--- a/CHANGELOG	Tue Jan 16 23:12:43 2024 +0100
+++ b/CHANGELOG	Tue Jan 16 23:13:01 2024 +0100
@@ -4,6 +4,7 @@
  * adds cxListFindRemove()
  * adds cxBufferReset()
  * adds cx_cmp_ptr()
+ * adds cx_sprintf() and several more variants
  * adds runtime constants to read out the actual SBO sizes
  * adds improved version of UCX 2 Test framework (now a self-contained header)
  * the cx_compare_func symbol is now also declared by compare.h
--- a/src/cx/printf.h	Tue Jan 16 23:12:43 2024 +0100
+++ b/src/cx/printf.h	Tue Jan 16 23:13:01 2024 +0100
@@ -164,6 +164,170 @@
 #define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
     (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
 
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4), __format__(printf, 4, 5)))
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... );
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len the current length of the buffer
+ * @param fmt the format string
+ * @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);
+
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ * 
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @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, ... );
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ * That means, when the buffer needed to be reallocated, the new size of the buffer will be
+ * the length returned by this function plus one.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @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);
+
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
--- a/src/printf.c	Tue Jan 16 23:12:43 2024 +0100
+++ b/src/printf.c	Tue Jan 16 23:13:01 2024 +0100
@@ -127,3 +127,73 @@
     return s;
 }
 
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(*str, len, fmt, ap);
+    if (ret < 0 || ((unsigned)ret) < len) {
+        va_end(ap2);
+        return ret;
+    } else {
+        unsigned newlen = ret + 1;
+        char *ptr = cxRealloc(alloc, *str, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            va_end(ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+                return ret;
+            } else {
+                *str = ptr;
+                return newret;
+            }
+        } else {
+            va_end(ap2);
+            return ret;
+        }
+    }
+}
+
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... ) {
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap) {
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, len, fmt, ap);
+    *str = buf;
+    if (ret < 0 || ((unsigned)ret) < len) {
+        va_end(ap2);
+        return ret;
+    } else {
+        unsigned newlen = ret + 1;
+        char *ptr = cxMalloc(alloc, newlen);
+        if (ptr) {
+            int newret = vsnprintf(ptr, newlen, fmt, ap2);
+            va_end(ap2);
+            if (newret < 0) {
+                cxFree(alloc, ptr);
+                return ret;
+            } else {
+                *str = ptr;
+                return newret;
+            }
+        } else {
+            va_end(ap2);
+            return ret;
+        }
+    }
+}
--- a/tests/test_printf.c	Tue Jan 16 23:12:43 2024 +0100
+++ b/tests/test_printf.c	Tue Jan 16 23:13:01 2024 +0100
@@ -294,6 +294,107 @@
     free(expected);
 }
 
+CX_TEST(test_sprintf_no_realloc) {
+    char *buf = malloc(16);
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    CX_TEST_DO {
+        char *oldbuf = buf;
+        size_t len = cx_sprintf_a(alloc, &buf, 16, "Test %d %s", 47, "string");
+        CX_TEST_ASSERT(oldbuf == buf);
+        CX_TEST_ASSERT(len == 14);
+        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15));
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+    free(buf);
+}
+
+CX_TEST(test_sprintf_realloc) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    char *buf = cxMalloc(alloc, 8);
+    CX_TEST_DO {
+        size_t len = cx_sprintf_a(alloc, &buf, 8, "Test %d %s", 47, "foobar");
+        CX_TEST_ASSERT(len == 14);
+        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 foobar", 15));
+        cxFree(alloc, buf);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_sprintf_realloc_to_fit_terminator) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    // make it so that only the zero-terminator does not fit
+    char *buf = cxMalloc(alloc, 14);
+    CX_TEST_DO {
+        size_t len = cx_sprintf_a(alloc, &buf, 14, "Test %d %s", 13, "string");
+        CX_TEST_ASSERT(len == 14);
+        CX_TEST_ASSERT(0 == memcmp(buf, "Test 13 string", 15));
+        cxFree(alloc, buf);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_sprintf_s_no_alloc) {
+    char buf[16];
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    CX_TEST_DO {
+        char *str;
+        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Test %d %s", 47, "string");
+        CX_TEST_ASSERT(str == buf);
+        CX_TEST_ASSERT(len == 14);
+        CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15));
+        CX_TEST_ASSERT(0 == memcmp(str, "Test 47 string", 15));
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_sprintf_s_alloc) {
+    char buf[16];
+    memcpy(buf, "0123456789abcdef", 16);
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    CX_TEST_DO {
+        char *str;
+        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 4711, "larger string");
+        CX_TEST_ASSERT(str != buf);
+        CX_TEST_ASSERT(len == 24);
+        CX_TEST_ASSERT(0 == memcmp(str, "Hello 4711 larger string", 25));
+        cxFree(alloc, str);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_sprintf_s_alloc_to_fit_terminator) {
+    char buf[16];
+    memcpy(buf, "0123456789abcdef", 16);
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *alloc = &talloc.base;
+    CX_TEST_DO {
+        char *str;
+        size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 112, "string");
+        CX_TEST_ASSERT(str != buf);
+        CX_TEST_ASSERT(len == 16);
+        CX_TEST_ASSERT(0 == memcmp(str, "Hello 112 string", 17)); // include terminator
+        cxFree(alloc, str);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
 CxTestSuite *cx_test_suite_printf(void) {
     CxTestSuite *suite = cx_test_suite_new("printf");
 
@@ -303,6 +404,12 @@
     cx_test_register(suite, test_fprintf);
     cx_test_register(suite, test_asprintf);
     cx_test_register(suite, test_asprintf_large_string);
+    cx_test_register(suite, test_sprintf_no_realloc);
+    cx_test_register(suite, test_sprintf_realloc);
+    cx_test_register(suite, test_sprintf_realloc_to_fit_terminator);
+    cx_test_register(suite, test_sprintf_s_no_alloc);
+    cx_test_register(suite, test_sprintf_s_alloc);
+    cx_test_register(suite, test_sprintf_s_alloc_to_fit_terminator);
 
     return suite;
 }

mercurial