https://github.com/python/cpython/commit/bab97c599f7c66b6b7b718a1c03a4bf4520b504c
commit: bab97c599f7c66b6b7b718a1c03a4bf4520b504c
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-12T20:12:45Z
summary:

gh-157242: Allow negative grow in PyBytesWriter_Grow() (#157375)

PyBytesWriter_Resize() doesn't need to call byteswriter_resize() if
the buffer is already large enough.

files:
M Doc/c-api/bytes.rst
M Lib/test/test_capi/test_bytes.py
M Objects/bytesobject.c

diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst
index ff68ecafcda4d0..bf89481a628ed2 100644
--- a/Doc/c-api/bytes.rst
+++ b/Doc/c-api/bytes.rst
@@ -429,7 +429,7 @@ Low-level API
    On success, return ``0``.
    On error, set an exception and return ``-1``.
 
-   *size* can be negative to shrink the writer.
+   *grow* can be negative to shrink the writer.
 
 .. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter 
*writer, Py_ssize_t size, void *buf)
 
diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index 1500faa7f71344..6c19ad14b7e6c5 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -461,6 +461,24 @@ def test_grow(self):
         writer.grow(0)  # noop
         self.assertEqual(writer.finish(), b'number=123')
 
+        for size in (self.SMALL_BUFFER, self.LARGE_BUFFER):
+            with self.subTest(size=size):
+                # Truncate the last byte
+                data = b'x' * size
+                writer = self.create_writer(size)
+                writer.write(0, data)
+                self.assertEqual(writer.get_data(), data)
+                writer.grow(-1)
+                self.assertEqual(writer.get_data(), data[:-1])
+                self.assertEqual(writer.finish(),  data[:-1])
+
+                # Make the buffer empty
+                writer = self.create_writer(size)
+                writer.write(0, data)
+                writer.grow(-size)
+                self.assertEqual(writer.get_data(), b'')
+                self.assertEqual(writer.finish(),  b'')
+
         # Switch from small buffer to large buffer
         writer = self.create_writer()
         small, large = self.SMALL_BUFFER, self.LARGE_BUFFER
@@ -476,8 +494,8 @@ def test_grow(self):
             with self.subTest(size=size):
                 writer = self.create_writer()
                 writer.write_bytes(b'x' * size, -1)
-                with self.assertRaisesRegex(ValueError, 'size must be >= 0'):
-                    writer.grow(-1)
+                with self.assertRaisesRegex(ValueError, 'invalid size'):
+                    writer.grow(-size - 1)
                 with self.assertRaises(MemoryError):
                     writer.grow(_testcapi.PY_SSIZE_T_MAX)
                 self.assertEqual(writer.finish(), b'x' * size)
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index ace5fe9d86a7d3..deddb8d959b157 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3845,8 +3845,13 @@ PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t 
size)
         PyErr_SetString(PyExc_ValueError, "size must be >= 0");
         return -1;
     }
-    if (byteswriter_resize(writer, size, 1) < 0) {
-        return -1;
+    if (writer->size < size) {
+        if (byteswriter_resize(writer, size, 1) < 0) {
+            return -1;
+        }
+    }
+    else {
+        // The buffer is already large enough. Never shrink the buffer.
     }
     writer->size = size;
     return 0;
@@ -3866,22 +3871,26 @@ _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter 
*writer, Py_ssize_t size,
 
 
 int
-PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size)
+PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
 {
-    if (size < 0) {
-        PyErr_SetString(PyExc_ValueError, "size must be >= 0");
-        return -1;
-    }
-    if (size == 0) {
+    if (grow == 0) {
         // Nothing to do
         return 0;
     }
 
-    if (size > PY_SSIZE_T_MAX - writer->size) {
-        PyErr_NoMemory();
-        return -1;
+    if (grow >= 0) {
+        if (grow > PY_SSIZE_T_MAX - writer->size) {
+            PyErr_NoMemory();
+            return -1;
+        }
+    }
+    else {
+        if (writer->size + grow < 0) {
+            PyErr_SetString(PyExc_ValueError, "invalid size");
+            return -1;
+        }
     }
-    size = writer->size + size;
+    Py_ssize_t size = writer->size + grow;
 
     if (byteswriter_resize(writer, size, 1) < 0) {
         return -1;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to