Skip to content

Commit a69f022

Browse files
Issue #25395: Fixed crash when highly nested OrderedDict structures were
garbage collected.
1 parent f55d474 commit a69f022

3 files changed

Lines changed: 41 additions & 3 deletions

File tree

Lib/test/test_collections.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,30 @@ def update(self, *args, **kwds):
20252025
items = [('a', 1), ('c', 3), ('b', 2)]
20262026
self.assertEqual(list(MyOD(items).items()), items)
20272027

2028+
def test_highly_nested(self):
2029+
# Issue 25395: crashes during garbage collection
2030+
OrderedDict = self.module.OrderedDict
2031+
obj = None
2032+
for _ in range(1000):
2033+
obj = OrderedDict([(None, obj)])
2034+
del obj
2035+
support.gc_collect()
2036+
2037+
def test_highly_nested_subclass(self):
2038+
# Issue 25395: crashes during garbage collection
2039+
OrderedDict = self.module.OrderedDict
2040+
deleted = []
2041+
class MyOD(OrderedDict):
2042+
def __del__(self):
2043+
deleted.append(self.i)
2044+
obj = None
2045+
for i in range(100):
2046+
obj = MyOD([(None, obj)])
2047+
obj.i = i
2048+
del obj
2049+
support.gc_collect()
2050+
self.assertEqual(deleted, list(reversed(range(100))))
2051+
20282052

20292053
class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
20302054

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Release date: TBA
1111
Core and Builtins
1212
-----------------
1313

14+
- Issue #25395: Fixed crash when highly nested OrderedDict structures were
15+
garbage collected.
16+
1417
- Issue #25274: sys.setrecursionlimit() now raises a RecursionError if the new
1518
recursion limit is too low depending at the current recursion depth. Modify
1619
also the "lower-water mark" formula to make it monotonic. This mark is used

Objects/odictobject.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,17 +1431,28 @@ static PyMemberDef odict_members[] = {
14311431
static void
14321432
odict_dealloc(PyODictObject *self)
14331433
{
1434+
PyThreadState *tstate = PyThreadState_GET();
1435+
14341436
PyObject_GC_UnTrack(self);
1435-
Py_TRASHCAN_SAFE_BEGIN(self);
1437+
Py_TRASHCAN_SAFE_BEGIN(self)
1438+
14361439
Py_XDECREF(self->od_inst_dict);
14371440
if (self->od_weakreflist != NULL)
14381441
PyObject_ClearWeakRefs((PyObject *)self);
14391442

14401443
_odict_clear_nodes(self);
1441-
Py_TRASHCAN_SAFE_END(self);
14421444

1443-
/* must be last */
1445+
/* Call the base tp_dealloc(). Since it too uses the trashcan mechanism,
1446+
* temporarily decrement trash_delete_nesting to prevent triggering it
1447+
* and putting the partially deallocated object on the trashcan's
1448+
* to-be-deleted-later list.
1449+
*/
1450+
--tstate->trash_delete_nesting;
1451+
assert(_tstate->trash_delete_nesting < PyTrash_UNWIND_LEVEL);
14441452
PyDict_Type.tp_dealloc((PyObject *)self);
1453+
++tstate->trash_delete_nesting;
1454+
1455+
Py_TRASHCAN_SAFE_END(self)
14451456
};
14461457

14471458
/* tp_repr */

0 commit comments

Comments
 (0)