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');




Reply via email to