From 4fabf02633f7f537a8318a7541eec02cb3338a0d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jun 2015 00:09:56 -0600 Subject: [PATCH] Issue #24369: Defend against key-changes during iteration. --- Lib/test/test_collections.py | 17 +++++++++++++++++ Misc/NEWS | 2 ++ Objects/odictobject.c | 32 +++++++++++++++++++------------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index eaceb97af3d..ab2b733b037 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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 diff --git a/Misc/NEWS b/Misc/NEWS index c4bfa701e5e..6367afd7fd5 100644 --- 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? ================================== diff --git a/Objects/odictobject.c b/Objects/odictobject.c index 55055ac0999..313b21ae2d5 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -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);