From c961d61e9ad5635939f59e869f7bee9df4c162e3 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 10 Jul 2026 10:55:48 +0100 Subject: [PATCH 1/5] gh-153419: Fix several issues around bytearray __init__ Introduce a bytearray_new function to ensure that ob_bytes_object is always set on a bytearray. Resizing a bytearray to 0 length explicitly sets the ob_bytes_object to the empty constant immortal. Add a check in the 'bytearray init from string' fast path to ensure there are no active exports. This fixes asserts/crashes on the following: - bytearray(1).__init__() - bytearray().__new__(bytearray).append(1) - a = bytearray(); b = memoryview(a); a.__init__('x', 'ascii') --- Lib/test/test_bytes.py | 48 +++++++++++++++++++ ...-07-10-10-53-11.gh-issue-153419.QI9uwG.rst | 2 + Objects/bytearrayobject.c | 41 +++++++++++----- 3 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e211c3d15a4ed20..b10d8aa0503cac2 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1860,6 +1860,54 @@ def g(): alloc = b.__alloc__() self.assertGreater(alloc, len(b)) + def test_reinit_small_buffer(self): + # gh-153419: reinitializing a bytearray whose buffer allocation is + # exactly one byte used to fail a debug assertion + b = bytearray(1) + b.__init__() + self.assertEqual(b, bytearray()) + + def test_reinit_empty_buffer(self): + # gh-153419: Calling __init__ on a bytearray that has an active + # export should only be allowed if the bytearray is unchanged. + b = bytearray() + with memoryview(b): + self.assertRaises(BufferError, b.__init__, b"abc") + self.assertRaises(BufferError, b.__init__, "abc", "ascii") + self.assertRaises(BufferError, b.__init__, 3) + b.__init__() + b.__init__(b"") + b.__init__("", "ascii") + self.assertEqual(b, bytearray()) + + def test_reinit_nonempty_buffer(self): + # gh-153419: If a buffer is non-empty and has an active export + # calling __init__ on it should always fail, even if the + # new value ends up the same as the old one. This is purely for + # practical reasons rather than a design goal. + b = bytearray(b"abc") + with memoryview(b): + self.assertRaises(BufferError, b.__init__) + self.assertRaises(BufferError, b.__init__, b"xy") + self.assertRaises(BufferError, b.__init__, b"abc") + + def test_new_without_init(self): + # gh-153419: a bytearray must be fully initialized by __new__ + # alone to ensure that the underlying buffer is always valid. + b = bytearray.__new__(bytearray) + self.assertEqual(b, bytearray()) + b.append(1) + self.assertEqual(b, bytearray(b"\x01")) + + class B(bytearray): + def __init__(self, *args): + pass # does not call super().__init__() + + b = B(b"ignored") + self.assertEqual(b, bytearray()) + b.extend(b"abc") + self.assertEqual(b, bytearray(b"abc")) + def test_extend(self): orig = b'hello' a = bytearray(orig) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst new file mode 100644 index 000000000000000..8f032029ca933e2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst @@ -0,0 +1,2 @@ +Fix several crashes relating to calling (or not calling) +``bytearray.__init__`` in unusual ways. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 696ddad8efaae55..98881731755aedd 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -236,6 +236,14 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) return -1; } + if (requested_size == 0) { + /* Resizing to zero just resets to the empty constant (#153419) */ + Py_XSETREF(obj->ob_bytes_object, + Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); + bytearray_reinit_from_bytes(obj, 0, 0); + return 0; + } + if (size + logical_offset <= alloc) { /* Current buffer is large enough to host the requested size, decide on a strategy. */ @@ -902,6 +910,19 @@ bytearray_ass_subscript(PyObject *op, PyObject *index, PyObject *values) return ret; } +static PyObject * +bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *self = PyType_GenericNew(type, args, kwds); + if (self == NULL) { + return NULL; + } + PyByteArrayObject *obj = _PyByteArray_CAST(self); + obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + bytearray_reinit_from_bytes(obj, 0, 0); + return self; +} + /*[clinic input] bytearray.__init__ @@ -920,22 +941,13 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, PyObject *it; PyObject *(*iternext)(PyObject *); - /* First __init__; set ob_bytes_object so ob_bytes is always non-null. */ - if (self->ob_bytes_object == NULL) { - self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - bytearray_reinit_from_bytes(self, 0, 0); - self->ob_exports = 0; - } - if (Py_SIZE(self) != 0) { - /* Empty previous contents (yes, do this first of all!) */ + /* Empty previous contents if possible (yes, do this first of all!). */ if (PyByteArray_Resize((PyObject *)self, 0) < 0) return -1; } - /* Should be caused by first init or the resize to 0. */ assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES)); - assert(self->ob_exports == 0); /* Make a quick exit if no first argument */ if (arg == NULL) { @@ -963,8 +975,11 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, } assert(PyBytes_Check(encoded)); - /* Most encodes return a new unique bytes, just use it as buffer. */ - if (_PyObject_IsUniquelyReferenced(encoded) + /* Most encodes return a new unique bytes, just use it as buffer. + If there are active exports, let `iconcat` handle raising the + error when encoded is not empty */ + if (self->ob_exports == 0 + && _PyObject_IsUniquelyReferenced(encoded) && PyBytes_CheckExact(encoded)) { Py_ssize_t size = Py_SIZE(encoded); @@ -2937,7 +2952,7 @@ PyTypeObject PyByteArray_Type = { 0, /* tp_dictoffset */ bytearray___init__, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ - PyType_GenericNew, /* tp_new */ + bytearray_new, /* tp_new */ PyObject_Free, /* tp_free */ .tp_version_tag = _Py_TYPE_VERSION_BYTEARRAY, }; From b54ca0f41bfb459f8b5d4f171c67758a8710f115 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sat, 11 Jul 2026 01:33:07 +0100 Subject: [PATCH 2/5] Revert initial version This reverts commit c961d61e9ad5635939f59e869f7bee9df4c162e3. --- Lib/test/test_bytes.py | 48 ------------------- ...-07-10-10-53-11.gh-issue-153419.QI9uwG.rst | 2 - Objects/bytearrayobject.c | 41 +++++----------- 3 files changed, 13 insertions(+), 78 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index b10d8aa0503cac2..e211c3d15a4ed20 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1860,54 +1860,6 @@ def g(): alloc = b.__alloc__() self.assertGreater(alloc, len(b)) - def test_reinit_small_buffer(self): - # gh-153419: reinitializing a bytearray whose buffer allocation is - # exactly one byte used to fail a debug assertion - b = bytearray(1) - b.__init__() - self.assertEqual(b, bytearray()) - - def test_reinit_empty_buffer(self): - # gh-153419: Calling __init__ on a bytearray that has an active - # export should only be allowed if the bytearray is unchanged. - b = bytearray() - with memoryview(b): - self.assertRaises(BufferError, b.__init__, b"abc") - self.assertRaises(BufferError, b.__init__, "abc", "ascii") - self.assertRaises(BufferError, b.__init__, 3) - b.__init__() - b.__init__(b"") - b.__init__("", "ascii") - self.assertEqual(b, bytearray()) - - def test_reinit_nonempty_buffer(self): - # gh-153419: If a buffer is non-empty and has an active export - # calling __init__ on it should always fail, even if the - # new value ends up the same as the old one. This is purely for - # practical reasons rather than a design goal. - b = bytearray(b"abc") - with memoryview(b): - self.assertRaises(BufferError, b.__init__) - self.assertRaises(BufferError, b.__init__, b"xy") - self.assertRaises(BufferError, b.__init__, b"abc") - - def test_new_without_init(self): - # gh-153419: a bytearray must be fully initialized by __new__ - # alone to ensure that the underlying buffer is always valid. - b = bytearray.__new__(bytearray) - self.assertEqual(b, bytearray()) - b.append(1) - self.assertEqual(b, bytearray(b"\x01")) - - class B(bytearray): - def __init__(self, *args): - pass # does not call super().__init__() - - b = B(b"ignored") - self.assertEqual(b, bytearray()) - b.extend(b"abc") - self.assertEqual(b, bytearray(b"abc")) - def test_extend(self): orig = b'hello' a = bytearray(orig) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst deleted file mode 100644 index 8f032029ca933e2..000000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-10-10-53-11.gh-issue-153419.QI9uwG.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix several crashes relating to calling (or not calling) -``bytearray.__init__`` in unusual ways. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 98881731755aedd..696ddad8efaae55 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -236,14 +236,6 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) return -1; } - if (requested_size == 0) { - /* Resizing to zero just resets to the empty constant (#153419) */ - Py_XSETREF(obj->ob_bytes_object, - Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); - bytearray_reinit_from_bytes(obj, 0, 0); - return 0; - } - if (size + logical_offset <= alloc) { /* Current buffer is large enough to host the requested size, decide on a strategy. */ @@ -910,19 +902,6 @@ bytearray_ass_subscript(PyObject *op, PyObject *index, PyObject *values) return ret; } -static PyObject * -bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyObject *self = PyType_GenericNew(type, args, kwds); - if (self == NULL) { - return NULL; - } - PyByteArrayObject *obj = _PyByteArray_CAST(self); - obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - bytearray_reinit_from_bytes(obj, 0, 0); - return self; -} - /*[clinic input] bytearray.__init__ @@ -941,13 +920,22 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, PyObject *it; PyObject *(*iternext)(PyObject *); + /* First __init__; set ob_bytes_object so ob_bytes is always non-null. */ + if (self->ob_bytes_object == NULL) { + self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + bytearray_reinit_from_bytes(self, 0, 0); + self->ob_exports = 0; + } + if (Py_SIZE(self) != 0) { - /* Empty previous contents if possible (yes, do this first of all!). */ + /* Empty previous contents (yes, do this first of all!) */ if (PyByteArray_Resize((PyObject *)self, 0) < 0) return -1; } + /* Should be caused by first init or the resize to 0. */ assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES)); + assert(self->ob_exports == 0); /* Make a quick exit if no first argument */ if (arg == NULL) { @@ -975,11 +963,8 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, } assert(PyBytes_Check(encoded)); - /* Most encodes return a new unique bytes, just use it as buffer. - If there are active exports, let `iconcat` handle raising the - error when encoded is not empty */ - if (self->ob_exports == 0 - && _PyObject_IsUniquelyReferenced(encoded) + /* Most encodes return a new unique bytes, just use it as buffer. */ + if (_PyObject_IsUniquelyReferenced(encoded) && PyBytes_CheckExact(encoded)) { Py_ssize_t size = Py_SIZE(encoded); @@ -2952,7 +2937,7 @@ PyTypeObject PyByteArray_Type = { 0, /* tp_dictoffset */ bytearray___init__, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ - bytearray_new, /* tp_new */ + PyType_GenericNew, /* tp_new */ PyObject_Free, /* tp_free */ .tp_version_tag = _Py_TYPE_VERSION_BYTEARRAY, }; From 314253c120505a3d6c510f1924d67f8d43c15f2e Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sat, 11 Jul 2026 01:10:17 +0100 Subject: [PATCH 3/5] bytearray_resize_lock_held always initializes ob_bytes_object if NULL There are several ways that bytearray_resize_lock_held gets called and for any of them, ob_bytes_object may be NULL. This allows us to remove the extra initialization in __init__ as this can't be guaranteed to be called anyway. Handle a related issue in take_bytes where in the extreme case of the bytes resize allocation failing (on a size reduction) then the bytearray other state fileds could be left inconsistent with a null ob_bytes_object. unconditionally call _canresize in __init__ as on initial creation, this will always be true, and if there are any exported views, then we just always fail, even if technically an empty bytearray doesn't get modified in this case, it's so rare, it's not worth it. --- Lib/test/test_bytes.py | 81 ++++++++++++++++++++++++++++++++++++++- Objects/bytearrayobject.c | 39 ++++++++++++++----- 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e211c3d15a4ed20..3db0927ba7ba54a 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -25,7 +25,7 @@ import test.string_tests import test.list_tests from test.support import bigaddrspacetest, MAX_Py_ssize_t -from test.support.script_helper import assert_python_failure +from test.support.script_helper import assert_python_failure, assert_python_ok if sys.flags.bytes_warning: @@ -2198,6 +2198,85 @@ def __len__(self): self.assertRaises(BufferError, ba.hex, S(b':')) +class ByteArrayInitialization1Test(unittest.TestCase): + # A bytearray created with __new__ so that __init__ is never called + # (often as a side-effect of a subclass not calling super().__init__). + # Is left with ob_bytes_object == NULL. It's easy for implementation + # code to not realise that ob_bytes_object can be NULL, so these tests + # verify a set of code paths that have historically crashed or asserted + # (see gh-153419) + + def _check(self, stmt, expected): + code = textwrap.dedent(f""" + a = bytearray.__new__(bytearray) + {stmt} + print(list(a)) + """) + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(out.decode().strip(), expected) + + def test_append(self): + self._check("a.append(1)", "[1]") + + def test_insert(self): + self._check("a.insert(0, 1)", "[1]") + + def test_extend_bytes(self): + self._check("a.extend(b'x')", "[120]") + + def test_extend_iterable(self): + self._check("a.extend([1, 2, 3])", "[1, 2, 3]") + + def test_iadd(self): + self._check("a += b'x'", "[120]") + + def test_slice_assign(self): + self._check("a[:] = b'xyz'", "[120, 121, 122]") + + def test_resize(self): + self._check("a.resize(4)", "[0, 0, 0, 0]") + + def test_init_int(self): + self._check("a.__init__(5)", "[0, 0, 0, 0, 0]") + + def test_init_bytes(self): + self._check("a.__init__(b'xyz')", "[120, 121, 122]") + + def test_reinit_length1(self): + # There is a shortcut taken when resizing, where alloc/2 < newsize. + # In this case, the existing buffer is reused, rather than reset + # If this happens when newsize == 0 and alloc == 1, then various + # code assumptions can be violated. This test should catch those + # in debug builds. (see gh-153419) + code = textwrap.dedent(""" + a = bytearray(1) + a.__init__() + print(list(a)) + """) + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(out.decode().strip(), repr([])) + + def test_take_bytes_all(self): + self._check("a.take_bytes()", "[]") + + def test_take_bytes_zero(self): + self._check("a.take_bytes(0)", "[]") + + def test_reinit_with_view(self): + code = textwrap.dedent(""" + a = bytearray() + v = memoryview(a) + try: + a.__init__("x", "ascii") + except BufferError: + print("PASS") + else: + print("NO EXCEPTION") + """) + rc, out, err = assert_python_ok('-c', code) + self.assertEqual(out.decode().strip(), "PASS") + + class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 696ddad8efaae55..11d7a4006af56ac 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -213,6 +213,15 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); PyByteArrayObject *obj = ((PyByteArrayObject *)self); + + /* If ob_bytes_object has not been initialized yet, eagerly initialize + it here so the following code can reason about state more easily, + and things like pointer comparisons are valid. */ + if (obj->ob_bytes_object == NULL) { + obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + bytearray_reinit_from_bytes(obj, 0, 0); + } + /* All computations are done unsigned to avoid integer overflows (see issue #22335). */ size_t alloc = (size_t) obj->ob_alloc; @@ -236,6 +245,15 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) return -1; } + /* When resizing to 0, always reset to the empty-bytes constant to avoid + complexity in the realloc path below (see gh-153419). */ + if (requested_size == 0) { + Py_XSETREF(obj->ob_bytes_object, + Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); + bytearray_reinit_from_bytes(obj, 0, 0); + return 0; + } + if (size + logical_offset <= alloc) { /* Current buffer is large enough to host the requested size, decide on a strategy. */ @@ -920,20 +938,17 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, PyObject *it; PyObject *(*iternext)(PyObject *); - /* First __init__; set ob_bytes_object so ob_bytes is always non-null. */ - if (self->ob_bytes_object == NULL) { - self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); - bytearray_reinit_from_bytes(self, 0, 0); - self->ob_exports = 0; + if (!_canresize(self)) { + return -1; } - if (Py_SIZE(self) != 0) { - /* Empty previous contents (yes, do this first of all!) */ - if (PyByteArray_Resize((PyObject *)self, 0) < 0) - return -1; + /* Empty any previous contents (do this first of all!). + Also initializes ob_bytes_object if needed */ + if (PyByteArray_Resize((PyObject *)self, 0) < 0) { + return -1; } - /* Should be caused by first init or the resize to 0. */ + /* PyByteArray_Resize(,0) should always leave the empty bytes constant */ assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES)); assert(self->ob_exports == 0); @@ -1607,6 +1622,10 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) } if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) { + /* _PyBytes_Resize has made ob_bytes_object NULL here + we need to ensure that the other byte array fields are consistent. */ + self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + bytearray_reinit_from_bytes(self, 0, 0); Py_DECREF(remaining); return NULL; } From 98888e034306dd6aefdb646eebb2b99be1065711 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sat, 11 Jul 2026 01:21:38 +0100 Subject: [PATCH 4/5] Add blurb --- .../2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst new file mode 100644 index 000000000000000..5c5b8b6c9b75963 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst @@ -0,0 +1,4 @@ +Fix several ``bytearray`` crashes caused by calling or not calling __init__ +at the expected times. As a side-effect, calling __init__ on an empty +bytearray that has an active buffer view (``memoryview`` or similar) will +now raise a ``BufferError`` From f5ea9673d7d19e72af80cd730fb2cf547edb1261 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sat, 11 Jul 2026 01:58:41 +0100 Subject: [PATCH 5/5] Comments/blurb spelling and grammar --- Lib/test/test_bytes.py | 6 +++--- .../2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst | 8 ++++---- Objects/bytearrayobject.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 3db0927ba7ba54a..c7067bc1ada1ac9 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2202,9 +2202,9 @@ class ByteArrayInitialization1Test(unittest.TestCase): # A bytearray created with __new__ so that __init__ is never called # (often as a side-effect of a subclass not calling super().__init__). # Is left with ob_bytes_object == NULL. It's easy for implementation - # code to not realise that ob_bytes_object can be NULL, so these tests + # code to not realize that ob_bytes_object can be NULL, so these tests # verify a set of code paths that have historically crashed or asserted - # (see gh-153419) + # (see gh-153419). def _check(self, stmt, expected): code = textwrap.dedent(f""" @@ -2244,7 +2244,7 @@ def test_init_bytes(self): def test_reinit_length1(self): # There is a shortcut taken when resizing, where alloc/2 < newsize. - # In this case, the existing buffer is reused, rather than reset + # In this case, the existing buffer is reused, rather than reset. # If this happens when newsize == 0 and alloc == 1, then various # code assumptions can be violated. This test should catch those # in debug builds. (see gh-153419) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst index 5c5b8b6c9b75963..d9f8309d6121671 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst @@ -1,4 +1,4 @@ -Fix several ``bytearray`` crashes caused by calling or not calling __init__ -at the expected times. As a side-effect, calling __init__ on an empty -bytearray that has an active buffer view (``memoryview`` or similar) will -now raise a ``BufferError`` +Fix several ``bytearray`` crashes caused by calling, or not calling, +``__init__`` when expected. As a side-effect, calling ``__init__`` on +an empty ``bytearray`` that has an active buffer view (``memoryview`` or +similar) now raises a ``BufferError``. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 11d7a4006af56ac..4cc736228fcef5c 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1622,8 +1622,8 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) } if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) { - /* _PyBytes_Resize has made ob_bytes_object NULL here - we need to ensure that the other byte array fields are consistent. */ + /* _PyBytes_Resize has made ob_bytes_object NULL here. We need to + ensure that the other bytearray fields are consistent. */ self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); bytearray_reinit_from_bytes(self, 0, 0); Py_DECREF(remaining);