From 761037af5cec71d68d43613cb1a963dd58a342d2 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 4 Jun 2002 21:19:55 +0000 Subject: [PATCH] Backport to 2.2.x: Address SF bug 519621: slots weren't traversed by GC. While I was at it, I added a tp_clear handler and changed the tp_dealloc handler to use the clear_slots helper for the tp_clear handler. Also set mp->flags = READONLY for the __weakref__ pseudo-slot. [Note that I am *not* backporting the part of that patch that tightened the __slot__ rules.] --- Lib/test/test_descr.py | 12 ++++ Misc/NEWS | 3 + Objects/typeobject.c | 154 +++++++++++++++++++++++++++++------------ 3 files changed, 124 insertions(+), 45 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 3f7e5dda47a2..5048b823f81b 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1093,6 +1093,18 @@ def slots(): del x vereq(Counted.counter, 0) + # Test cyclical leaks [SF bug 519621] + class F(object): + __slots__ = ['a', 'b'] + log = [] + s = F() + s.a = [Counted(), s] + vereq(Counted.counter, 1) + s = None + import gc + gc.collect() + vereq(Counted.counter, 0) + def dynamics(): if verbose: print "Testing class attribute propagation..." class D(object): diff --git a/Misc/NEWS b/Misc/NEWS index 48c9b321a6b3..cd2ad3915004 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -4,6 +4,9 @@ Release date: dd-mmm-2002 Core and builtins +- Classes using __slots__ are now properly garbage collected. + [SF bug 519621] + - Repaired a slow memory leak possible only in programs creating a great many cyclic structures involving frames. Reported on SourceForge as bug 543148. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c2264541cec8..3736c8483721 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4,6 +4,20 @@ #include "Python.h" #include "structmember.h" +#include + +/* The *real* layout of a type object when allocated on the heap */ +/* XXX Should we publish this in a header file? */ +typedef struct { + PyTypeObject type; + PyNumberMethods as_number; + PySequenceMethods as_sequence; + PyMappingMethods as_mapping; + PyBufferProcs as_buffer; + PyObject *name, *slots; + PyMemberDef members[1]; +} etype; + static PyMemberDef type_members[] = { {"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY}, {"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY}, @@ -222,17 +236,44 @@ PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds) /* Helpers for subtyping */ +static int +traverse_slots(PyTypeObject *type, PyObject *self, visitproc visit, void *arg) +{ + int i, n; + PyMemberDef *mp; + + n = type->ob_size; + mp = ((etype *)type)->members; + for (i = 0; i < n; i++, mp++) { + if (mp->type == T_OBJECT_EX) { + char *addr = (char *)self + mp->offset; + PyObject *obj = *(PyObject **)addr; + if (obj != NULL) { + int err = visit(obj, arg); + if (err) + return err; + } + } + } + return 0; +} + static int subtype_traverse(PyObject *self, visitproc visit, void *arg) { PyTypeObject *type, *base; - traverseproc f; - int err; + traverseproc basetraverse; - /* Find the nearest base with a different tp_traverse */ + /* Find the nearest base with a different tp_traverse, + and traverse slots while we're at it */ type = self->ob_type; - base = type->tp_base; - while ((f = base->tp_traverse) == subtype_traverse) { + base = type; + while ((basetraverse = base->tp_traverse) == subtype_traverse) { + if (base->ob_size) { + int err = traverse_slots(base, self, visit, arg); + if (err) + return err; + } base = base->tp_base; assert(base); } @@ -240,14 +281,63 @@ subtype_traverse(PyObject *self, visitproc visit, void *arg) if (type->tp_dictoffset != base->tp_dictoffset) { PyObject **dictptr = _PyObject_GetDictPtr(self); if (dictptr && *dictptr) { - err = visit(*dictptr, arg); + int err = visit(*dictptr, arg); if (err) return err; } } - if (f) - return f(self, visit, arg); + if (basetraverse) + return basetraverse(self, visit, arg); + return 0; +} + +static void +clear_slots(PyTypeObject *type, PyObject *self) +{ + int i, n; + PyMemberDef *mp; + + n = type->ob_size; + mp = ((etype *)type)->members; + for (i = 0; i < n; i++, mp++) { + if (mp->type == T_OBJECT_EX && !(mp->flags & READONLY)) { + char *addr = (char *)self + mp->offset; + PyObject *obj = *(PyObject **)addr; + if (obj != NULL) { + Py_DECREF(obj); + *(PyObject **)addr = NULL; + } + } + } +} + +static int +subtype_clear(PyObject *self) +{ + PyTypeObject *type, *base; + inquiry baseclear; + + /* Find the nearest base with a different tp_clear + and clear slots while we're at it */ + type = self->ob_type; + base = type; + while ((baseclear = base->tp_clear) == subtype_clear) { + if (base->ob_size) + clear_slots(base, self); + base = base->tp_base; + assert(base); + } + + if (type->tp_dictoffset != base->tp_dictoffset) { + PyObject **dictptr = _PyObject_GetDictPtr(self); + if (dictptr && *dictptr) { + PyDict_Clear(*dictptr); + } + } + + if (baseclear) + return baseclear(self); return 0; } @@ -326,41 +416,24 @@ static void subtype_dealloc(PyObject *self) { PyTypeObject *type, *base; - destructor f; + destructor basedealloc; /* This exists so we can DECREF self->ob_type */ if (call_finalizer(self) < 0) return; - /* Find the nearest base with a different tp_dealloc */ + /* Find the nearest base with a different tp_dealloc + and clear slots while we're at it */ type = self->ob_type; - base = type->tp_base; - while ((f = base->tp_dealloc) == subtype_dealloc) { + base = type; + while ((basedealloc = base->tp_dealloc) == subtype_dealloc) { + if (base->ob_size) + clear_slots(base, self); base = base->tp_base; assert(base); } - /* Clear __slots__ variables */ - if (type->tp_basicsize != base->tp_basicsize && - type->tp_itemsize == 0) - { - char *addr = ((char *)self); - char *p = addr + base->tp_basicsize; - char *q = addr + type->tp_basicsize; - for (; p < q; p += sizeof(PyObject *)) { - PyObject **pp; - if (p == addr + type->tp_dictoffset || - p == addr + type->tp_weaklistoffset) - continue; - pp = (PyObject **)p; - if (*pp != NULL) { - Py_DECREF(*pp); - *pp = NULL; - } - } - } - /* If we added a dict, DECREF it */ if (type->tp_dictoffset && !base->tp_dictoffset) { PyObject **dictptr = _PyObject_GetDictPtr(self); @@ -382,8 +455,8 @@ subtype_dealloc(PyObject *self) _PyObject_GC_UNTRACK(self); /* Call the base tp_dealloc() */ - assert(f); - f(self); + assert(basedealloc); + basedealloc(self); /* Can't reference self beyond this point */ if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) { @@ -393,16 +466,6 @@ subtype_dealloc(PyObject *self) staticforward PyTypeObject *solid_base(PyTypeObject *type); -typedef struct { - PyTypeObject type; - PyNumberMethods as_number; - PySequenceMethods as_sequence; - PyMappingMethods as_mapping; - PyBufferProcs as_buffer; - PyObject *name, *slots; - PyMemberDef members[1]; -} etype; - /* type test with subclassing support */ int @@ -1142,6 +1205,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) if (base->tp_weaklistoffset == 0 && strcmp(mp->name, "__weakref__") == 0) { mp->type = T_OBJECT; + mp->flags = READONLY; type->tp_weaklistoffset = slotoffset; } slotoffset += sizeof(PyObject *); @@ -1191,7 +1255,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) if (type->tp_flags & Py_TPFLAGS_HAVE_GC) { type->tp_free = _PyObject_GC_Del; type->tp_traverse = subtype_traverse; - type->tp_clear = base->tp_clear; + type->tp_clear = subtype_clear; } else type->tp_free = _PyObject_Del; -- 2.47.3