diff -r ea91bb92c28b Lib/test/test_dict.py --- a/Lib/test/test_dict.py Tue Nov 15 21:21:35 2016 -0500 +++ b/Lib/test/test_dict.py Wed Nov 16 11:40:51 2016 +0000 @@ -690,6 +690,38 @@ class DictTest(unittest.TestCase): test_support.check_free_after_iterating(self, lambda d: iter(d.viewvalues()), dict) test_support.check_free_after_iterating(self, lambda d: iter(d.viewitems()), dict) + def test_init_use_after_free(self): + class X: + def __hash__(self): + pair[:] = [] + return 13 + + pair = [X(), 123] + dict([pair]) + + def test_dictitems_contains_use_after_free(self): + class X: + def __eq__(self, other): + d.clear() + return NotImplemented + + d = {0: set()} + (0, X()) in d.items() + + def test_oob_indexing_dictiter_iternextitem(self): + class X(int): + def __del__(self): + d.clear() + + d = {i: X(i) for i in range(8)} + + def iter_and_mutate(): + for result in d.items(): + if result[0] == 2: + d[2] = None # free d[2] --> X(2).__del__ was called + + self.assertRaises(RuntimeError, iter_and_mutate) + from test import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): diff -r ea91bb92c28b Objects/dictobject.c --- a/Objects/dictobject.c Tue Nov 15 21:21:35 2016 -0500 +++ b/Objects/dictobject.c Wed Nov 16 11:40:51 2016 +0000 @@ -1550,11 +1550,18 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje /* Update/merge with this (key, value) pair. */ key = PySequence_Fast_GET_ITEM(fast, 0); value = PySequence_Fast_GET_ITEM(fast, 1); + Py_INCREF(key); + Py_INCREF(value); if (override || PyDict_GetItem(d, key) == NULL) { int status = PyDict_SetItem(d, key, value); - if (status < 0) + if (status < 0) { + Py_DECREF(key); + Py_DECREF(value); goto Fail; + } } + Py_DECREF(key); + Py_DECREF(value); Py_DECREF(fast); Py_DECREF(item); } @@ -2707,12 +2714,8 @@ static PyObject *dictiter_iternextitem(d return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { - PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ - return NULL; - } + if (di->di_used != d->ma_used) + goto mutated; i = di->di_pos; if (i < 0) @@ -2729,6 +2732,14 @@ static PyObject *dictiter_iternextitem(d Py_INCREF(result); Py_DECREF(PyTuple_GET_ITEM(result, 0)); Py_DECREF(PyTuple_GET_ITEM(result, 1)); + + /* note that above may have mutated the dictionary (see #27945) */ + if (di->di_used != d->ma_used) { + PyTuple_SET_ITEM(result, 0, NULL); + PyTuple_SET_ITEM(result, 1, NULL); + Py_DECREF(result); + goto mutated; + } } else { result = PyTuple_New(2); if (result == NULL) @@ -2743,6 +2754,12 @@ static PyObject *dictiter_iternextitem(d PyTuple_SET_ITEM(result, 1, value); return result; +mutated: + PyErr_SetString(PyExc_RuntimeError, + "dictionary changed size during iteration"); + di->di_used = -1; /* Make this state sticky */ + return NULL; + fail: di->di_dict = NULL; Py_DECREF(d); @@ -3141,6 +3158,7 @@ dictitems_iter(dictviewobject *dv) static int dictitems_contains(dictviewobject *dv, PyObject *obj) { + int result; PyObject *key, *value, *found; if (dv->dv_dict == NULL) return 0; @@ -3154,7 +3172,10 @@ dictitems_contains(dictviewobject *dv, P return -1; return 0; } - return PyObject_RichCompareBool(value, found, Py_EQ); + Py_INCREF(found); + result = PyObject_RichCompareBool(value, found, Py_EQ); + Py_DECREF(found); + return result; } static PySequenceMethods dictitems_as_sequence = {