From c961d61e9ad5635939f59e869f7bee9df4c162e3 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 10 Jul 2026 10:55:48 +0100 Subject: [PATCH 01/13] 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 e211c3d15a4ed2..b10d8aa0503cac 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 00000000000000..8f032029ca933e --- /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 696ddad8efaae5..98881731755aed 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 02/13] 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 b10d8aa0503cac..e211c3d15a4ed2 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 8f032029ca933e..00000000000000 --- 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 98881731755aed..696ddad8efaae5 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 03/13] 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 e211c3d15a4ed2..3db0927ba7ba54 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 696ddad8efaae5..11d7a4006af56a 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 04/13] 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 00000000000000..5c5b8b6c9b7596 --- /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 05/13] 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 3db0927ba7ba54..c7067bc1ada1ac 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 5c5b8b6c9b7596..d9f8309d612167 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 11d7a4006af56a..4cc736228fcef5 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); From c57fe6b612805b60843b5b3825ae9760c8d75ce5 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sat, 11 Jul 2026 20:00:22 +0100 Subject: [PATCH 06/13] Update tests to just run inline, and fold many of the tests into one. Format blurb correctly, and reduce the chatter (There is a user-visible behaviour change, but I guess it's so minor it's not worth mentioning) --- Lib/test/test_bytes.py | 105 ++++++------------ ...-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst | 5 +- 2 files changed, 36 insertions(+), 74 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index c7067bc1ada1ac..22d0b2e96eb2db 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, assert_python_ok +from test.support.script_helper import assert_python_failure if sys.flags.bytes_warning: @@ -2197,50 +2197,33 @@ 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 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). - - 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_uninitialized_instance(self): + # 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 realize that ob_bytes_object can be NULL, so these + # checks exercise code paths that have historically crashed or + # asserted (see gh-153419). + def uninitialized(): + return bytearray.__new__(bytearray) + + uninitialized().insert(0, 1) + uninitialized().extend(b"x") + uninitialized().extend([1, 2, 3]) + uninitialized().resize(4) + uninitialized().__init__(5) + uninitialized().__init__(b"xyz") + uninitialized().take_bytes() + uninitialized().take_bytes(0) + + a = uninitialized() + a.append(1) + + a = uninitialized() + a += b"x" + + a = uninitialized() + a[:] = b"xyz" def test_reinit_length1(self): # There is a shortcut taken when resizing, where alloc/2 < newsize. @@ -2248,33 +2231,15 @@ def test_reinit_length1(self): # 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)", "[]") + a = bytearray(1) + a.__init__() + self.assertEqual(a, b"") 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") + a = bytearray() + with memoryview(a): + self.assertRaises(BufferError, a.__init__, "x", "ascii") + self.assertEqual(a, b"") class AssortedBytesTest(unittest.TestCase): 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 d9f8309d612167..d0d8562e4ffb7d 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 @@ -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``. +Fix several :class:`bytearray` crashes caused by calling :meth:`~object.__init__`/:meth:`~object.__new__` in unusual ways. From b03da26b4400f6d086dbd633dd89822b23478a74 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sun, 19 Jul 2026 23:18:24 +0100 Subject: [PATCH 07/13] Revert to tp_new approach --- Lib/test/test_bytes.py | 6 ++---- Objects/bytearrayobject.c | 28 ++++++++++++++++------------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 22d0b2e96eb2db..73e80cee8528ca 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2200,10 +2200,8 @@ def __len__(self): def test_uninitialized_instance(self): # 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 realize that ob_bytes_object can be NULL, so these - # checks exercise code paths that have historically crashed or - # asserted (see gh-153419). + # must be left in a state where methods called on it do not crash or + # assert (see gh-153419). def uninitialized(): return bytearray.__new__(bytearray) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 4cc736228fcef5..b7d92f177996bd 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -214,13 +214,7 @@ 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); - } + assert(obj->ob_bytes_object != NULL); /* All computations are done unsigned to avoid integer overflows (see issue #22335). */ @@ -920,6 +914,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__ @@ -942,8 +949,7 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, return -1; } - /* Empty any previous contents (do this first of all!). - Also initializes ob_bytes_object if needed */ + /* Empty any previous contents (do this first of all!). */ if (PyByteArray_Resize((PyObject *)self, 0) < 0) { return -1; } @@ -1622,8 +1628,6 @@ 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 bytearray fields are consistent. */ self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); bytearray_reinit_from_bytes(self, 0, 0); Py_DECREF(remaining); @@ -2956,7 +2960,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 689a52d1a039c935331425a1dcd24169775a6f23 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sun, 19 Jul 2026 23:33:49 +0100 Subject: [PATCH 08/13] Tidy up some comments --- Objects/bytearrayobject.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index b7d92f177996bd..c9e1b48754dc19 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -239,8 +239,7 @@ 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). */ + /* resize to 0 resets to empty bytes (see issue #153419)*/ if (requested_size == 0) { Py_XSETREF(obj->ob_bytes_object, Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); @@ -954,7 +953,6 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, return -1; } - /* 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); From 9ee14d493529cbfcea60a990c33202f6f2389021 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Sun, 19 Jul 2026 23:48:18 +0100 Subject: [PATCH 09/13] Reset to Py_SETREF on resize path now we guarantee ob_bytes_object is not NULL --- Objects/bytearrayobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index c9e1b48754dc19..ecd94c7fa519bb 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -241,7 +241,7 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) /* resize to 0 resets to empty bytes (see issue #153419)*/ if (requested_size == 0) { - Py_XSETREF(obj->ob_bytes_object, + Py_SETREF(obj->ob_bytes_object, Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); bytearray_reinit_from_bytes(obj, 0, 0); return 0; From 6f73d57128c73ca1ce914e4f4756f7c024610bd9 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Mon, 20 Jul 2026 11:50:10 +0100 Subject: [PATCH 10/13] Review comments --- Lib/test/test_bytes.py | 10 ++++------ .../2026-07-11-01-17-57.gh-issue-153419.U8HJOJ.rst | 2 +- Objects/bytearrayobject.c | 3 +++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 73e80cee8528ca..8f089154cbe6d3 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2197,11 +2197,9 @@ def __len__(self): self.assertRaises(BufferError, ba.hex, S(b':')) - def test_uninitialized_instance(self): - # A bytearray created with __new__ so that __init__ is never called - # (often as a side-effect of a subclass not calling super().__init__) - # must be left in a state where methods called on it do not crash or - # assert (see gh-153419). + def test_no_init_called(self): + # A bytearray created without calling bytearray.__init__ + # should not crash the interpreter (see gh-153419). def uninitialized(): return bytearray.__new__(bytearray) @@ -2223,7 +2221,7 @@ def uninitialized(): a = uninitialized() a[:] = b"xyz" - def test_reinit_length1(self): + def test_reinit_length(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 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 d0d8562e4ffb7d..4459ec835395ea 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 +1 @@ -Fix several :class:`bytearray` crashes caused by calling :meth:`~object.__init__`/:meth:`~object.__new__` in unusual ways. +Fix multiple :class:`bytearray` crashes and reference leaks caused by skipping :meth:`~object.__init__` and broken state setup code. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index ecd94c7fa519bb..07bad99ccac6c2 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -923,6 +923,7 @@ bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) PyByteArrayObject *obj = _PyByteArray_CAST(self); obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); bytearray_reinit_from_bytes(obj, 0, 0); + obj->ob_exports = 0; return self; } @@ -944,6 +945,8 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, PyObject *it; PyObject *(*iternext)(PyObject *); + /* Disallow any __init__ call if the object is not resizable (has exports) + to make the handling of non-null `source` init values simpler. */ if (!_canresize(self)) { return -1; } From 0d64e2b081d1b9643e007ea9e11f8a70397f3d32 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 22 Jul 2026 10:44:10 +0100 Subject: [PATCH 11/13] Update Objects/bytearrayobject.c Fix capitalization in comment Co-authored-by: Cody Maloney --- Objects/bytearrayobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 07bad99ccac6c2..2ad4d01cd4f277 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -239,7 +239,7 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size) return -1; } - /* resize to 0 resets to empty bytes (see issue #153419)*/ + /* Resize to 0 resets to empty bytes (see issue #153419). */ if (requested_size == 0) { Py_SETREF(obj->ob_bytes_object, Py_GetConstant(Py_CONSTANT_EMPTY_BYTES)); From ecd594112b8393f444e1b21e094726167a858207 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 24 Jul 2026 15:25:40 +0100 Subject: [PATCH 12/13] Review feedback: - Add assert to bytes object resize (ensure we're not resizing the empty constant) - Test helper rename - Flip obj<->self usage in bytearray_new - Assert PyBytes_Resize failure sets ob_bytes_object to NULL --- Lib/test/test_bytes.py | 24 ++++++++++++------------ Objects/bytearrayobject.c | 16 ++++++++-------- Objects/bytesobject.c | 1 + 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 8f089154cbe6d3..e3e703fef1eb5d 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2200,25 +2200,25 @@ def __len__(self): def test_no_init_called(self): # A bytearray created without calling bytearray.__init__ # should not crash the interpreter (see gh-153419). - def uninitialized(): + def bytearray_new(): return bytearray.__new__(bytearray) - uninitialized().insert(0, 1) - uninitialized().extend(b"x") - uninitialized().extend([1, 2, 3]) - uninitialized().resize(4) - uninitialized().__init__(5) - uninitialized().__init__(b"xyz") - uninitialized().take_bytes() - uninitialized().take_bytes(0) + bytearray_new().insert(0, 1) + bytearray_new().extend(b"x") + bytearray_new().extend([1, 2, 3]) + bytearray_new().resize(4) + bytearray_new().__init__(5) + bytearray_new().__init__(b"xyz") + bytearray_new().take_bytes() + bytearray_new().take_bytes(0) - a = uninitialized() + a = bytearray_new() a.append(1) - a = uninitialized() + a = bytearray_new() a += b"x" - a = uninitialized() + a = bytearray_new() a[:] = b"xyz" def test_reinit_length(self): diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 2ad4d01cd4f277..28a241d37d6b7b 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -916,15 +916,15 @@ bytearray_ass_subscript(PyObject *op, PyObject *index, PyObject *values) static PyObject * bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - PyObject *self = PyType_GenericNew(type, args, kwds); - if (self == NULL) { + PyObject *obj = PyType_GenericNew(type, args, kwds); + if (obj == 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); - obj->ob_exports = 0; - return self; + PyByteArrayObject *self = _PyByteArray_CAST(obj); + self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + bytearray_reinit_from_bytes(self, 0, 0); + self->ob_exports = 0; + return obj; } /*[clinic input] @@ -955,7 +955,6 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, if (PyByteArray_Resize((PyObject *)self, 0) < 0) { return -1; } - assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES)); assert(self->ob_exports == 0); @@ -1629,6 +1628,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) } 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); Py_DECREF(remaining); diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index f63185e14284b1..ef35dad82e8aae 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3380,6 +3380,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) Py_DECREF(v); return (*pv == NULL) ? -1 : 0; } + assert(v != bytes_get_empty()); #ifdef Py_TRACE_REFS _Py_ForgetReference(v); From 072bc0452f8b8cea6ea707cd24a1cf7fe2d3d9a3 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 24 Jul 2026 15:52:23 +0100 Subject: [PATCH 13/13] Fix some sloppy errors, using correct '->' operator, and mis-reading vstinner's suggestion. --- Objects/bytearrayobject.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 28a241d37d6b7b..ca1cf0beeb7d9f 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -916,15 +916,15 @@ bytearray_ass_subscript(PyObject *op, PyObject *index, PyObject *values) static PyObject * bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - PyObject *obj = PyType_GenericNew(type, args, kwds); - if (obj == NULL) { + PyObject *op = PyType_GenericNew(type, args, kwds); + if (op == NULL) { return NULL; } - PyByteArrayObject *self = _PyByteArray_CAST(obj); + PyByteArrayObject *self = _PyByteArray_CAST(op); self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); bytearray_reinit_from_bytes(self, 0, 0); self->ob_exports = 0; - return obj; + return op; } /*[clinic input] @@ -1628,7 +1628,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n) } if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) { - assert(self.ob_bytes_object == NULL); + assert(self->ob_bytes_object == NULL); self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); bytearray_reinit_from_bytes(self, 0, 0); Py_DECREF(remaining);