Thu, 20 Nov 2025 20:06:20 +0100
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 } }