add missing test coverage in string.c and fix overflow checking bug in cx_strcat() default tip

Thu, 20 Nov 2025 20:06:20 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 20 Nov 2025 20:06:20 +0100
changeset 1500
d20037235c9c
parent 1499
d0a0a41405bb

add missing test coverage in string.c and fix overflow checking bug in cx_strcat()

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
tests/test_string.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Thu Nov 20 18:51:00 2025 +0100
+++ b/CHANGELOG	Thu Nov 20 20:06:20 2025 +0100
@@ -45,6 +45,7 @@
  * fixes that the elem_count member of an iterator was not updated after removing an element flagged by cxIteratorFlagRemoval()
  * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node
  * fixes unnecessary allocations in cx_strcat() family of functions
+ * fixes ineffective overflow check in cx_strcat() family of functions
  * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
  * fixes implementation of cxBufferTerminate()
  * fixes allocator arguments for some printf.h functions not being const
--- a/docs/Writerside/topics/about.md	Thu Nov 20 18:51:00 2025 +0100
+++ b/docs/Writerside/topics/about.md	Thu Nov 20 20:06:20 2025 +0100
@@ -72,6 +72,7 @@
 * fixes that the elem_count member of an iterator was not updated after removing an element flagged by cxIteratorFlagRemoval()
 * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node
 * fixes unnecessary allocations in cx_strcat() family of functions
+* fixes ineffective overflow check in cx_strcat() family of functions
 * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
 * fixes implementation of cxBufferTerminate()
 * fixes allocator arguments for some printf.h functions not being const
--- a/src/string.c	Thu Nov 20 18:51:00 2025 +0100
+++ b/src/string.c	Thu Nov 20 20:06:20 2025 +0100
@@ -91,7 +91,7 @@
         cxstring src
 ) {
     if (cxReallocate(alloc, &dest->ptr, src.length + 1)) {
-        return 1;
+        return 1; // LCOV_EXCL_LINE
     }
 
     memcpy(dest->ptr, src.ptr, src.length);
@@ -137,7 +137,7 @@
     size_t slen = str.length;
     for (size_t i = 0; i < count; i++) {
         cxstring s = va_arg(ap, cxstring);
-        if (slen > SIZE_MAX - str.length) overflow = true;
+        if (slen > SIZE_MAX - s.length) overflow = true;
         slen += s.length;
     }
     va_end(ap);
@@ -156,10 +156,10 @@
     } else {
         newstr = cxRealloc(alloc, str.ptr, slen + 1);
     }
-    if (newstr == NULL) {
+    if (newstr == NULL) { // LCOV_EXCL_START
         va_end(ap2);
         return (cxmutstr) {NULL, 0};
-    }
+    } // LCOV_EXCL_STOP
     str.ptr = newstr;
 
     // concatenate strings
@@ -521,10 +521,12 @@
             cxMalloc(allocator, string.length + 1),
             string.length
     };
+    // LCOV_EXCL_START
     if (result.ptr == NULL) {
         result.length = 0;
         return result;
     }
+    // LCOV_EXCL_STOP
     memcpy(result.ptr, string.ptr, string.length);
     result.ptr[string.length] = '\0';
     return result;
--- a/tests/test_string.c	Thu Nov 20 18:51:00 2025 +0100
+++ b/tests/test_string.c	Thu Nov 20 20:06:20 2025 +0100
@@ -43,6 +43,8 @@
     cxstring s2 = cx_strn("abcd", 2);
     cxmutstr s3 = cx_mutstr((char *) "1234");
     cxmutstr s4 = cx_mutstrn((char *) "abcd", 2);
+    cxstring snull = cx_str(NULL);
+    cxmutstr mnull = cx_mutstr(NULL);
     CX_TEST_DO {
         CX_TEST_ASSERT(s1.length == 4);
         CX_TEST_ASSERT(strncmp(s1.ptr, "1234", 4) == 0);
@@ -52,6 +54,12 @@
         CX_TEST_ASSERT(strncmp(s3.ptr, "1234", 4) == 0);
         CX_TEST_ASSERT(s4.length == 2);
         CX_TEST_ASSERT(strncmp(s4.ptr, "ab", 2) == 0);
+        CX_TEST_ASSERT(0 == snull.length);
+        CX_TEST_ASSERT(NULL == snull.ptr);
+        CX_TEST_ASSERT(0 == mnull.length);
+        CX_TEST_ASSERT(NULL == mnull.ptr);
+        CX_TEST_ASSERT(0 == cx_strcmp(snull, ""));
+        CX_TEST_ASSERT(0 == cx_strcmp(mnull, ""));
     }
 }
 
