Skip to content

Commit 2d79804

Browse files
GH-91153: Handle mutating __index__ methods in bytearray item assignment (GH-94891)
(cherry picked from commit f365895) Co-authored-by: Brandt Bucher <[email protected]>
1 parent 906b345 commit 2d79804

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
@@ -1698,6 +1698,23 @@ def test_repeat_after_setslice(self):
16981698
self.assertEqual(b1, b)
16991699
self.assertEqual(b3, b'xcxcxc')
17001700

1701+
def test_mutating_index(self):
1702+
class Boom:
1703+
def __index__(self):
1704+
b.clear()
1705+
return 0
1706+
1707+
with self.subTest("tp_as_mapping"):
1708+
b = bytearray(b'Now you see me...')
1709+
with self.assertRaises(IndexError):
1710+
b[0] = Boom()
1711+
1712+
with self.subTest("tp_as_sequence"):
1713+
_testcapi = import_helper.import_module('_testcapi')
1714+
b = bytearray(b'Now you see me...')
1715+
with self.assertRaises(IndexError):
1716+
_testcapi.sequence_setitem(b, 0, Boom())
1717+
17011718

17021719
class AssortedBytesTest(unittest.TestCase):
17031720
#
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
@@ -5388,6 +5388,21 @@ sequence_getitem(PyObject *self, PyObject *args)
53885388
}
53895389

53905390

5391+
static PyObject *
5392+
sequence_setitem(PyObject *self, PyObject *args)
5393+
{
5394+
Py_ssize_t i;
5395+
PyObject *seq, *val;
5396+
if (!PyArg_ParseTuple(args, "OnO", &seq, &i, &val)) {
5397+
return NULL;
5398+
}
5399+
if (PySequence_SetItem(seq, i, val)) {
5400+
return NULL;
5401+
}
5402+
Py_RETURN_NONE;
5403+
}
5404+
5405+
53915406
/* Functions for testing C calling conventions (METH_*) are named meth_*,
53925407
* e.g. "meth_varargs" for METH_VARARGS.
53935408
*
@@ -5884,6 +5899,7 @@ static PyMethodDef TestMethods[] = {
58845899
#endif
58855900
{"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
58865901
{"sequence_getitem", sequence_getitem, METH_VARARGS},
5902+
{"sequence_setitem", sequence_setitem, METH_VARARGS},
58875903
{"meth_varargs", meth_varargs, METH_VARARGS},
58885904
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS},
58895905
{"meth_o", meth_o, METH_O},

Objects/bytearrayobject.c

+25-11
Original file line numberDiff line numberDiff line change
@@ -577,22 +577,28 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
577577
static int
578578
bytearray_setitem(PyByteArrayObject *self, Py_ssize_t i, PyObject *value)
579579
{
580-
int ival;
580+
int ival = -1;
581581

582-
if (i < 0)
582+
// GH-91153: We need to do this *before* the size check, in case value has a
583+
// nasty __index__ method that changes the size of the bytearray:
584+
if (value && !_getbytevalue(value, &ival)) {
585+
return -1;
586+
}
587+
588+
if (i < 0) {
583589
i += Py_SIZE(self);
590+
}
584591

585592
if (i < 0 || i >= Py_SIZE(self)) {
586593
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
587594
return -1;
588595
}
589596

590-
if (value == NULL)
597+
if (value == NULL) {
591598
return bytearray_setslice(self, i, i+1, NULL);
599+
}
592600

593-
if (!_getbytevalue(value, &ival))
594-
return -1;
595-
601+
assert(0 <= ival && ival < 256);
596602
PyByteArray_AS_STRING(self)[i] = ival;
597603
return 0;
598604
}
@@ -607,11 +613,21 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
607613
if (_PyIndex_Check(index)) {
608614
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
609615

610-
if (i == -1 && PyErr_Occurred())
616+
if (i == -1 && PyErr_Occurred()) {
611617
return -1;
618+
}
612619

613-
if (i < 0)
620+
int ival = -1;
621+
622+
// GH-91153: We need to do this *before* the size check, in case values
623+
// has a nasty __index__ method that changes the size of the bytearray:
624+
if (values && !_getbytevalue(values, &ival)) {
625+
return -1;
626+
}
627+
628+
if (i < 0) {
614629
i += PyByteArray_GET_SIZE(self);
630+
}
615631

616632
if (i < 0 || i >= Py_SIZE(self)) {
617633
PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
@@ -626,9 +642,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
626642
slicelen = 1;
627643
}
628644
else {
629-
int ival;
630-
if (!_getbytevalue(values, &ival))
631-
return -1;
645+
assert(0 <= ival && ival < 256);
632646
buf[i] = (char)ival;
633647
return 0;
634648
}

0 commit comments

Comments
 (0)