While making use of the 'string-buffer' module in GNU gettext, I noticed that more API is needed, to cover the frequent use-cases.
For code that is not performance-critical, the benefits of this module are: - You need to declare one variable instead of 3 variables. - It moves the xrealloc logic out-of-sight. - In many cases it gets away with 1 memory allocation instead of 1x malloc and 1x or 2x realloc. In some cases it avoids heap memory allocation entirely. 2024-09-25 Bruno Haible <br...@clisp.org> string-buffer: Add more API. * lib/string-buffer.h: Include string-desc.h. (sb_append1, sb_append_desc): New declarations. (sb_append_c): Renamed from sb_append. (sb_contents, sb_contents_c, sb_dupfree): New declarations. (sb_dupfree_c): Renamed from sb_dupfree. * lib/string-buffer.c (sb_append1, sb_append_desc): New functions. (sb_append_c): Renamed from sb_append. (sb_contents, sb_contents_c, sb_dupfree): New functions. (sb_dupfree_c): Renamed from sb_dupfree. Optimize. * modules/string-buffer (Depends-on): Add string-desc. * tests/test-string-buffer.c (main): Use sb_append_c instead of sb_append. Use sb_dupfree_c instead of sb_dupfree. Test also sb_append1, sb_append_desc, sb_contents. * NEWS: Mention the changes. diff --git a/NEWS b/NEWS index 40a8da647a..c3710825dd 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,9 @@ User visible incompatible changes Date Modules Changes +2024-09-25 string-buffer The function sb_append is renamed to sb_append_c. + The function sb_dupfree is renamed to sb_dupfree_c. + 2024-08-14 verror The include file is changed from "verror.h" to <error.h>. diff --git a/lib/string-buffer.c b/lib/string-buffer.c index ed34e6b78b..b3759aa395 100644 --- a/lib/string-buffer.c +++ b/lib/string-buffer.c @@ -87,7 +87,33 @@ sb_ensure_more_bytes (struct string_buffer *buffer, size_t increment) } int -sb_append (struct string_buffer *buffer, const char *str) +sb_append1 (struct string_buffer *buffer, char c) +{ + if (sb_ensure_more_bytes (buffer, 1) < 0) + { + buffer->error = true; + return -1; + } + buffer->data[buffer->length++] = c; + return 0; +} + +int +sb_append_desc (struct string_buffer *buffer, string_desc_t s) +{ + size_t len = string_desc_length (s); + if (sb_ensure_more_bytes (buffer, len) < 0) + { + buffer->error = true; + return -1; + } + memcpy (buffer->data + buffer->length, string_desc_data (s), len); + buffer->length += len; + return 0; +} + +int +sb_append_c (struct string_buffer *buffer, const char *str) { size_t len = strlen (str); if (sb_ensure_more_bytes (buffer, len) < 0) @@ -107,10 +133,57 @@ sb_free (struct string_buffer *buffer) free (buffer->data); } -/* Returns the contents of BUFFER, and frees all other memory held - by BUFFER. Returns NULL upon failure or if there was an error earlier. */ -char * +string_desc_t +sb_contents (struct string_buffer *buffer) +{ + return string_desc_new_addr (buffer->length, buffer->data); +} + +const char * +sb_contents_c (struct string_buffer *buffer) +{ + if (sb_ensure_more_bytes (buffer, 1) < 0) + return NULL; + buffer->data[buffer->length] = '\0'; + + return buffer->data; +} + +string_desc_t sb_dupfree (struct string_buffer *buffer) +{ + if (buffer->error) + goto fail; + + size_t length = buffer->length; + if (buffer->data == buffer->space) + { + char *copy = (char *) malloc (length > 0 ? length : 1); + if (copy == NULL) + goto fail; + memcpy (copy, buffer->data, length); + return string_desc_new_addr (length, copy); + } + else + { + /* Shrink the string before returning it. */ + char *contents = buffer->data; + if (length < buffer->allocated) + { + contents = realloc (contents, length > 0 ? length : 1); + if (contents == NULL) + goto fail; + } + return string_desc_new_addr (length, contents); + } + + fail: + sb_free (buffer); + return string_desc_new_addr (0, NULL); +} + +char * +sb_dupfree_c (struct string_buffer *buffer) { if (buffer->error) goto fail; @@ -120,21 +193,22 @@ sb_dupfree (struct string_buffer *buffer) buffer->data[buffer->length] = '\0'; buffer->length++; + size_t length = buffer->length; if (buffer->data == buffer->space) { - char *copy = (char *) malloc (buffer->length); + char *copy = (char *) malloc (length); if (copy == NULL) goto fail; - memcpy (copy, buffer->data, buffer->length); + memcpy (copy, buffer->data, length); return copy; } else { /* Shrink the string before returning it. */ char *contents = buffer->data; - if (buffer->length < buffer->allocated) + if (length < buffer->allocated) { - contents = realloc (contents, buffer->length); + contents = realloc (contents, length); if (contents == NULL) goto fail; } diff --git a/lib/string-buffer.h b/lib/string-buffer.h index 5476197d52..02ba90abfb 100644 --- a/lib/string-buffer.h +++ b/lib/string-buffer.h @@ -29,6 +29,7 @@ #include <stdlib.h> #include "attribute.h" +#include "string-desc.h" typedef char * _GL_ATTRIBUTE_CAPABILITY_TYPE ("memory resource") sb_heap_allocated_pointer_t; @@ -51,9 +52,17 @@ extern "C" { extern void sb_init (struct string_buffer *buffer) _GL_ATTRIBUTE_ACQUIRE_CAPABILITY (buffer->data); -/* Appends the contents of STR to BUFFER. +/* Appends the character C to BUFFER. Returns 0, or -1 in case of out-of-memory error. */ -extern int sb_append (struct string_buffer *buffer, const char *str); +extern int sb_append1 (struct string_buffer *buffer, char c); + +/* Appends the contents of the memory area STR to BUFFER. + Returns 0, or -1 in case of out-of-memory error. */ +extern int sb_append_desc (struct string_buffer *buffer, string_desc_t s); + +/* Appends the contents of the C string STR to BUFFER. + Returns 0, or -1 in case of out-of-memory error. */ +extern int sb_append_c (struct string_buffer *buffer, const char *str); /* Appends the result of the printf-compatible FORMATSTRING with the argument list LIST to BUFFER. @@ -77,7 +86,7 @@ extern int sb_appendvf (struct string_buffer *buffer, Therefore, if the format string is valid and does not use %ls/%lc directives nor widths, the only possible error code is ENOMEM. */ extern int sb_appendf (struct string_buffer *buffer, - const char *formatstring, ...) + const char *formatstring, ...) #if (__GNUC__ + (__GNUC_MINOR__ >= 4) > 4) && !defined __clang__ ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 3)) #else @@ -89,10 +98,29 @@ extern int sb_appendf (struct string_buffer *buffer, extern void sb_free (struct string_buffer *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); -/* Returns the contents of BUFFER, and frees all other memory held - by BUFFER. Returns NULL upon failure or if there was an error earlier. +/* Returns a read-only view of the current contents of BUFFER. + The result is only valid until the next operation on BUFFER. */ +extern string_desc_t sb_contents (struct string_buffer *buffer); + +/* Ensures the contents of BUFFER is followed by a NUL byte (without + incrementing the length of the contents). + Then returns a read-only view of the current contents of BUFFER, + that is, the current contents of BUFFER as a C string. + Returns NULL upon out-of-memory error. + The result is only valid until the next operation on BUFFER. */ +extern const char * sb_contents_c (struct string_buffer *buffer); + +/* Returns the contents of BUFFER and frees all other memory held by BUFFER. + Returns NULL upon failure or if there was an error earlier. + It is the responsibility of the caller to string_desc_free() the result. */ +extern string_desc_t sb_dupfree (struct string_buffer *buffer) + _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); + +/* Returns the contents of BUFFER (with an added trailing NUL, that is, + as a C string), and frees all other memory held by BUFFER. + Returns NULL upon failure or if there was an error earlier. It is the responsibility of the caller to free() the result. */ -extern char * sb_dupfree (struct string_buffer *buffer) +extern char * sb_dupfree_c (struct string_buffer *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); #ifdef __cplusplus diff --git a/modules/string-buffer b/modules/string-buffer index 2010641b2b..21d3fdcf5b 100644 --- a/modules/string-buffer +++ b/modules/string-buffer @@ -9,6 +9,7 @@ lib/string-buffer-printf.c Depends-on: stdbool attribute +string-desc stdarg vsnzprintf-posix diff --git a/tests/test-string-buffer.c b/tests/test-string-buffer.c index d492837b39..580263b656 100644 --- a/tests/test-string-buffer.c +++ b/tests/test-string-buffer.c @@ -44,12 +44,50 @@ char invalid_format_string_2[] = "%^"; int main () { + /* Test accumulation. */ + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append1 (&buffer, 'x'); + sb_append1 (&buffer, '\377'); + char *s = sb_dupfree_c (&buffer); + ASSERT (s != NULL && strcmp (s, "x\377") == 0); + free (s); + } + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append1 (&buffer, 'x'); + sb_append1 (&buffer, '\377'); + { + string_desc_t sd = sb_contents (&buffer); + ASSERT (string_desc_length (sd) == 2); + ASSERT (string_desc_char_at (sd, 0) == 'x'); + ASSERT (string_desc_char_at (sd, 1) == '\377'); + } + sb_append1 (&buffer, '\0'); + sb_append1 (&buffer, 'z'); + { + string_desc_t sd = sb_contents (&buffer); + ASSERT (string_desc_length (sd) == 4); + ASSERT (string_desc_char_at (sd, 0) == 'x'); + ASSERT (string_desc_char_at (sd, 1) == '\377'); + ASSERT (string_desc_char_at (sd, 2) == '\0'); + ASSERT (string_desc_char_at (sd, 3) == 'z'); + } + char *s = sb_dupfree_c (&buffer); + ASSERT (s != NULL && memcmp (s, "x\377\0z\0", 5) == 0); + free (s); + } + /* Test simple string concatenation. */ { struct string_buffer buffer; sb_init (&buffer); - char *s = sb_dupfree (&buffer); + char *s = sb_dupfree_c (&buffer); ASSERT (s != NULL && strcmp (s, "") == 0); free (s); } @@ -58,23 +96,35 @@ main () struct string_buffer buffer; sb_init (&buffer); - sb_append (&buffer, "abc"); - sb_append (&buffer, ""); - sb_append (&buffer, "defg"); - char *s = sb_dupfree (&buffer); + sb_append_c (&buffer, "abc"); + sb_append_c (&buffer, ""); + sb_append_c (&buffer, "defg"); + char *s = sb_dupfree_c (&buffer); ASSERT (s != NULL && strcmp (s, "abcdefg") == 0); free (s); } + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append_c (&buffer, "abc"); + sb_append_desc (&buffer, string_desc_new_addr (5, "de\0fg")); + sb_append_c (&buffer, "hij"); + char *s = sb_dupfree_c (&buffer); + ASSERT (s != NULL && memcmp (s, "abcde\0fghij", 12) == 0); + free (s); + } + /* Test printf-like formatting. */ { struct string_buffer buffer; sb_init (&buffer); - sb_append (&buffer, "<"); + sb_append_c (&buffer, "<"); sb_appendf (&buffer, "%x", 3735928559U); - sb_append (&buffer, ">"); - char *s = sb_dupfree (&buffer); + sb_append_c (&buffer, ">"); + char *s = sb_dupfree_c (&buffer); ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0); free (s); } @@ -84,10 +134,10 @@ main () struct string_buffer buffer; sb_init (&buffer); - sb_append (&buffer, "<"); + sb_append_c (&buffer, "<"); my_appendf (&buffer, "%x", 3735928559U); - sb_append (&buffer, ">"); - char *s = sb_dupfree (&buffer); + sb_append_c (&buffer, ">"); + char *s = sb_dupfree_c (&buffer); ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0); free (s); } @@ -102,7 +152,7 @@ main () int ret; sb_init (&buffer); - sb_append (&buffer, "<"); + sb_append_c (&buffer, "<"); ret = sb_appendf (&buffer, "%lc", (wint_t) 0x76543210); #if !defined _AIX @@ -110,20 +160,20 @@ main () ASSERT (errno == EILSEQ); #endif - sb_append (&buffer, "|"); + sb_append_c (&buffer, "|"); ret = sb_appendf (&buffer, invalid_format_string_1, 1); ASSERT (ret < 0); ASSERT (errno == EINVAL); - sb_append (&buffer, "|"); + sb_append_c (&buffer, "|"); ret = sb_appendf (&buffer, invalid_format_string_2, 2); ASSERT (ret < 0); ASSERT (errno == EINVAL); - sb_append (&buffer, ">"); - char *s = sb_dupfree (&buffer); + sb_append_c (&buffer, ">"); + char *s = sb_dupfree_c (&buffer); ASSERT (s == NULL); }