@@ -89,6 +97,9 @@
         CX_TEST_ASSERT(str.ptr == NULL);
         CX_TEST_ASSERT(str.length == 0);
         CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+        // check that this does not explode
+        cx_strfree(NULL);
+        cx_strfree_a(alloc, NULL);
     }
     cx_testing_allocator_destroy(&talloc);
 }
@@ -393,6 +404,17 @@
         CX_TEST_ASSERT(0 == cx_strcmp(cx_strcast(t6), cx_str("Hello, World!")));
         ASSERT_ZERO_TERMINATED(t6);
         cx_strfree(&t6);
+
+        // test overflow with fake strings
+        char *fakestr = NULL;
+        cxstring a = cx_strn(fakestr, SIZE_MAX / 3 - 10);
+        cxstring b = cx_strn(fakestr, SIZE_MAX / 3 - 5);
+        cxstring c = cx_strn(fakestr, SIZE_MAX / 3 + 20);
+        errno = 0;
+        cxmutstr z = cx_strcat(3, a, b, c);
+        CX_TEST_ASSERT(errno == EOVERFLOW);
+        CX_TEST_ASSERT(z.ptr == NULL);
+        CX_TEST_ASSERT(z.length == 0);
     }
     cx_testing_allocator_destroy(&talloc);
 }
@@ -753,6 +775,9 @@
     cxmutstr replan10 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 10);
     const char *an10expected = "xxxxxxxxxx";
 
+    cxmutstr norepl = cx_strreplace(cx_strn("hello world", 11), cx_str("worlds"), cx_str("test"));
+    const char *noreplexpect = "hello world";
+
     CX_TEST_DO {
         cxmutstr repl1_a = cx_strreplace_a(alloc, csstr, cx_str("AB"), cx_str("*"));
         const char *expeced1_a = "test * ab TEST xyz";
@@ -789,6 +814,8 @@
         CX_TEST_ASSERT(0 == strcmp(repl1_a.ptr, expeced1_a));
         ASSERT_ZERO_TERMINATED(repl2_a);
         CX_TEST_ASSERT(0 == strcmp(repl2_a.ptr, expected2_a));
+        ASSERT_ZERO_TERMINATED(norepl);
+        CX_TEST_ASSERT(0 == strcmp(norepl.ptr, noreplexpect));
 
         cx_strfree_a(alloc, &repl1_a);
         cx_strfree_a(alloc, &repl2_a);
@@ -807,6 +834,7 @@
     cx_strfree(&replan4);
     cx_strfree(&replan9);
     cx_strfree(&replan10);
+    cx_strfree(&norepl);
     cx_testing_allocator_destroy(&talloc);
 }
 
@@ -1107,6 +1135,49 @@
         CX_TEST_ASSERT(0 != cx_strtoll(cx_str("0x8df9CE03AbC90815"), &ll, 16));
         CX_TEST_ASSERT(errno == ERANGE);
 
