Issue #25395: Fixed crash when highly nested OrderedDict structures were

garbage collected.
This commit is contained in:
Serhiy Storchaka 2015-11-01 16:12:34 +02:00
parent 964ec8b2f3
commit 14eefe353e
3 changed files with 41 additions and 3 deletions

View File

@ -2025,6 +2025,30 @@ class OrderedDictTests:
items = [('a', 1), ('c', 3), ('b', 2)] items = [('a', 1), ('c', 3), ('b', 2)]
self.assertEqual(list(MyOD(items).items()), items) 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): class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):

View File

@ -11,6 +11,9 @@ Release date: TBA
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #25395: Fixed crash when highly nested OrderedDict structures were
garbage collected.
- Issue #25274: sys.setrecursionlimit() now raises a RecursionError if the new - Issue #25274: sys.setrecursionlimit() now raises a RecursionError if the new
recursion limit is too low depending at the current recursion depth. Modify recursion limit is too low depending at the current recursion depth. Modify
also the "lower-water mark" formula to make it monotonic. This mark is used also the "lower-water mark" formula to make it monotonic. This mark is used

View File

@ -1431,17 +1431,28 @@ static PyMemberDef odict_members[] = {
static void static void
odict_dealloc(PyODictObject *self) odict_dealloc(PyODictObject *self)
{ {
PyThreadState *tstate = PyThreadState_GET();
PyObject_GC_UnTrack(self); PyObject_GC_UnTrack(self);
Py_TRASHCAN_SAFE_BEGIN(self); Py_TRASHCAN_SAFE_BEGIN(self)
Py_XDECREF(self->od_inst_dict); Py_XDECREF(self->od_inst_dict);
if (self->od_weakreflist != NULL) if (self->od_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *)self); PyObject_ClearWeakRefs((PyObject *)self);
_odict_clear_nodes(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); PyDict_Type.tp_dealloc((PyObject *)self);
++tstate->trash_delete_nesting;
Py_TRASHCAN_SAFE_END(self)
}; };
/* tp_repr */ /* tp_repr */