Hi Marc, > I have another question concerning the string-buffer/string-desc > modules. The code > > struct string_buffer sb[1]; > sb_init (sb); > sb_xdupfree (sb); > > gives a "memory exhausted" error (through xalloc_die). I found this > surprising, at the very least.
That's definitely a bug. Thanks for finding it! Fixed as follows. 2025-07-17 Bruno Haible <br...@clisp.org> Fix sb_dupfree(), sbr_dupfree() on an empty buffer. Reported by Marc Nieper-Wißkirchen <marc.nieper+...@gmail.com> at <https://lists.gnu.org/archive/html/bug-gnulib/2025-07/msg00119.html>. * lib/string-desc.c (_sd_new_addr, _rwsd_new_addr): Don't canonicalize (0, non-NULL) to (0, NULL). * lib/string-buffer.h (sb_dupfree): Fix description. * lib/string-buffer-reversed.h (sbr_dupfree): Likewise. * tests/test-string-buffer.c (main): Test sb_dupfree on an empty buffer. * tests/test-string-buffer-reversed.c (main): Test sbr_dupfree on an empty buffer. diff --git a/lib/string-buffer-reversed.h b/lib/string-buffer-reversed.h index 957af427b5..749e0e546b 100644 --- a/lib/string-buffer-reversed.h +++ b/lib/string-buffer-reversed.h @@ -129,7 +129,7 @@ extern string_desc_t sbr_contents (struct string_buffer_reversed *buffer); extern const char * sbr_contents_c (struct string_buffer_reversed *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. + Returns (0, NULL) upon failure or if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ extern rw_string_desc_t sbr_dupfree (struct string_buffer_reversed *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); diff --git a/lib/string-buffer.h b/lib/string-buffer.h index 99f5d40bb0..e87f8f6fc4 100644 --- a/lib/string-buffer.h +++ b/lib/string-buffer.h @@ -127,7 +127,7 @@ extern string_desc_t sb_contents (struct string_buffer *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. + Returns (0, NULL) upon failure or if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ extern rw_string_desc_t sb_dupfree (struct string_buffer *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); diff --git a/lib/string-desc.c b/lib/string-desc.c index 7cabe4ed84..ba8443fe59 100644 --- a/lib/string-desc.c +++ b/lib/string-desc.c @@ -154,9 +154,14 @@ _sd_new_addr (idx_t n, const char *addr) string_desc_t result; result._nbytes = n; + /* No, it is not a good idea to canonicalize (0, non-NULL) to (0, NULL). + Some functions that return a string_desc_t use a return value of (0, NULL) + to denote failure. */ +#if 0 if (n == 0) result._data = NULL; else +#endif result._data = (char *) addr; return result; @@ -167,9 +172,14 @@ _rwsd_new_addr (idx_t n, /*!*/const/*!*/ char *addr) rw_string_desc_t result; result._nbytes = n; + /* No, it is not a good idea to canonicalize (0, non-NULL) to (0, NULL). + Some functions that return a rw_string_desc_t use a return value of + (0, NULL) to denote failure. */ +#if 0 if (n == 0) result._data = NULL; else +#endif result._data = (char *) addr; return result; diff --git a/tests/test-string-buffer-reversed.c b/tests/test-string-buffer-reversed.c index 39428e6ca5..aeee6587f3 100644 --- a/tests/test-string-buffer-reversed.c +++ b/tests/test-string-buffer-reversed.c @@ -49,6 +49,17 @@ main () { struct string_buffer_reversed buffer; + sbr_init (&buffer); + rw_string_desc_t contents = sbr_dupfree (&buffer); + ASSERT (sd_is_empty (contents)); + /* Here it is important to distinguish (0, NULL), which stands for an error, + from (0, non-NULL), which is a successful result. */ + ASSERT (sd_data (contents) != NULL); + sd_free (contents); + } + { + struct string_buffer_reversed buffer; + sbr_init (&buffer); sbr_prepend1 (&buffer, '\377'); sbr_prepend1 (&buffer, 'x'); diff --git a/tests/test-string-buffer.c b/tests/test-string-buffer.c index fa3bb82bb6..c540aadeb9 100644 --- a/tests/test-string-buffer.c +++ b/tests/test-string-buffer.c @@ -48,6 +48,17 @@ main () { struct string_buffer buffer; + sb_init (&buffer); + rw_string_desc_t contents = sb_dupfree (&buffer); + ASSERT (sd_is_empty (contents)); + /* Here it is important to distinguish (0, NULL), which stands for an error, + from (0, non-NULL), which is a successful result. */ + ASSERT (sd_data (contents) != NULL); + sd_free (contents); + } + { + struct string_buffer buffer; + sb_init (&buffer); sb_append1 (&buffer, 'x'); sb_append1 (&buffer, '\377');