GH-91153: Handle mutating __index__ methods in bytearray item assignment (GH-94891)

(cherry picked from commit f36589510b)

Co-authored-by: Brandt Bucher <brandtbucher@microsoft.com>
This commit is contained in:
Miss Islington (bot) 2022-07-19 10:12:39 -07:00 committed by GitHub
parent d2be44230e
commit 9487e8d250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 11 deletions

View file

@ -1710,6 +1710,23 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
self.assertEqual(b1, b) self.assertEqual(b1, b)
self.assertEqual(b3, b'xcxcxc') self.assertEqual(b3, b'xcxcxc')
def test_mutating_index(self):
class Boom:
def __index__(self):
b.clear()
return 0
with self.subTest("tp_as_mapping"):
b = bytearray(b'Now you see me...')
with self.assertRaises(IndexError):
b[0] = Boom()
with self.subTest("tp_as_sequence"):
_testcapi = import_helper.import_module('_testcapi')
b = bytearray(b'Now you see me...')
with self.assertRaises(IndexError):
_testcapi.sequence_setitem(b, 0, Boom())
class AssortedBytesTest(unittest.TestCase): class AssortedBytesTest(unittest.TestCase):
# #

View file

@ -0,0 +1,2 @@
Fix an issue where a :class:`bytearray` item assignment could crash if it's
resized by the new value's :meth:`__index__` method.

View file

@ -5489,6 +5489,21 @@ sequence_getitem(PyObject *self, PyObject *args)
} }
static PyObject *
sequence_setitem(PyObject *self, PyObject *args)
{
Py_ssize_t i;
PyObject *seq, *val;
if (!PyArg_ParseTuple(args, "OnO", &seq, &i, &val)) {
return NULL;
}
if (PySequence_SetItem(seq, i, val)) {
return NULL;
}
Py_RETURN_NONE;
}
/* Functions for testing C calling conventions (METH_*) are named meth_*, /* Functions for testing C calling conventions (METH_*) are named meth_*,
* e.g. "meth_varargs" for METH_VARARGS. * e.g. "meth_varargs" for METH_VARARGS.
* *
@ -6272,6 +6287,7 @@ static PyMethodDef TestMethods[] = {
#endif #endif
{"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS}, {"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
{"sequence_getitem", sequence_getitem, METH_VARARGS}, {"sequence_getitem", sequence_getitem, METH_VARARGS},
{"sequence_setitem", sequence_setitem, METH_VARARGS},
{"meth_varargs", meth_varargs, METH_VARARGS}, {"meth_varargs", meth_varargs, METH_VARARGS},
{"meth_varargs_keywords", _PyCFunction_CAST(meth_varargs_keywords), METH_VARARGS|METH_KEYWORDS}, {"meth_varargs_keywords", _PyCFunction_CAST(meth_varargs_keywords), METH_VARARGS|METH_KEYWORDS},
{"meth_o", meth_o, METH_O}, {"meth_o", meth_o, METH_O},

View file

@ -563,22 +563,28 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
static int static int
bytearray_setitem(PyByteArrayObject *self, Py_ssize_t i, PyObject *value) bytearray_setitem(PyByteArrayObject *self, Py_ssize_t i, PyObject *value)
{ {
int ival; int ival = -1;
if (i < 0) // GH-91153: We need to do this *before* the size check, in case value has a
// nasty __index__ method that changes the size of the bytearray:
if (value && !_getbytevalue(value, &ival)) {
return -1;
}
if (i < 0) {
i += Py_SIZE(self); i += Py_SIZE(self);
}
if (i < 0 || i >= Py_SIZE(self)) { if (i < 0 || i >= Py_SIZE(self)) {
PyErr_SetString(PyExc_IndexError, "bytearray index out of range"); PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
return -1; return -1;
} }
if (value == NULL) if (value == NULL) {
return bytearray_setslice(self, i, i+1, NULL); return bytearray_setslice(self, i, i+1, NULL);
}
if (!_getbytevalue(value, &ival)) assert(0 <= ival && ival < 256);
return -1;
PyByteArray_AS_STRING(self)[i] = ival; PyByteArray_AS_STRING(self)[i] = ival;
return 0; return 0;
} }
@ -593,11 +599,21 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
if (_PyIndex_Check(index)) { if (_PyIndex_Check(index)) {
Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError); Py_ssize_t i = PyNumber_AsSsize_t(index, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) if (i == -1 && PyErr_Occurred()) {
return -1; return -1;
}
if (i < 0) int ival = -1;
// GH-91153: We need to do this *before* the size check, in case values
// has a nasty __index__ method that changes the size of the bytearray:
if (values && !_getbytevalue(values, &ival)) {
return -1;
}
if (i < 0) {
i += PyByteArray_GET_SIZE(self); i += PyByteArray_GET_SIZE(self);
}
if (i < 0 || i >= Py_SIZE(self)) { if (i < 0 || i >= Py_SIZE(self)) {
PyErr_SetString(PyExc_IndexError, "bytearray index out of range"); PyErr_SetString(PyExc_IndexError, "bytearray index out of range");
@ -612,9 +628,7 @@ bytearray_ass_subscript(PyByteArrayObject *self, PyObject *index, PyObject *valu
slicelen = 1; slicelen = 1;
} }
else { else {
int ival; assert(0 <= ival && ival < 256);
if (!_getbytevalue(values, &ival))
return -1;
buf[i] = (char)ival; buf[i] = (char)ival;
return 0; return 0;
} }