Skip to content

Commit f365895

Browse files
authored
GH-91153: Handle mutating __index__ methods in bytearray item assignment (GH-94891)
1 parent 3f73860 commit f365895

File tree

4 files changed

+60
-11
lines changed

4 files changed

+60
-11
lines changed

Lib/test/test_bytes.py

+17
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,23 @@ def test_repeat_after_setslice(self):
17101710
self.assertEqual(b1, b)
17111711
self.assertEqual(b3, b'xcxcxc')
17121712

1713+
def test_mutating_index(self):
1714+
class Boom:
1715+
def __index__(self):
1716+
b.clear()
1717+
return 0
1718+
1719+
with self.subTest("tp_as_mapping"):
1720+
b = bytearray(b'Now you see me...')
1721+
with self.assertRaises(IndexError):
1722+
b[0] = Boom()
1723+
1724+
with self.subTest("tp_as_sequence"):
1725+
_testcapi = import_helper.import_module('_testcapi')
1726+
b = bytearray(b'Now you see me...')
1727+
with self.assertRaises(IndexError):
1728+
_testcapi.sequence_setitem(b, 0, Boom())
1729+
17131730

17141731
class AssortedBytesTest(unittest.TestCase):
17151732
#
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an issue where a :class:`bytearray` item assignment could crash if it's
2+
resized by the new value's :meth:`__index__` method.

Modules/_testcapimodule.c

+16
Original file line numberDiff line numberDiff line change
@@ -5484,6 +5484,21 @@ sequence_getitem(PyObject *self, PyObject *args)
54845484
}
54855485

54865486

5487+
static PyObject *
5488+
sequence_setitem(PyObject *self, PyObject *args)
5489+
{
5490+
Py_ssize_t i;
5491+
PyObject *seq, *val;
5492+
if (!PyArg_ParseTuple(args, "OnO", &seq, &i, &val)) {
5493+
return NULL;
5494+
}
5495+
if (PySequence_SetItem(seq, i, val)) {
5496+
return NULL;
5497+
}
5498+
Py_RETURN_NONE;
5499+
}
5500+
5501+
54875502
/* Functions for testing C calling conventions (METH_*) are named meth_*,
54885503
* e.g. "meth_varargs" for METH_VARARGS.
54895504
*
@@ -6303,6 +6318,7 @@ static PyMethodDef TestMethods[] = {
63036318
#endif
63046319
{"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
63056320
{"sequence_getitem", sequence_getitem, METH_VARARGS},
6321+
{"sequence_setitem", sequence_setitem, METH_VARARGS},
63066322
{"meth_varargs", meth_varargs, METH_VARARGS},
63076323
{"meth_varargs_keywords", _PyCFunction_CAST(meth_varargs_keywords), METH_VARARGS|METH_KEYWORDS},
63086324
{"meth_o", meth_o, METH_O},

Objects/bytearrayobject.c

+25-11
Original file line numberDiff line numberDiff line change
@@ -563,22 +563,28 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
563563
static int
564564
bytearray_setitem(PyByteArrayObject *self, Py_ssize_t i, PyObject *value)
565565
{
566-
int ival;
566+
int ival = -1;
567567

568-
if (i < 0)
568+
// GH-91153: We need to do this *before* the size check, in case value has a
569+
// nasty __index__ method that changes the size of the bytearray:
570+
if (value && !_getbytevalue(value, &ival)) {
571+
return -1;
572+
}
573+
574+
if (i < 0) {
569575
i += Py_SIZE(self);
576+
}
570577

571578
if (i < 0 || i >= Py_SIZE(self)) {
572579
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
573580
return -1;
574581
}
575582

576-
if (value == NULL)
583+
if (value == NULL) {
577584
return bytearray_setslice(self, i, i+1, NULL);
585+
}
578586

579-
if (!_getbytevalue(value, &ival))
580-
return -1;
581-
587+
assert(0 <= ival && ival < 256);
582588
PyByteArray_AS_STRING(self)[i] = ival;
583589
return 0;
584590
}
@@ -593,11 +599,21 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
593599
if (_PyIndex_Check(index)) {
594600
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
595601

596-
if (i == -1 && PyErr_Occurred())
602+
if (i == -1 && PyErr_Occurred()) {
597603
return -1;
604+
}
598605

599-
if (i < 0)
606+
int ival = -1;
607+
608+
// GH-91153: We need to do this *before* the size check, in case values
609+
// has a nasty __index__ method that changes the size of the bytearray:
610+
if (values && !_getbytevalue(values, &ival)) {
611+
return -1;
612+
}
613+
614+
if (i < 0) {
600615
i += PyByteArray_GET_SIZE(self);
616+
}
601617

602618
if (i < 0 || i >= Py_SIZE(self)) {
603619
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
@@ -612,9 +628,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
612628
slicelen = 1;
613629
}
614630
else {
615-
int ival;
616-
if (!_getbytevalue(values, &ival))
617-
return -1;
631+
assert(0 <= ival && ival < 256);
618632
buf[i] = (char)ival;
619633
return 0;
620634
}

0 commit comments

Comments
 (0)