From d0441c49597936730fc9ba6c69bba5e6814399a8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 02:00:24 +0200 Subject: [PATCH 1/6] gh-157242: Leave bytearray unchanged if resize() fails If bytearray.resize() or bytearray.take_bytes() fails, leave the bytearray unchanged. If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. --- Include/internal/pycore_bytesobject.h | 2 + Lib/test/test_bytes.py | 15 +++++ Lib/test/test_capi/test_bytes.py | 15 +++++ Objects/bytearrayobject.c | 15 ++--- Objects/bytesobject.c | 90 ++++++++++++++++++--------- 5 files changed, 98 insertions(+), 39 deletions(-) diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h index 27a7a46152f57b8..32da177c637c268 100644 --- a/Include/internal/pycore_bytesobject.h +++ b/Include/internal/pycore_bytesobject.h @@ -75,6 +75,8 @@ PyAPI_FUNC(PyObject *) _PyBytes_Repeat(PyObject *self, Py_ssize_t n); */ #define _PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1) +extern int _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize); + /* --- PyBytesWriter ------------------------------------------------------ */ struct PyBytesWriter { diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 1b9918c6c8f473c..ffcefe1dcb53f74 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1555,6 +1555,21 @@ def test_resize(self): self.assertRaises(MemoryError, bytearray().resize, sys.maxsize) self.assertRaises(MemoryError, bytearray(1000).resize, sys.maxsize) + def test_resize_error(self): + # gh-157242: If bytearray.resize() fails (memory allocation failure), + # the bytearray must be left unchanged. + _testcapi = import_helper.import_module('_testcapi') + + data = b'some data' + ba = bytearray(data) + try: + with self.assertRaises(MemoryError): + _testcapi.set_nomemory(0) + ba.resize(1024) + finally: + _testcapi.remove_mem_hooks() + self.assertEqual(ba, data) + def test_take_bytes(self): ba = bytearray(b'ab') self.assertEqual(ba.take_bytes(), b'ab') diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py index 4c1431bacef0a27..70acca8b7693b1b 100644 --- a/Lib/test/test_capi/test_bytes.py +++ b/Lib/test/test_capi/test_bytes.py @@ -373,6 +373,21 @@ def test_resize(self): writer.resize(len(b'number=123456'), b'456') self.assertEqual(writer.finish(), self.result_type(b'number=123456')) + def test_resize_error(self): + small_buffer = _testcapi.PyBytesWriter_small_buffer + init = b'x' * (small_buffer * 2) + writer = self.create_writer(len(init), init) + size = len(init) + 100 + try: + with self.assertRaises(MemoryError): + _testcapi.set_nomemory(0) + writer.resize(size, b'') + finally: + _testcapi.remove_mem_hooks() + suffix = b'still working' + writer.write_bytes(suffix, -1) + self.assertEqual(writer.finish(), self.result_type(init + suffix)) + def test_format_i(self): # Test PyBytesWriter_Format() writer = self.create_writer() diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 055fedc3ddfb034..a510360a738539d 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -288,18 +288,16 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) Py_MIN(requested_size, Py_SIZE(self))); } - int ret = _PyBytes_Resize(&obj->ob_bytes_object, alloc); - if (ret == -1) { - obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - size = alloc = 0; + if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) { + return -1; } + bytearray_reinit_from_bytes(obj, size, alloc); if (alloc != size) { /* Add mid-buffer null; end provided by bytes. */ obj->ob_bytes[size] = '\0'; } - - return ret; + return 0; } int @@ -1632,10 +1630,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) self->ob_start = self->ob_bytes; } - if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) { - assert(self->ob_bytes_object == NULL); - self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - bytearray_reinit_from_bytes(self, 0, 0); + if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) { Py_DECREF(remaining); return NULL; } diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 2ae55b33f4f49d7..4f73789c25b838f 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3344,68 +3344,99 @@ PyBytes_ConcatAndDel(PyObject **pv, PyObject *w) does *not* include that), and a trailing \0 byte is stored. */ -int -_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) +static int +bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) { - PyObject *v; - PyBytesObject *sv; - v = *pv; + PyObject *v = *pv; + PyObject *result; + if (!PyBytes_Check(v) || newsize < 0) { - *pv = 0; - Py_DECREF(v); PyErr_BadInternalCall(); - return -1; + goto error; } + Py_ssize_t oldsize = PyBytes_GET_SIZE(v); if (oldsize == newsize) { /* return early if newsize equals to v->ob_size */ return 0; } + if (oldsize == 0) { - *pv = _PyBytes_FromSize(newsize, 0); + result = _PyBytes_FromSize(newsize, 0); + if (result == NULL) { + goto error; + } + *pv = result; Py_DECREF(v); - return (*pv == NULL) ? -1 : 0; + return 0; } + if (newsize == 0) { - *pv = bytes_get_empty(); + *pv = bytes_get_empty(); // cannot fail Py_DECREF(v); return 0; } + if (!_PyObject_IsUniquelyReferenced(v)) { - if (oldsize < newsize) { - *pv = _PyBytes_FromSize(newsize, 0); - if (*pv) { - memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v), oldsize); - } - } - else { - *pv = PyBytes_FromStringAndSize(PyBytes_AS_STRING(v), newsize); + result = _PyBytes_FromSize(newsize, 0); + if (!result) { + goto error; } + + memcpy(PyBytes_AS_STRING(result), PyBytes_AS_STRING(v), Py_MIN(oldsize, newsize)); + *pv = result; Py_DECREF(v); - return (*pv == NULL) ? -1 : 0; + return 0; } - assert(v != bytes_get_empty()); + assert(v != bytes_get_empty()); #ifdef Py_TRACE_REFS _Py_ForgetReference(v); #endif _PyReftracerTrack(v, PyRefTracer_DESTROY); - *pv = (PyObject *) - PyObject_Realloc(v, PyBytesObject_SIZE + newsize); - if (*pv == NULL) { + result = (PyObject *)PyObject_Realloc(v, PyBytesObject_SIZE + newsize); + if (result == NULL) { + if (clear_obj_on_error) { + *pv = NULL; #ifdef Py_REF_DEBUG - _Py_DecRefTotal(_PyThreadState_GET()); + _Py_DecRefTotal(_PyThreadState_GET()); #endif - PyObject_Free(v); + PyObject_Free(v); + } PyErr_NoMemory(); return -1; } - _Py_NewReferenceNoTotal(*pv); - sv = (PyBytesObject *) *pv; + + v = result; + _Py_NewReferenceNoTotal(v); + PyBytesObject *sv = (PyBytesObject *)v; Py_SET_SIZE(sv, newsize); sv->ob_sval[newsize] = '\0'; set_ob_shash(sv, -1); /* invalidate cached hash value */ + *pv = v; return 0; + +error: + if (clear_obj_on_error) { + *pv = NULL; + Py_DECREF(v); + } + return -1; +} + + +int +_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) +{ + return bytes_resize(pv, newsize, 1); +} + + +// Similar to _PyBytes_Resize(), but leaves the object unchanged on error. +int +_PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) +{ + return bytes_resize(pv, newsize, 0); } @@ -3646,7 +3677,8 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize) } } else { - if (_PyBytes_Resize(&writer->obj, size)) { + if (_PyBytes_ResizeKeepOnError(&writer->obj, size)) { + assert(writer->obj != NULL); return -1; } } From 5f1ef328da91e6708fd4c474508a32408d069a19 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 02:51:27 +0200 Subject: [PATCH 2/6] Add NEWS entries --- .../next/C_API/2026-09-10-02-51-26.gh-issue-157242.tfXIsf.rst | 3 +++ .../2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-10-02-51-26.gh-issue-157242.tfXIsf.rst create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst diff --git a/Misc/NEWS.d/next/C_API/2026-09-10-02-51-26.gh-issue-157242.tfXIsf.rst b/Misc/NEWS.d/next/C_API/2026-09-10-02-51-26.gh-issue-157242.tfXIsf.rst new file mode 100644 index 000000000000000..d2e48a651e367a3 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-10-02-51-26.gh-issue-157242.tfXIsf.rst @@ -0,0 +1,3 @@ +Fix :c:func:`PyBytesWriter_Resize` to handle properly memory allocation +failure. Leave the writer unchanged on error, instead of leaving it in an +inconsistent state. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst new file mode 100644 index 000000000000000..fa8de3bd0abb61a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst @@ -0,0 +1,3 @@ +If :meth:`bytearray.resize` or :meth:`bytearray.take_bytes` fails, leave the +:class:`bytearray` unchanged, instead of clearing it. Patch by Victor +Stinner. From a1e64d39f51d65e9572c94604e16d5b001ed4caa Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 10:38:44 +0200 Subject: [PATCH 3/6] Fix resize() with non-zero start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maurycy Pawłowski-Wieroński --- Lib/test/test_bytes.py | 15 ++++++++++++++- Objects/bytearrayobject.c | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index ffcefe1dcb53f74..d92d08fe49d4e72 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1560,6 +1560,7 @@ def test_resize_error(self): # the bytearray must be left unchanged. _testcapi = import_helper.import_module('_testcapi') + # Simple bytearray data = b'some data' ba = bytearray(data) try: @@ -1568,7 +1569,19 @@ def test_resize_error(self): ba.resize(1024) finally: _testcapi.remove_mem_hooks() - self.assertEqual(ba, data) + self.assertEqual(ba, bytearray(data)) + + # bytearray with non-zero logical start + ba = bytearray(b'0123456789') + expected = ba[3:] + del ba[:3] + try: + with self.assertRaises(MemoryError): + _testcapi.set_nomemory(0) + ba.resize(1024) + finally: + _testcapi.remove_mem_hooks() + self.assertEqual(ba, expected) def test_take_bytes(self): ba = bytearray(b'ab') diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index a510360a738539d..914241b5d87ae4e 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -286,6 +286,7 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) _PyBytes_Resize will do a malloc + memcpy internally. */ memmove(obj->ob_bytes, obj->ob_start, Py_MIN(requested_size, Py_SIZE(self))); + obj->ob_start = obj->ob_bytes; } if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) { From 4b4bcfba9682cd70076ad848497fca001a4f2988 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 10:45:54 +0200 Subject: [PATCH 4/6] Reimplement _PyBytes_Resize() via _PyBytes_ResizeKeepOnError() For the in-place resize code path, no longer call _Py_ForgetReference() and _PyReftracerTrack() before PyObject_Realloc(). --- Objects/bytesobject.c | 50 ++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 4f73789c25b838f..19e147b289d351e 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3344,15 +3344,16 @@ PyBytes_ConcatAndDel(PyObject **pv, PyObject *w) does *not* include that), and a trailing \0 byte is stored. */ -static int -bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) +// Similar to _PyBytes_Resize(), but leaves the object unchanged on error. +int +_PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) { PyObject *v = *pv; PyObject *result; if (!PyBytes_Check(v) || newsize < 0) { PyErr_BadInternalCall(); - goto error; + return -1; } Py_ssize_t oldsize = PyBytes_GET_SIZE(v); @@ -3364,7 +3365,7 @@ bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) if (oldsize == 0) { result = _PyBytes_FromSize(newsize, 0); if (result == NULL) { - goto error; + return -1; } *pv = result; Py_DECREF(v); @@ -3380,7 +3381,7 @@ bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) if (!_PyObject_IsUniquelyReferenced(v)) { result = _PyBytes_FromSize(newsize, 0); if (!result) { - goto error; + return -1; } memcpy(PyBytes_AS_STRING(result), PyBytes_AS_STRING(v), Py_MIN(oldsize, newsize)); @@ -3390,23 +3391,17 @@ bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) } assert(v != bytes_get_empty()); -#ifdef Py_TRACE_REFS - _Py_ForgetReference(v); -#endif - _PyReftracerTrack(v, PyRefTracer_DESTROY); result = (PyObject *)PyObject_Realloc(v, PyBytesObject_SIZE + newsize); if (result == NULL) { - if (clear_obj_on_error) { - *pv = NULL; -#ifdef Py_REF_DEBUG - _Py_DecRefTotal(_PyThreadState_GET()); -#endif - PyObject_Free(v); - } PyErr_NoMemory(); return -1; } +#ifdef Py_TRACE_REFS + _Py_ForgetReference(v); +#endif + _PyReftracerTrack(v, PyRefTracer_DESTROY); + v = result; _Py_NewReferenceNoTotal(v); PyBytesObject *sv = (PyBytesObject *)v; @@ -3415,28 +3410,19 @@ bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error) set_ob_shash(sv, -1); /* invalidate cached hash value */ *pv = v; return 0; - -error: - if (clear_obj_on_error) { - *pv = NULL; - Py_DECREF(v); - } - return -1; } int _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) { - return bytes_resize(pv, newsize, 1); -} - - -// Similar to _PyBytes_Resize(), but leaves the object unchanged on error. -int -_PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) -{ - return bytes_resize(pv, newsize, 0); + int res = _PyBytes_ResizeKeepOnError(pv, newsize); + if (res < 0) { + PyObject *v = *pv; + *pv = NULL; + Py_DECREF(v); + } + return res; } From 4d581fae561124a745c27fbadbe4f54751cf7199 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 12:03:50 +0200 Subject: [PATCH 5/6] Fix resize() when shrinking the bytearray --- Lib/test/test_bytes.py | 30 +++++++++++++++++++++--------- Objects/bytearrayobject.c | 38 +++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 57fd229f7f9ace7..4936fec51cd0db3 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1563,24 +1563,36 @@ def test_resize_error(self): # Simple bytearray data = b'some data' ba = bytearray(data) - try: - with self.assertRaises(MemoryError): + with self.assertRaises(MemoryError): + try: _testcapi.set_nomemory(0) ba.resize(1024) - finally: - _testcapi.remove_mem_hooks() + finally: + _testcapi.remove_mem_hooks() self.assertEqual(ba, bytearray(data)) - # bytearray with non-zero logical start + # growing bytearray with non-zero logical start ba = bytearray(b'0123456789') expected = ba[3:] del ba[:3] - try: - with self.assertRaises(MemoryError): + with self.assertRaises(MemoryError): + try: _testcapi.set_nomemory(0) ba.resize(1024) - finally: - _testcapi.remove_mem_hooks() + finally: + _testcapi.remove_mem_hooks() + self.assertEqual(ba, expected) + + # shrink bytearray with non-zero logical start + ba = bytearray(b'0123456789') + expected = ba[3:] + del ba[:3] + with self.assertRaises(MemoryError): + try: + _testcapi.set_nomemory(0) + ba.resize(1) + finally: + _testcapi.remove_mem_hooks() self.assertEqual(ba, expected) def test_take_bytes(self): diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 175b23fd47a426b..ff82800515f59ee 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -281,19 +281,31 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) return -1; } - /* Re-align data to the start of the allocation. */ - if (logical_offset > 0) { - /* optimization tradeoff: This is faster than a new allocation when - the number of bytes being removed in a resize is small; for large - size changes it may be better to just make a new bytes object as - _PyBytes_Resize will do a malloc + memcpy internally. */ - memmove(obj->ob_bytes, obj->ob_start, - Py_MIN(requested_size, Py_SIZE(self))); - obj->ob_start = obj->ob_bytes; - } - - if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) { - return -1; + if (logical_offset == 0 || requested_size >= Py_SIZE(self)) { + /* Re-align data to the start of the allocation. */ + if (logical_offset > 0) { + /* optimization tradeoff: This is faster than a new allocation when + the number of bytes being removed in a resize is small; for large + size changes it may be better to just make a new bytes object as + _PyBytes_Resize will do a malloc + memcpy internally. */ + memmove(obj->ob_bytes, obj->ob_start, Py_SIZE(self)); + obj->ob_start = obj->ob_bytes; + } + + if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) { + return -1; + } + } + else { + // Using memmove() would be unsafe, since _PyBytes_ResizeKeepOnError() + // failure code path would be unable to restore the bytearray to its + // previous state. + PyObject *resized = PyBytes_FromStringAndSize(NULL, requested_size); + if (resized == NULL) { + return -1; + } + memcpy(PyBytes_AS_STRING(resized), obj->ob_start, requested_size); + Py_SETREF(obj->ob_bytes_object, resized); } bytearray_reinit_from_bytes(obj, size, alloc); From f51cba57d3e5b2acb6bfd4540ebf4abc679beb71 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 10 Sep 2026 12:25:17 +0200 Subject: [PATCH 6/6] Fix take_bytes() --- Lib/test/test_bytes.py | 19 +++++++++++++++++++ Objects/bytearrayobject.c | 32 ++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 4936fec51cd0db3..069c8c89e687c0c 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1659,6 +1659,25 @@ def test_take_bytes(self): self.assertEqual(ba, bytearray(b'A')) self.assertEqual(ord(b'c'), ord('c')) + def test_take_bytes_error(self): + # gh-157242: If bytearray.take_bytes() fails (memory allocation + # failure), the bytearray must be left unchanged. + _testcapi = import_helper.import_module('_testcapi') + + for mem_error in (0, 1): + for to_take in (5, None): + with self.subTest(mem_error=mem_error, to_take=to_take): + ba = bytearray(b'0123456789') + expected = ba[3:] + del ba[:3] + with self.assertRaises(MemoryError): + try: + _testcapi.set_nomemory(mem_error) + ba.take_bytes(5) + finally: + _testcapi.remove_mem_hooks() + self.assertEqual(ba, expected) + @support.cpython_only # tests an implementation detail def test_take_bytes_optimization(self): # Validate optimization around taking lots of little chunks out of a diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index ff82800515f59ee..bca5d0ad95a1006 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1621,6 +1621,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) } Py_ssize_t remaining_length = size - to_take; + // optimization: If taking less than leaving, just copy the small to_take // portion out and move ob_start. if (to_take < remaining_length) { @@ -1642,15 +1643,30 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) memcpy(PyBytes_AS_STRING(remaining), self->ob_start + to_take, remaining_length); - // If the bytes are offset inside the buffer must first align. - if (self->ob_start != self->ob_bytes) { - memmove(self->ob_bytes, self->ob_start, to_take); - self->ob_start = self->ob_bytes; - } + size_t logical_offset = (size_t) (self->ob_start - self->ob_bytes); + if (logical_offset == 0 || remaining_length == 0) { + // If the bytes are offset inside the buffer must first align. + if (logical_offset != 0) { + memmove(self->ob_bytes, self->ob_start, to_take); + self->ob_start = self->ob_bytes; + } - if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) { - Py_DECREF(remaining); - return NULL; + if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) { + Py_DECREF(remaining); + return NULL; + } + } + else { + // Using memmove() would be unsafe, since _PyBytes_ResizeKeepOnError() + // failure code path would be unable to restore the bytearray to its + // previous state. + PyObject *resized = PyBytes_FromStringAndSize(NULL, to_take); + if (resized == NULL) { + Py_DECREF(remaining); + return NULL; + } + memcpy(PyBytes_AS_STRING(resized), self->ob_start, to_take); + Py_SETREF(self->ob_bytes_object, resized); } // Point the bytearray towards the buffer with the remaining data.