Crash report
What happened?
The debug assert is triggered by:
For the refleak, we need:
b = bytearray((1, ))
b.__init__("x", "ascii")
But this hits the above assert before leaking the ref, and in a non-debug build, you leak a 1-byte Bytes object each time, so detecting this is hard without custom c hooks.
The issue is that bytearray___init___impl, if ob_bytes_object is not NULL, calls PyByteArray_Resize((PyObject *)self, 0) to reset the backing bytes object:
|
if (Py_SIZE(self) != 0) { |
|
/* Empty previous contents (yes, do this first of all!) */ |
|
if (PyByteArray_Resize((PyObject *)self, 0) < 0) |
|
return -1; |
|
} |
assuming that this resets the bytes object to be the
Py_CONSTANT_EMPTY_BYTES object by virtue of being resized to 0. BUT:
PyByteArray_Resize has a fast path for when the requested size < the allocated size, but >= alloc / 2
(when alloc == 1, then 0 >= alloc / 2):
|
if (size + logical_offset <= alloc) { |
|
/* Current buffer is large enough to host the requested size, |
|
decide on a strategy. */ |
|
if (size < alloc / 2) { |
|
/* Major downsize; resize down to exact size */ |
|
alloc = size; |
|
} |
|
else { |
|
/* Minor downsize; quick exit */ |
|
Py_SET_SIZE(self, size); |
|
/* Add mid-buffer null; end provided by bytes. */ |
|
PyByteArray_AS_STRING(self)[size] = '\0'; /* Trailing null */ |
|
return 0; |
|
} |
|
} |
In this case, the underlying bytes object is re-used with size set to 0, which is not the same as Py_CONSTANT_EMPTY_BYTES.
However, bytearray___init___impl immediately then asserts:
`assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES));
which causes the above issue.
For the refleak, we have to continue a bit further down this function, because we're left with a usable, but empty bytes object, which is fine as-is, but if a unicode object is passed in, there's another fast path for when the string is encoded:
|
if (_PyObject_IsUniquelyReferenced(encoded) |
|
&& PyBytes_CheckExact(encoded)) |
|
{ |
|
Py_ssize_t size = Py_SIZE(encoded); |
|
self->ob_bytes_object = encoded; |
This unilaterally overwrites the ob_bytes_object which leaks the above bytes object reference. (Code assumes that the ob_bytes_object is the immortal Py_CONSTANT_EMPTY_BYTES, so this would be safe if the assert were true).
I'm not sure of the best fix here, in some ways, none of this is very important, because leaking tiny 1-byte Py_Bytes objects when you call __init__ on an object is pretty minor. and the resize fast path seems reasonable, it would be nice if there were a more explicit/simple bytearray_empty() or _clear() method that could be used with a simpler implementation, or we just remove the assert and decref the ob_bytes_object in the case where a unicode object is passed in?
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a0 (heads/main-dirty:f932a74, Jul 8 2026, 14:33:20) [Clang 22.1.6 ]
Linked PRs
Crash report
What happened?
The debug assert is triggered by:
For the refleak, we need:
But this hits the above assert before leaking the ref, and in a non-debug build, you leak a 1-byte Bytes object each time, so detecting this is hard without custom c hooks.
The issue is that
bytearray___init___impl, ifob_bytes_objectis not NULL, callsPyByteArray_Resize((PyObject *)self, 0)to reset the backing bytes object:cpython/Objects/bytearrayobject.c
Lines 930 to 934 in cd9994e
assuming that this resets the bytes object to be the
Py_CONSTANT_EMPTY_BYTESobject by virtue of being resized to 0. BUT:PyByteArray_Resizehas a fast path for when the requested size<theallocated size, but>= alloc / 2(when
alloc == 1, then0 >= alloc / 2):cpython/Objects/bytearrayobject.c
Lines 239 to 253 in cd9994e
In this case, the underlying bytes object is re-used with size set to 0, which is not the same as
Py_CONSTANT_EMPTY_BYTES.However,
bytearray___init___implimmediately then asserts:`assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES));
which causes the above issue.
For the refleak, we have to continue a bit further down this function, because we're left with a usable, but empty bytes object, which is fine as-is, but if a unicode object is passed in, there's another fast path for when the string is encoded:
cpython/Objects/bytearrayobject.c
Lines 967 to 971 in cd9994e
This unilaterally overwrites the
ob_bytes_objectwhich leaks the above bytes object reference. (Code assumes that theob_bytes_objectis the immortalPy_CONSTANT_EMPTY_BYTES, so this would be safe if the assert were true).I'm not sure of the best fix here, in some ways, none of this is very important, because leaking tiny 1-byte Py_Bytes objects when you call
__init__on an object is pretty minor. and the resize fast path seems reasonable, it would be nice if there were a more explicit/simple bytearray_empty() or _clear() method that could be used with a simpler implementation, or we just remove the assert and decref the ob_bytes_object in the case where a unicode object is passed in?CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a0 (heads/main-dirty:f932a74, Jul 8 2026, 14:33:20) [Clang 22.1.6 ]
Linked PRs