+        // negative overflow (we only test for 64 bit long long)
+#if LLONG_MAX == 9223372036854775807ll
+        errno = 0;
+        CX_TEST_ASSERT(0 == cx_strtoll(cx_str("-9223372036854775808"), &ll, 10));
+        CX_TEST_ASSERT(ll == LLONG_MIN);
+        CX_TEST_ASSERT(errno == 0);
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("-9223372036854775809"), &ll, 10));
+        CX_TEST_ASSERT(errno == ERANGE);
+#endif
+
+        // edge case: empty and NULL string
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str(""), &ll, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str(NULL), &ll, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+
+        // edge case: unsupported base
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("7"), &ll, 12));
+        CX_TEST_ASSERT(errno == EINVAL);
+
+        // edge case: incorrect sign characters
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("-"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("+"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("-+15"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("--15"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("+-15"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoll(cx_str("++15"), &ll, 10));
+        CX_TEST_ASSERT(errno == EINVAL);
+
         // edge case: only the sign bit is set
         errno = 0;
         CX_TEST_ASSERT(0 != cx_strtoi16(cx_str("0x8000"), &i16, 16));
@@ -1163,6 +1234,7 @@
     size_t z;
     CX_TEST_DO {
         // do some brute force tests with all ranges
+        test_strtoint_rollout(0, 10);
         test_strtoint_rollout(47, 10);
         test_strtoint_rollout(210, 10);
         test_strtoint_rollout(5678, 10);
@@ -1170,6 +1242,7 @@
         test_strtoint_rollout(1350266537, 10);
         test_strtoint_rollout(3350266537, 10);
         test_strtoint_rollout(473350266537, 10);
+        test_strtoint_rollout(0, 8);
         test_strtoint_rollout(057, 8);
         test_strtoint_rollout(0322, 8);
         test_strtoint_rollout(013056, 8);
@@ -1178,6 +1251,8 @@
         test_strtoint_rollout(030754201251, 8);
         test_strtoint_rollout(06706567757251, 8);
         test_strtoint_rollout(01767716340165362204025, 8);
+        test_strtoint_rollout(0x0, 16);
+        test_strtoint_rollout(0, 16);
         test_strtoint_rollout(0x65, 16);
         test_strtoint_rollout(0xf5, 16);
         test_strtoint_rollout(0xABC5, 16);
@@ -1187,6 +1262,8 @@
         test_strtoint_rollout(0x6df9CE03AbC90815, 16);
         test_strtoint_rollout(0xfdf9CE03AbC90815, 16);
 #if __STDC_VERSION__ >= 202300L
+        test_strtoint_rollout(0b0, 2);
+        test_strtoint_rollout(0, 2);
         test_strtoint_rollout(0b01000010100100101110101001110101, 2);
         test_strtoint_rollout(0b00011010101100001111111001010100, 2);
         test_strtoint_rollout(0b10110001001001010001010111010011, 2);
@@ -1218,6 +1295,36 @@
         CX_TEST_ASSERT(0 == cx_strtou8_lc(cx_str("1010 1011"), &u8, 2, " "));
         CX_TEST_ASSERT(errno == 0);
         CX_TEST_ASSERT(u8 == 0xAB);
+
+        // edge case: empty and NULL string
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str(""), &ull, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str(NULL), &ull, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+
+        // edge case: unsupported base
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("7"), &ull, 12));
+        CX_TEST_ASSERT(errno == EINVAL);
+
+        // edge case: prefix only
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("0b"), &ull, 2));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("b"), &ull, 2));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("0x"), &ull, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("x"), &ull, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtoull(cx_str("#"), &ull, 16));
+        CX_TEST_ASSERT(errno == EINVAL);
     }
 }
 
@@ -1275,6 +1382,12 @@
         CX_TEST_ASSERT(0 == cx_strtod(cx_str("11.3"), &d));
         CX_TEST_ASSERT(0 == cx_vcmp_double(11.3, d));
 
+        CX_TEST_ASSERT(0 == cx_strtod(cx_str("13."), &d));
+        CX_TEST_ASSERT(0 == cx_vcmp_double(13.0, d));
+
+        CX_TEST_ASSERT(0 == cx_strtod(cx_str("47"), &d));
+        CX_TEST_ASSERT(0 == cx_vcmp_double(47.0, d));
+
         CX_TEST_ASSERT(0 == cx_strtod(cx_str("-13.37"), &d));
         CX_TEST_ASSERT(0 == cx_vcmp_double(-13.37, d));
 
@@ -1290,6 +1403,53 @@
         CX_TEST_ASSERT(0 == cx_strtod_lc(cx_str("138,339.4"), &d, ',', "."));
         CX_TEST_ASSERT(0 == cx_vcmp_double(138.3394, d));
 
+        CX_TEST_ASSERT(0 == cx_strtod_lc(cx_str("13.37e04.7"), &d, ',', "."));
+        CX_TEST_ASSERT(0 == cx_vcmp_double(1337e47, d));
+
+        d = 47.11;
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str(""), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str(NULL), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("+"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("-"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("+-5"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("-+5"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("++5"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("--5"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod_lc(cx_str("."), &d, '.', "'"));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+
+        errno = 0;
+        CX_TEST_ASSERT(0 != cx_strtod(cx_str("19e0x5"), &d));
+        CX_TEST_ASSERT(errno == EINVAL);
+        CX_TEST_ASSERT(d == 47.11);
+
         // TODO: test and improve support for big numbers, precision, and out-of-range detection
     }
 }

mercurial