Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4fabf02

Browse files
Issue #24369: Defend against key-changes during iteration.
1 parent b6c6a4d commit 4fabf02

3 files changed

Lines changed: 38 additions & 13 deletions

File tree

Lib/test/test_collections.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,6 +2047,23 @@ def blocking_hash(hash):
20472047
del od[colliding]
20482048
self.assertEqual(list(od.items()), [(key, ...), ('after', ...)])
20492049

2050+
def test_key_change_during_iteration(self):
2051+
OrderedDict = self.module.OrderedDict
2052+
2053+
od = OrderedDict.fromkeys('abcde')
2054+
self.assertEqual(list(od), list('abcde'))
2055+
with self.assertRaises(RuntimeError):
2056+
for i, k in enumerate(od):
2057+
od.move_to_end(k)
2058+
self.assertLess(i, 5)
2059+
with self.assertRaises(RuntimeError):
2060+
for k in od:
2061+
od['f'] = None
2062+
with self.assertRaises(RuntimeError):
2063+
for k in od:
2064+
del od['c']
2065+
self.assertEqual(list(od), list('bdeaf'))
2066+
20502067
def test_issue24347(self):
20512068
OrderedDict = self.module.OrderedDict
20522069

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Library
2727

2828
- Issue #24377: Fix a ref leak in OrderedDict.__repr__.
2929

30+
- Issue #24369: Defend against key-changes during iteration.
31+
3032

3133
What's New in Python 3.5.0 beta 2?
3234
==================================

Objects/odictobject.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -480,20 +480,15 @@ typedef struct _odictnode _ODictNode;
480480

481481
/* PyODictObject */
482482
struct _odictobject {
483-
/* od_dict is the underlying dict. */
484-
PyDictObject od_dict;
485-
/* od_first is the first node in the odict, if any. */
486-
_ODictNode *od_first;
487-
/* od_last is the last node in the odict, if any. */
488-
_ODictNode *od_last;
489-
/* od_size is the number of entries in od_fast_nodes. */
490-
Py_ssize_t od_size; /* managed by _odict_resize() */
491-
/* od_fast_nodes is a hash table that mirrors the dict table. */
483+
PyDictObject od_dict; /* the underlying dict */
484+
_ODictNode *od_first; /* first node in the linked list, if any */
485+
_ODictNode *od_last; /* last node in the linked list, if any */
486+
/* od_size and od_fast_nodes are managed by _odict_resize() */
487+
Py_ssize_t od_size; /* hash table that mirrors the dict table */
492488
_ODictNode **od_fast_nodes; /* managed by _odict_resize() */
493-
/* od_inst_dict is OrderedDict().__dict__. */
494-
PyObject *od_inst_dict;
495-
/* od_weakreflist holds weakrefs to the odict. */
496-
PyObject *od_weakreflist;
489+
size_t od_state; /* incremented whenever the LL changes */
490+
PyObject *od_inst_dict; /* OrderedDict().__dict__ */
491+
PyObject *od_weakreflist; /* holds weakrefs to the odict */
497492
};
498493

499494

@@ -608,6 +603,7 @@ _odict_get_index(PyODictObject *od, PyObject *key)
608603
static int
609604
_odict_initialize(PyODictObject *od)
610605
{
606+
od->od_state = 0;
611607
_odict_FIRST(od) = NULL;
612608
_odict_LAST(od) = NULL;
613609
return _odict_resize((PyODictObject *)od);
@@ -642,6 +638,7 @@ _odict_add_head(PyODictObject *od, _ODictNode *node)
642638
_odict_FIRST(od) = node;
643639
_odictnode_PREV(_odict_FIRST(od)) = node;
644640
}
641+
od->od_state++;
645642
}
646643

647644
static void
@@ -659,6 +656,7 @@ _odict_add_tail(PyODictObject *od, _ODictNode *node)
659656
_odictnode_NEXT(_odict_LAST(od)) = node;
660657
_odict_LAST(od) = node;
661658
}
659+
od->od_state++;
662660
}
663661

664662
/* adds the node to the end of the list */
@@ -725,6 +723,7 @@ _odict_remove_node(PyODictObject *od, _ODictNode *node)
725723

726724
_odictnode_PREV(node) = NULL;
727725
_odictnode_NEXT(node) = NULL;
726+
od->od_state++;
728727
}
729728

730729
static _ODictNode *
@@ -1829,6 +1828,7 @@ typedef struct {
18291828
int kind;
18301829
PyODictObject *di_odict;
18311830
Py_ssize_t di_size;
1831+
size_t di_state;
18321832
PyObject *di_current;
18331833
PyObject *di_result; /* reusable result tuple for iteritems */
18341834
} odictiterobject;
@@ -1869,6 +1869,11 @@ odictiter_nextkey(odictiterobject *di)
18691869
goto done; /* We're already done. */
18701870

18711871
/* Check for unsupported changes. */
1872+
if (di->di_odict->od_state != di->di_state) {
1873+
PyErr_SetString(PyExc_RuntimeError,
1874+
"OrderedDict mutated during iteration");
1875+
goto done;
1876+
}
18721877
if (di->di_size != PyODict_SIZE(di->di_odict)) {
18731878
PyErr_SetString(PyExc_RuntimeError,
18741879
"OrderedDict changed size during iteration");
@@ -2075,6 +2080,7 @@ odictiter_new(PyODictObject *od, int kind)
20752080
di->di_current = node ? _odictnode_KEY(node) : NULL;
20762081
Py_XINCREF(di->di_current);
20772082
di->di_size = PyODict_SIZE(od);
2083+
di->di_state = od->od_state;
20782084
di->di_odict = od;
20792085
Py_INCREF(od);
20802086

0 commit comments

Comments
 (0)