changeset: 98923:bd6bfa5fe203 parent: 98921:f7db966c9fee parent: 98922:ce8c850a52d4 user: Serhiy Storchaka date: Sun Nov 01 16:13:45 2015 +0200 files: Lib/test/test_collections.py Misc/NEWS description: Issue #25395: Fixed crash when highly nested OrderedDict structures were garbage collected. diff -r f7db966c9fee -r bd6bfa5fe203 Lib/test/test_collections.py --- a/Lib/test/test_collections.py Sun Nov 01 00:55:31 2015 +0300 +++ b/Lib/test/test_collections.py Sun Nov 01 16:13:45 2015 +0200 @@ -2033,6 +2033,30 @@ items = [('a', 1), ('c', 3), ('b', 2)] self.assertEqual(list(MyOD(items).items()), items) + def test_highly_nested(self): + # Issue 25395: crashes during garbage collection + OrderedDict = self.module.OrderedDict + obj = None + for _ in range(1000): + obj = OrderedDict([(None, obj)]) + del obj + support.gc_collect() + + def test_highly_nested_subclass(self): + # Issue 25395: crashes during garbage collection + OrderedDict = self.module.OrderedDict + deleted = [] + class MyOD(OrderedDict): + def __del__(self): + deleted.append(self.i) + obj = None + for i in range(100): + obj = MyOD([(None, obj)]) + obj.i = i + del obj + support.gc_collect() + self.assertEqual(deleted, list(reversed(range(100)))) + class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase): diff -r f7db966c9fee -r bd6bfa5fe203 Misc/NEWS --- a/Misc/NEWS Sun Nov 01 00:55:31 2015 +0300 +++ b/Misc/NEWS Sun Nov 01 16:13:45 2015 +0200 @@ -10,6 +10,9 @@ Core and Builtins ----------------- +- Issue #25395: Fixed crash when highly nested OrderedDict structures were + garbage collected. + - Issue #25401: Optimize bytes.fromhex() and bytearray.fromhex(): they are now between 2x and 3.5x faster. diff -r f7db966c9fee -r bd6bfa5fe203 Objects/odictobject.c --- a/Objects/odictobject.c Sun Nov 01 00:55:31 2015 +0300 +++ b/Objects/odictobject.c Sun Nov 01 16:13:45 2015 +0200 @@ -1431,17 +1431,28 @@ static void odict_dealloc(PyODictObject *self) { + PyThreadState *tstate = PyThreadState_GET(); + PyObject_GC_UnTrack(self); - Py_TRASHCAN_SAFE_BEGIN(self); + Py_TRASHCAN_SAFE_BEGIN(self) + Py_XDECREF(self->od_inst_dict); if (self->od_weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *)self); _odict_clear_nodes(self); - Py_TRASHCAN_SAFE_END(self); - /* must be last */ + /* Call the base tp_dealloc(). Since it too uses the trashcan mechanism, + * temporarily decrement trash_delete_nesting to prevent triggering it + * and putting the partially deallocated object on the trashcan's + * to-be-deleted-later list. + */ + --tstate->trash_delete_nesting; + assert(_tstate->trash_delete_nesting < PyTrash_UNWIND_LEVEL); PyDict_Type.tp_dealloc((PyObject *)self); + ++tstate->trash_delete_nesting; + + Py_TRASHCAN_SAFE_END(self) }; /* tp_repr */