Thu, 25 Dec 2025 11:48:25 +0100
merge changes for Windows
| src/properties.c | file | annotate | diff | comparison | revisions |
--- a/CHANGELOG Wed Dec 24 15:05:36 2025 +0100 +++ b/CHANGELOG Thu Dec 25 11:48:25 2025 +0100 @@ -11,6 +11,7 @@ * adds line continuation support to CxProperties / CxPropertiesConfig * adds cx_hash_key_as_string() * adds support for CxHashKey pointers in CX_HASH_KEY() and all map functions + * adds cx_strat() * adds cx_bstr() and cx_bstr_m() * adds cxBufferMaximumCapacity() * adds cxBufferAppendString() @@ -21,6 +22,7 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible + * changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc()
--- a/docs/Writerside/topics/about.md Wed Dec 24 15:05:36 2025 +0100 +++ b/docs/Writerside/topics/about.md Thu Dec 25 11:48:25 2025 +0100 @@ -38,6 +38,7 @@ * adds line continuation support to CxProperties / CxPropertiesConfig * adds cx_hash_key_as_string() * adds support for CxHashKey pointers in CX_HASH_KEY() and all map functions +* adds cx_strat() * adds cx_bstr() and cx_bstr_m() * adds cxBufferMaximumCapacity() * adds cxBufferAppendString() @@ -48,6 +49,7 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible +* changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc()
--- a/docs/Writerside/topics/string.h.md Wed Dec 24 15:05:36 2025 +0100 +++ b/docs/Writerside/topics/string.h.md Thu Dec 25 11:48:25 2025 +0100 @@ -119,27 +119,24 @@ ```C #include <cx/string.h> -cxmutstr cx_strcat(size_t count, ... ); - -cxmutstr cx_strcat_a(const CxAllocator *alloc, size_t count, ... ); +int cx_strcat(cxmutstr *str, size_t count, ... ); -cxmutstr cx_strcat_m(cxmutstr str, size_t count, ... ); - -cxmutstr cx_strcat_ma(const CxAllocator *alloc, - cxmutstr str, size_t count, ... ); +int cx_strcat_a(const CxAllocator *allocator, + cxmutstr *str, size_t count, ... ); size_t cx_strlen(size_t count, ...); ``` -The `cx_strcat_a()` function takes `count` UCX strings (`cxstring` or `cxmutstr`), -allocates memory for a concatenation of those strings _with a single allocation_, -and copies the contents of the strings to the new memory. +The `cx_strcat_a()` function takes `count` UCX strings (`cxstring` or `cxmutstr` - not pointers!), +allocates memory in `str` for a concatenation of those strings _with a single allocation_, +and appends the contents of the strings to `str`. `cx_strcat()` is equivalent, except that it uses the [default allocator](allocator.h.md#default-allocator). -The `cx_strcat_ma()` and `cx_strcat_m()` append the `count` strings to the specified string `str` and, -instead of allocating new memory, reallocate the existing memory in `str`. -If the pointer in `str` is `NULL`, there is no difference to `cx_strcat_a()`. -Note, that `count` always denotes the number of variadic arguments in _both_ variants. +Example usage: +```C +cxmutstr str = {0}; +cx_strcat(&str, 2, cx_str("Hello, "), cx_str("World!")); +``` The function `cx_strlen()` sums the length of the specified strings. @@ -153,6 +150,8 @@ ```C #include <cx/string.h> +char cx_strat(cxstring str, off_t index); + cxstring cx_strchr(cxstring string, int chr); cxstring cx_strrchr(cxstring string, int chr); @@ -178,6 +177,10 @@ cxmutstr cx_strtrim_m(cxmutstr string); ``` +The function `cx_strat()` returns the character at the specified `index`. +When the `index` is negative, the characters are counted from the end of the string. +When the `index` is out of bounds, the zero-character is returned. + The functions `cx_strchr()`, `cx_strrchr()`, and `cx_strstr()`, behave like their stdlib counterparts. The function `cx_strsubs()` returns the substring starting at the specified `start` index,
--- a/src/cx/json.h Wed Dec 24 15:05:36 2025 +0100 +++ b/src/cx/json.h Thu Dec 25 11:48:25 2025 +0100 @@ -289,13 +289,6 @@ CxBuffer buffer; /** - * Used internally. - * - * Remembers the prefix of the last uncompleted token. - */ - CxJsonToken uncompleted; - - /** * A pointer to an intermediate state of the currently parsed value. * * Never access this value manually. @@ -310,6 +303,16 @@ cxmutstr uncompleted_member_name; /** + * Internal buffer for uncompleted tokens. + */ + cxmutstr uncompleted_content; + + /** + * The expected type of the currently parsed, uncompleted token. + */ + CxJsonTokenType uncompleted_tokentype; + + /** * State stack. */ CX_ARRAY(int, states);
--- a/src/cx/string.h Wed Dec 24 15:05:36 2025 +0100 +++ b/src/cx/string.h Thu Dec 25 11:48:25 2025 +0100 @@ -416,9 +416,6 @@ * If @p str already contains a string, the memory will be reallocated and * the other strings are appended. Otherwise, new memory is allocated. * - * If memory allocation fails, the pointer in the returned string will - * be @c NULL. Depending on the allocator, @c errno might be set. - * * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. @@ -427,32 +424,12 @@ * @param str the string the other strings shall be concatenated to * @param count the number of the other following strings to concatenate * @param ... all other UCX strings - * @return the concatenated string + * @retval zero success + * @retval non-zero allocation failure */ -cx_attr_nodiscard cx_attr_nonnull -CX_EXPORT cxmutstr cx_strcat_ma(const CxAllocator *alloc, - cxmutstr str, size_t count, ...); - -/** - * Concatenates strings and returns a new string. - * - * The resulting string will be allocated by the specified allocator. - * So developers @em must pass the return value to cx_strfree_a() eventually. - * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL. Depending on the allocator, @c errno might be set. - * - * @note It is guaranteed that there is only one allocation for the - * resulting string. - * It is also guaranteed that the returned string is zero-terminated. - * - * @param alloc (@c CxAllocator*) the allocator to use - * @param count (@c size_t) the number of the other following strings to concatenate - * @param ... all other UCX strings - * @return (@c cxmutstr) the concatenated string - */ -#define cx_strcat_a(alloc, count, ...) \ - cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) +cx_attr_nonnull +CX_EXPORT int cx_strcat_a(const CxAllocator *alloc, + cxmutstr *str, size_t count, ...); /** * Concatenates strings and returns a new string. @@ -460,43 +437,18 @@ * The resulting string will be allocated by the cxDefaultAllocator. * So developers @em must pass the return value to cx_strfree() eventually. * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL and @c errno might be set. - * * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. * + * @param str (@c cxmutstr*) the string the other strings shall be concatenated to * @param count (@c size_t) the number of the other following strings to concatenate * @param ... all other UCX strings - * @return (@c cxmutstr) the concatenated string + * @retval zero success + * @retval non-zero allocation failure */ -#define cx_strcat(count, ...) \ - cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) - -/** - * Concatenates strings. - * - * The resulting string will be allocated by the cxDefaultAllocator. - * So developers @em must pass the return value to cx_strfree() eventually. - * - * If @p str already contains a string, the memory will be reallocated and - * the other strings are appended. Otherwise, new memory is allocated. - * -* If memory allocation fails, the pointer in the returned string will - * be @c NULL and @c errno might be set. - * - * @note It is guaranteed that there is only one allocation for the - * resulting string. - * It is also guaranteed that the returned string is zero-terminated. - * - * @param str (@c cxmutstr) the string the other strings shall be concatenated to - * @param count (@c size_t) the number of the other following strings to concatenate - * @param ... all other strings - * @return (@c cxmutstr) the concatenated string - */ -#define cx_strcat_m(str, count, ...) \ - cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) +#define cx_strcat(str, count, ...) \ + cx_strcat_a(cxDefaultAllocator, str, count, __VA_ARGS__) /** * Returns a substring starting at the specified location. @@ -579,6 +531,44 @@ CX_EXPORT cxmutstr cx_strsubsl_m(cxmutstr string, size_t start, size_t length); /** + * Returns the character at the specified index offset. + * + * Internal function - do not use. + * + * @param str the string + * @param index the index offset + * @return the character at the index + * @see cx_strat() + */ +CX_INLINE char cx_strat_(cxstring str, off_t index) { + size_t i; + if (index >= 0) { + i = index; + } else { + i = (size_t) (str.length + index); + } + if (i >= str.length) { + return '\0'; + } + return str.ptr[i]; +} + +/** + * Returns the character at the specified index offset. + * + * When the @p index is negative, the character is counted from the end of the + * string where -1 denotes the last character in the string. + * + * When the @p index is out of bounds, the function returns zero. + * + * @param str the string + * @param index the index offset + * @return the character at the index + * @see cx_strat() + */ +#define cx_strat(str, index) cx_strat_(cx_strcast(str), index) + +/** * Returns a substring starting at the location of the first occurrence of the * specified character. *
--- a/src/json.c Wed Dec 24 15:05:36 2025 +0100 +++ b/src/json.c Thu Dec 25 11:48:25 2025 +0100 @@ -96,23 +96,27 @@ } static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { - cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); - bool allocated = false; - if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { + cxmutstr buf_str = cx_mutstrn(json->buffer.space + start, end - start); + cxmutstr str; + bool allocated; + if (json->uncompleted_tokentype != CX_JSON_NO_TOKEN) { allocated = true; - str = cx_strcat_m(json->uncompleted.content, 1, str); - if (str.ptr == NULL) { // LCOV_EXCL_START - return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; - } // LCOV_EXCL_STOP + str = json->uncompleted_content; + if (cx_strcat(&str, 1, buf_str)) { + return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; // LCOV_EXCL_LINE + } + json->uncompleted_content = (cxmutstr){NULL, 0}; + json->uncompleted_tokentype = CX_JSON_NO_TOKEN; + } else { + allocated = false; + str = buf_str; } - json->uncompleted = (CxJsonToken){0}; CxJsonTokenType ttype; if (isstring) { ttype = CX_JSON_TOKEN_STRING; } else { - cxstring s = cx_strcast(str); - if (!cx_strcmp(s, "true") || !cx_strcmp(s, "false") - || !cx_strcmp(s, "null")) { + if (!cx_strcmp(str, "true") || !cx_strcmp(str, "false") + || !cx_strcmp(str, "null")) { ttype = CX_JSON_TOKEN_LITERAL; } else { ttype = token_numbertype(str.ptr, str.length); @@ -162,16 +166,16 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { // check if there is data in the buffer if (cxBufferEof(&json->buffer)) { - return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? + return json->uncompleted_tokentype == CX_JSON_NO_TOKEN ? CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; } // current token type and start index - CxJsonTokenType ttype = json->uncompleted.tokentype; + CxJsonTokenType ttype = json->uncompleted_tokentype; size_t token_part_start = json->buffer.pos; bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING - && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; + && cx_strat(json->uncompleted_content, -1) == '\\'; for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { char c = json->buffer.space[i]; @@ -232,31 +236,23 @@ return CX_JSON_NO_DATA; } else { // uncompleted token - size_t uncompleted_len = json->buffer.size - token_part_start; - if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { - // current token is uncompleted - // save current token content - CxJsonToken uncompleted = { - ttype, true, - cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) - }; - if (uncompleted.content.ptr == NULL) { + cxstring uncompleted = cx_strn(json->buffer.space + token_part_start, json->buffer.size - token_part_start); + if (json->uncompleted_tokentype == CX_JSON_NO_TOKEN) { + assert(json->uncompleted_content.ptr == NULL); + json->uncompleted_content = cx_strdup(uncompleted); + if (json->uncompleted_content.ptr == NULL) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } - json->uncompleted = uncompleted; + json->uncompleted_tokentype = ttype; } else { // previously we also had an uncompleted token // combine the uncompleted token with the current token - assert(json->uncompleted.allocated); - cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, - cx_strn(json->buffer.space + token_part_start, uncompleted_len)); - if (str.ptr == NULL) { + if (cx_strcat(&json->uncompleted_content, 1, uncompleted)) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } - json->uncompleted.content = str; } // advance the buffer position - we saved the stuff in the uncompleted token - json->buffer.pos += uncompleted_len; + json->buffer.pos += uncompleted.length; return CX_JSON_INCOMPLETE_DATA; } } @@ -564,7 +560,8 @@ } cxJsonValueFree(json->parsed); json->parsed = NULL; - token_destroy(&json->uncompleted); + json->uncompleted_tokentype = CX_JSON_NO_TOKEN; + cx_strfree(&json->uncompleted_content); cx_strfree_a(json->allocator, &json->uncompleted_member_name); }
--- a/src/properties.c Wed Dec 24 15:05:36 2025 +0100 +++ b/src/properties.c Thu Dec 25 11:48:25 2025 +0100 @@ -112,7 +112,7 @@ cxstring nl = cx_strchr(input, '\n'); while (nl.length > 0) { // check for line continuation - char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1]; + char previous = nl.ptr > input.ptr ? nl.ptr[-1] : cx_strat(cx_bstr(&prop->buffer), -1); if (previous == continuation) { // this nl is a line continuation, check the next newline nl = cx_strchr(cx_strsubs(nl, 1), '\n');
--- a/src/string.c Wed Dec 24 15:05:36 2025 +0100 +++ b/src/string.c Thu Dec 25 11:48:25 2025 +0100 @@ -99,13 +99,13 @@ return size; } -cxmutstr cx_strcat_ma( +int cx_strcat_a( const CxAllocator *alloc, - cxmutstr str, + cxmutstr *str, size_t count, ... ) { - if (count == 0) return str; + if (count == 0) return 0; va_list ap; va_start(ap, count); va_list ap2; @@ -113,7 +113,7 @@ // compute overall length bool overflow = false; - size_t slen = str.length; + size_t slen = str->length; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap, cxstring); if (slen > SIZE_MAX - s.length) overflow = true; @@ -125,36 +125,31 @@ if (overflow) { va_end(ap2); errno = EOVERFLOW; - return (cxmutstr) { NULL, 0 }; + return -1; } // reallocate or create new string - char *newstr; - if (str.ptr == NULL) { - newstr = cxMalloc(alloc, slen + 1); - } else { - newstr = cxRealloc(alloc, str.ptr, slen + 1); + if (cxReallocate(alloc, &str->ptr, slen + 1)) { + // LCOV_EXCL_START + va_end(ap2); + return -1; + // LCOV_EXCL_STOP } - if (newstr == NULL) { // LCOV_EXCL_START - va_end(ap2); - return (cxmutstr) {NULL, 0}; - } // LCOV_EXCL_STOP - str.ptr = newstr; // concatenate strings - size_t pos = str.length; - str.length = slen; + size_t pos = str->length; + str->length = slen; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap2, cxstring); - memcpy(str.ptr + pos, s.ptr, s.length); + memcpy(str->ptr + pos, s.ptr, s.length); pos += s.length; } va_end(ap2); // terminate string - str.ptr[str.length] = '\0'; + str->ptr[str->length] = '\0'; - return str; + return 0; } cxstring cx_strsubs( @@ -510,11 +505,11 @@ cxstring cx_strtrim(cxstring string) { cxstring result = string; - while (result.length > 0 && isspace((unsigned char)(result.ptr[0]))) { + while (isspace((unsigned char)cx_strat(result, 0))) { result.ptr++; result.length--; } - while (result.length > 0 && isspace((unsigned char)result.ptr[result.length - 1])) { + while (isspace((unsigned char)cx_strat(result, -1))) { result.length--; } return result;
--- a/tests/test_properties.c Wed Dec 24 15:05:36 2025 +0100 +++ b/tests/test_properties.c Thu Dec 25 11:48:25 2025 +0100 @@ -384,6 +384,32 @@ free(long_value); } +CX_TEST(test_properties_next_starts_with_newlines) { + const char *str1 = "key1 = value1"; + const char *str2 = "\nkey2 = value2\n"; + + CxProperties prop; + cxPropertiesInitDefault(&prop); + + cxstring key; + cxstring value; + + CX_TEST_DO { + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str1)); + CX_TEST_ASSERT(cxPropertiesNext(&prop, &key, &value) == CX_PROPERTIES_INCOMPLETE_DATA); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str2)); + CX_TEST_ASSERT(cxPropertiesNext(&prop, &key, &value) == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(!cx_strcmp(key, "key1")); + CX_TEST_ASSERT(!cx_strcmp(value, "value1")); + CX_TEST_ASSERT(cxPropertiesNext(&prop, &key, &value) == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(!cx_strcmp(key, "key2")); + CX_TEST_ASSERT(!cx_strcmp(value, "value2")); + CX_TEST_ASSERT(cxPropertiesNext(&prop, &key, &value) == CX_PROPERTIES_NO_DATA); + } + + cxPropertiesDestroy(&prop); +} + CX_TEST(test_properties_next_line_continuation) { const char *str = "key1 = multiline \\\nvalue\n" @@ -836,6 +862,7 @@ cx_test_register(suite, test_properties_next_multi); cx_test_register(suite, test_properties_next_part); cx_test_register(suite, test_properties_next_long_lines); + cx_test_register(suite, test_properties_next_starts_with_newlines); cx_test_register(suite, test_properties_next_line_continuation); cx_test_register(suite, test_properties_next_line_continuation_part); cx_test_register(suite, test_properties_load);
--- a/tests/test_string.c Wed Dec 24 15:05:36 2025 +0100 +++ b/tests/test_string.c Thu Dec 25 11:48:25 2025 +0100 @@ -220,6 +220,31 @@ } } +CX_TEST(test_strat) { + cxstring str = cx_str("Hello, World!"); + + CX_TEST_DO { + // the entire string + for (size_t i = 0; i < str.length; i++) { + CX_TEST_ASSERT(cx_strat(str, i) == str.ptr[i]); + } + // the entire string backwards + for (off_t i = 1; i <= (off_t) str.length; i++) { + CX_TEST_ASSERT(cx_strat(str, -i) == str.ptr[str.length-i]); + } + // out of bounds (positive) + CX_TEST_ASSERT(cx_strat(str, 12) == '!'); + CX_TEST_ASSERT(cx_strat(str, 13) == '\0'); + CX_TEST_ASSERT(cx_strat(str, 14) == '\0'); + CX_TEST_ASSERT(cx_strat(str, 651273) == '\0'); + // out of bounds (negative) + CX_TEST_ASSERT(cx_strat(str, -13) == 'H'); + CX_TEST_ASSERT(cx_strat(str, -14) == '\0'); + CX_TEST_ASSERT(cx_strat(str, -15) == '\0'); + CX_TEST_ASSERT(cx_strat(str, -651273) == '\0'); + } +} + CX_TEST(test_strchr) { cxstring str = cx_str("I will find you - and I will kill you"); @@ -376,22 +401,26 @@ CxAllocator *alloc = &talloc.base; CX_TEST_DO { - cxmutstr t1 = cx_strcat_a(alloc, 2, s1, s2); + cxmutstr t1 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t1, 2, s1, s2)); CX_TEST_ASSERT(0 == cx_strcmp(t1, "1234")); ASSERT_ZERO_TERMINATED(t1); cx_strfree_a(alloc, &t1); - cxmutstr t2 = cx_strcat_a(alloc, 3, s1, s2, s3); + cxmutstr t2 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t2, 3, s1, s2, s3)); CX_TEST_ASSERT(0 == cx_strcmp(t2, "123456")); ASSERT_ZERO_TERMINATED(t2); cx_strfree_a(alloc, &t2); - cxmutstr t3 = cx_strcat_a(alloc, 6, s1, sn, s2, sn, s3, sn); + cxmutstr t3 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t3, 6, s1, sn, s2, sn, s3, sn)); CX_TEST_ASSERT(0 == cx_strcmp(t3, "123456")); ASSERT_ZERO_TERMINATED(t3); cx_strfree_a(alloc, &t3); - cxmutstr t4 = cx_strcat_a(alloc, 2, sn, sn); + cxmutstr t4 = {0}; + CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t4, 2, sn, sn)); CX_TEST_ASSERT(0 == cx_strcmp(t4, "")); ASSERT_ZERO_TERMINATED(t4); cx_strfree_a(alloc, &t4); @@ -399,14 +428,15 @@ CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); // use the macro - cxmutstr t5 = cx_strcat(3, s3, s1, s2); + cxmutstr t5 = {0}; + CX_TEST_ASSERT(0 == cx_strcat(&t5, 3, s3, s1, s2)); CX_TEST_ASSERT(0 == cx_strcmp(t5, "561234")); ASSERT_ZERO_TERMINATED(t5); cx_strfree(&t5); // use an initial string cxmutstr t6 = cx_strdup(cx_str("Hello")); - t6 = cx_strcat_m(t6, 2, cx_str(", "), cx_str("World!")); + CX_TEST_ASSERT(0 == cx_strcat(&t6, 2, cx_str(", "), cx_str("World!"))); CX_TEST_ASSERT(0 == cx_strcmp(t6, "Hello, World!")); ASSERT_ZERO_TERMINATED(t6); cx_strfree(&t6); @@ -417,7 +447,8 @@ 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); + cxmutstr z = {0}; + CX_TEST_ASSERT(0 != cx_strcat(&z, 3, a, b, c)); CX_TEST_ASSERT(errno == EOVERFLOW); CX_TEST_ASSERT(z.ptr == NULL); CX_TEST_ASSERT(z.length == 0); @@ -437,7 +468,8 @@ cxstring s9 = cx_str("xy"); CX_TEST_DO { - cxmutstr r = cx_strcat(9, s1, s2, s3, s4, s5, s6, s7, s8, s9); + cxmutstr r = {0}; + CX_TEST_ASSERT(0 == cx_strcat(&r, 9, s1, s2, s3, s4, s5, s6, s7, s8, s9)); CX_TEST_ASSERT(0 == cx_strcmp(r, "123456789abcdef0xy")); ASSERT_ZERO_TERMINATED(r); cx_strfree(&r); @@ -1504,6 +1536,7 @@ cx_test_register(suite, test_strcpy); cx_test_register(suite, test_strlen); cx_test_register(suite, test_strsubs); + cx_test_register(suite, test_strat); cx_test_register(suite, test_strchr); cx_test_register(suite, test_strrchr); cx_test_register(suite, test_strstr);