Skip to content
25 changes: 25 additions & 0 deletions Lib/test/test_numeric_tower.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,30 @@ def test_complex(self):
self.assertRaises(TypeError, op, v, z)


class IndexMutationDuringNumericOpTest(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it in the numeric tower??? it has nothing to do with numeric tower.

def test_index_mutates_lhs_type_during_operation(self):
import array

class Good(array.array):
pass

class Hide(type):
def mro(cls):
return (cls, object)

class Bad(Good, metaclass=Hide):
pass

arr = Good('b', b'x')

class Count:
def __index__(self):
arr.__class__ = Bad
return 2

with self.assertRaises(TypeError):
arr * Count()


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed a crash during array multiplication when ``__index__`` changes the
array’s class while the operation is in progress.
17 changes: 15 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -9714,13 +9714,26 @@ wrap_indexargfunc(PyObject *self, PyObject *args, void *wrapped)
ssizeargfunc func = (ssizeargfunc)wrapped;
PyObject* o;
Py_ssize_t i;
PyTypeObject *type_before = Py_TYPE(self);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very sure about this, but I'm wondering should we increase the refcount for type_before, because we will call user's code next, thus it may be GCed (like user delete the type in __index__)?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. Since we call user code next, the type could indeed be GCed if its last reference is dropped. I’ve updated the code to INCREF type_before before calling user code and DECREF it on all exit paths. I also ran the regression test locally and it passes.

Py_INCREF(type_before);

if (!check_num_args(args, 1))
if (!check_num_args(args, 1)){
Py_DECREF(type_before);
return NULL;
}
o = PyTuple_GET_ITEM(args, 0);
i = PyNumber_AsSsize_t(o, PyExc_OverflowError);
if (i == -1 && PyErr_Occurred())
if (i == -1 && PyErr_Occurred()){
Py_DECREF(type_before);
return NULL;
}
if (Py_TYPE(self) != type_before) {
Py_DECREF(type_before);
PyErr_SetString(PyExc_TypeError,
"object mutated during numeric operation");
return NULL;
}
Py_DECREF(type_before);
return (*func)(self, i);
}

Expand Down
Loading