diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -286,13 +286,9 @@ However, if the derived class declares a __slots__, those slots are already gone. */ - if (_PyIOBase_finalize((PyObject *) self) < 0) { - /* When called from a heap type's dealloc, the type will be - decref'ed on return (see e.g. subtype_dealloc in typeobject.c). */ - if (PyType_HasFeature(Py_TYPE(self), Py_TPFLAGS_HEAPTYPE)) - Py_INCREF(Py_TYPE(self)); + if (_PyIOBase_finalize((PyObject *) self) < 0) return; - } + _PyObject_GC_UNTRACK(self); if (self->weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *) self); diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -881,6 +881,59 @@ return 0; } +/* Code to monitor if the object gets deleted during the call to an objects + * destructor method. + * The fundamental problem is that tp_dealloc (of parent types) does not + * return information about whether it deleted the object or not + * (it may have been resurrected.) And so subtype_dealloc doesn't know if + * it should Py_DECREF its type or not. + * The approach taken here is to temporarily replace the type's tp_del + * slot with one that monitors if it is being called for a specific object. + * This code is used only by dynamically created subtypes and so + * the basetype _must_ call tp_free to release its memory (all inheritable + * types must do so.) + * This code uses static globals and thus relies on the GIL + * not being released for the duration or otherwise other threads might + * clobber them. This could be a problem if a __del__ method is invoked, + * but all "python" __del__ methods should get invoked from subtype_dealloc + * so it shouldn't be a problem, otherwise, we would have to resort to TLS. + */ +typedef struct _free_data_t +{ + freefunc base_free; + PyObject *target; + int hit; +} free_data_t; +static free_data_t free_data; +static void checking_free(void *ptr) +{ + if ((PyObject*)ptr == free_data.target) + free_data.hit = 1; + free_data.base_free(ptr); +} + +static int +call_basedealloc(destructor basedealloc, PyObject *self) +{ + PyTypeObject *type = Py_TYPE(self); + /* Store old state, if there is recursion, due to one tp_dealloc + * causing other tp_deallocs + */ + free_data_t old_data = free_data; + free_data_t new_data = {type->tp_free, self, 0}; + int result; + + free_data = new_data; + type->tp_free = checking_free; + basedealloc(self); + type->tp_free = free_data.base_free; + result = free_data.hit; + + /* restore old state */ + free_data = old_data; + return result; +} + static void subtype_dealloc(PyObject *self) { @@ -920,10 +973,12 @@ /* Call the base tp_dealloc() */ assert(basedealloc); - basedealloc(self); - - /* Can't reference self beyond this point */ - Py_DECREF(type); + if (call_basedealloc(basedealloc, self)) { + /* Object was indeed destroyed. + * Can't reference self beyond this point + */ + Py_DECREF(type); + } /* Done */ return; @@ -1008,10 +1063,12 @@ if (PyType_IS_GC(base)) _PyObject_GC_TRACK(self); assert(basedealloc); - basedealloc(self); - - /* Can't reference self beyond this point */ - Py_DECREF(type); + if (call_basedealloc(basedealloc, self)) { + /* Object was indeed destroyed. + * Can't reference self beyond this point + */ + Py_DECREF(type); + } endlabel: ++_PyTrash_delete_nesting;