Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix several crashes relating to calling (or not calling)
``bytearray.__init__`` in unusual ways.
41 changes: 28 additions & 13 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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__

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
};
Expand Down
Loading