diff -r 224aa49f22f1 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Mon Oct 21 12:03:09 2013 +0200 +++ b/Lib/test/test_dict.py Mon Oct 21 16:27:13 2013 +0300 @@ -414,12 +414,25 @@ self.assertRaises(Exc, d.pop, x) def test_mutating_iteration(self): - # changing dict size during iteration - d = {} - d[1] = 1 + # changing dict during iteration + d = {i: i for i in range(100)} with self.assertRaises(RuntimeError): for i in d: - d[i+1] = 1 + d[i + 100] = i + 100 + del d[i] + + d = {i: i for i in range(100)} + with self.assertRaises(RuntimeError): + for i, j in d.items(): + d[i + 100] = i + 100 + del d[i] + + d = {i: i for i in range(100)} + with self.assertRaises(RuntimeError): + for i in d.values(): + d[i + 100] = i + 100 + del d[i] + def test_mutating_lookup(self): # changing dict during a lookup (issue #14417) diff -r 224aa49f22f1 Objects/dictobject.c --- a/Objects/dictobject.c Mon Oct 21 12:03:09 2013 +0200 +++ b/Objects/dictobject.c Mon Oct 21 16:27:13 2013 +0300 @@ -84,6 +84,7 @@ Py_ssize_t dk_size; dict_lookup_func dk_lookup; Py_ssize_t dk_usable; + size_t dk_count; PyDictKeyEntry dk_entries[1]; }; @@ -317,6 +318,7 @@ 1, /* dk_size */ lookdict_split, /* dk_lookup */ 0, /* dk_usable (immutable) */ + 0, /* dk_count (immutable) */ { { 0, 0, 0 } /* dk_entries (empty) */ } @@ -341,6 +343,7 @@ return NULL; } DK_DEBUG_INCREF dk->dk_refcnt = 1; + dk->dk_count = 0; dk->dk_size = size; dk->dk_usable = USABLE_FRACTION(size); ep0 = &dk->dk_entries[0]; @@ -831,12 +834,14 @@ } mp->ma_keys->dk_usable--; assert(mp->ma_keys->dk_usable >= 0); + mp->ma_keys->dk_count++; ep->me_key = key; ep->me_hash = hash; } else { if (ep->me_key == dummy) { Py_INCREF(key); + mp->ma_keys->dk_count++; ep->me_key = key; ep->me_hash = hash; Py_DECREF(dummy); @@ -925,6 +930,7 @@ mp->ma_keys = oldkeys; return -1; } + mp->ma_keys->dk_count = oldkeys->dk_count + 1; if (oldkeys->dk_lookup == lookdict) mp->ma_keys->dk_lookup = lookdict; oldsize = DK_SIZE(oldkeys); @@ -956,6 +962,7 @@ } } mp->ma_keys->dk_usable -= mp->ma_used; + mp->ma_keys->dk_count++; if (oldvalues != NULL) { /* NULL out me_value slot in oldkeys, in case it was shared */ for (i = 0; i < oldsize; i++) @@ -1237,6 +1244,7 @@ ENSURE_ALLOWS_DELETIONS(mp); old_key = ep->me_key; Py_INCREF(dummy); + mp->ma_keys->dk_count++; ep->me_key = dummy; Py_DECREF(old_key); } @@ -2268,6 +2276,7 @@ *value_addr = defaultobj; val = defaultobj; mp->ma_keys->dk_usable--; + mp->ma_keys->dk_count++; mp->ma_used++; } return val; @@ -2335,6 +2344,7 @@ mp->ma_used--; if (!_PyDict_HasSplitTable(mp)) { ENSURE_ALLOWS_DELETIONS(mp); + mp->ma_keys->dk_count++; old_key = ep->me_key; Py_INCREF(dummy); ep->me_key = dummy; @@ -2403,6 +2413,7 @@ PyTuple_SET_ITEM(res, 0, ep->me_key); PyTuple_SET_ITEM(res, 1, ep->me_value); Py_INCREF(dummy); + mp->ma_keys->dk_count++; ep->me_key = dummy; ep->me_value = NULL; mp->ma_used--; @@ -2753,7 +2764,7 @@ typedef struct { PyObject_HEAD PyDictObject *di_dict; /* Set to NULL when iterator is exhausted */ - Py_ssize_t di_used; + size_t di_count; Py_ssize_t di_pos; PyObject* di_result; /* reusable result tuple for iteritems */ Py_ssize_t len; @@ -2768,7 +2779,7 @@ return NULL; Py_INCREF(dict); di->di_dict = dict; - di->di_used = dict->ma_used; + di->di_count = dict->ma_keys->dk_count; di->di_pos = 0; di->len = dict->ma_used; if (itertype == &PyDictIterItem_Type) { @@ -2804,7 +2815,7 @@ dictiter_len(dictiterobject *di) { Py_ssize_t len = 0; - if (di->di_dict != NULL && di->di_used == di->di_dict->ma_used) + if (di->di_dict != NULL && di->di_count == di->di_dict->ma_keys->dk_count) len = di->len; return PyLong_FromSize_t(len); } @@ -2837,10 +2848,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_keys->dk_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_keys->dk_count - 1; /* Make this state sticky */ return NULL; } @@ -2919,10 +2930,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_keys->dk_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_keys->dk_count - 1; /* Make this state sticky */ return NULL; } @@ -3000,10 +3011,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_keys->dk_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_keys->dk_count - 1; /* Make this state sticky */ return NULL; }