]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #24369: Defend against key-changes during iteration.
authorEric Snow <ericsnowcurrently@gmail.com>
Thu, 4 Jun 2015 06:09:56 +0000 (00:09 -0600)
committerEric Snow <ericsnowcurrently@gmail.com>
Thu, 4 Jun 2015 06:09:56 +0000 (00:09 -0600)
Lib/test/test_collections.py
Misc/NEWS
Objects/odictobject.c

index eaceb97af3dd0ade2cebefd6fa9a7ed8f640461d..ab2b733b037f114b33caeea4e184ca7cef215742 100644 (file)
@@ -2047,6 +2047,23 @@ class CPythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
         del od[colliding]
         self.assertEqual(list(od.items()), [(key, ...), ('after', ...)])
 
+    def test_key_change_during_iteration(self):
+        OrderedDict = self.module.OrderedDict
+
+        od = OrderedDict.fromkeys('abcde')
+        self.assertEqual(list(od), list('abcde'))
+        with self.assertRaises(RuntimeError):
+            for i, k in enumerate(od):
+                od.move_to_end(k)
+                self.assertLess(i, 5)
+        with self.assertRaises(RuntimeError):
+            for k in od:
+                od['f'] = None
+        with self.assertRaises(RuntimeError):
+            for k in od:
+                del od['c']
+        self.assertEqual(list(od), list('bdeaf'))
+
     def test_issue24347(self):
         OrderedDict = self.module.OrderedDict
 
index c4bfa701e5e5389b81d674375f76a2f8ce7041f9..6367afd7fd5380cd7f5c98efdf5784432cde53be 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -27,6 +27,8 @@ Library
 
 - Issue #24377: Fix a ref leak in OrderedDict.__repr__.
 
+- Issue #24369: Defend against key-changes during iteration.
+
 
 What's New in Python 3.5.0 beta 2?
 ==================================
index 55055ac0999853072abbf731311e22ecd5d64743..313b21ae2d546b4a941c4a61d695adfdb2a25025 100644 (file)
@@ -480,20 +480,15 @@ typedef struct _odictnode _ODictNode;
 
 /* PyODictObject */
 struct _odictobject {
-    /* od_dict is the underlying dict. */
-    PyDictObject od_dict;
-    /* od_first is the first node in the odict, if any. */
-    _ODictNode *od_first;
-    /* od_last is the last node in the odict, if any. */
-    _ODictNode *od_last;
-    /* od_size is the number of entries in od_fast_nodes. */
-    Py_ssize_t od_size;  /* managed by _odict_resize() */
-    /* od_fast_nodes is a hash table that mirrors the dict table. */
+    PyDictObject od_dict;        /* the underlying dict */
+    _ODictNode *od_first;        /* first node in the linked list, if any */
+    _ODictNode *od_last;         /* last node in the linked list, if any */
+    /* od_size and od_fast_nodes are managed by _odict_resize() */
+    Py_ssize_t od_size;          /* hash table that mirrors the dict table */
     _ODictNode **od_fast_nodes;  /* managed by _odict_resize() */
-    /* od_inst_dict is OrderedDict().__dict__. */
-    PyObject *od_inst_dict;
-    /* od_weakreflist holds weakrefs to the odict. */
-    PyObject *od_weakreflist;
+    size_t od_state;             /* incremented whenever the LL changes */
+    PyObject *od_inst_dict;      /* OrderedDict().__dict__ */
+    PyObject *od_weakreflist;    /* holds weakrefs to the odict */
 };
 
 
@@ -608,6 +603,7 @@ _odict_get_index(PyODictObject *od, PyObject *key)
 static int
 _odict_initialize(PyODictObject *od)
 {
+    od->od_state = 0;
     _odict_FIRST(od) = NULL;
     _odict_LAST(od) = NULL;
     return _odict_resize((PyODictObject *)od);
@@ -642,6 +638,7 @@ _odict_add_head(PyODictObject *od, _ODictNode *node)
         _odict_FIRST(od) = node;
         _odictnode_PREV(_odict_FIRST(od)) = node;
     }
+    od->od_state++;
 }
 
 static void
@@ -659,6 +656,7 @@ _odict_add_tail(PyODictObject *od, _ODictNode *node)
         _odictnode_NEXT(_odict_LAST(od)) = node;
         _odict_LAST(od) = node;
     }
+    od->od_state++;
 }
 
 /* adds the node to the end of the list */
@@ -725,6 +723,7 @@ _odict_remove_node(PyODictObject *od, _ODictNode *node)
 
     _odictnode_PREV(node) = NULL;
     _odictnode_NEXT(node) = NULL;
+    od->od_state++;
 }
 
 static _ODictNode *
@@ -1829,6 +1828,7 @@ typedef struct {
     int kind;
     PyODictObject *di_odict;
     Py_ssize_t di_size;
+    size_t di_state;
     PyObject *di_current;
     PyObject *di_result; /* reusable result tuple for iteritems */
 } odictiterobject;
@@ -1869,6 +1869,11 @@ odictiter_nextkey(odictiterobject *di)
         goto done;  /* We're already done. */
 
     /* Check for unsupported changes. */
+    if (di->di_odict->od_state != di->di_state) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "OrderedDict mutated during iteration");
+        goto done;
+    }
     if (di->di_size != PyODict_SIZE(di->di_odict)) {
         PyErr_SetString(PyExc_RuntimeError,
                         "OrderedDict changed size during iteration");
@@ -2075,6 +2080,7 @@ odictiter_new(PyODictObject *od, int kind)
     di->di_current = node ? _odictnode_KEY(node) : NULL;
     Py_XINCREF(di->di_current);
     di->di_size = PyODict_SIZE(od);
+    di->di_state = od->od_state;
     di->di_odict = od;
     Py_INCREF(od);