mirror of
https://github.com/python/cpython.git
synced 2024-11-28 04:15:11 +08:00
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 tightened the rules for slot names: they must now be proper identifiers (ignoring the dirty little fact that <ctype.h> is locale sensitive). Also set mp->flags = READONLY for the __weakref__ pseudo-slot. Most of this is a 2.2 bugfix candidate; I'll apply it there myself.
This commit is contained in:
parent
e22bc1e841
commit
9923ffe2c0
@ -1060,6 +1060,45 @@ def slots():
|
||||
vereq(x.b, 2)
|
||||
vereq(x.c, 3)
|
||||
|
||||
# Make sure slot names are proper identifiers
|
||||
try:
|
||||
class C(object):
|
||||
__slots__ = [None]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise TestFailed, "[None] slots not caught"
|
||||
try:
|
||||
class C(object):
|
||||
__slots__ = ["foo bar"]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise TestFailed, "['foo bar'] slots not caught"
|
||||
try:
|
||||
class C(object):
|
||||
__slots__ = ["foo\0bar"]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise TestFailed, "['foo\\0bar'] slots not caught"
|
||||
try:
|
||||
class C(object):
|
||||
__slots__ = ["1"]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise TestFailed, "['1'] slots not caught"
|
||||
try:
|
||||
class C(object):
|
||||
__slots__ = [""]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise TestFailed, "[''] slots not caught"
|
||||
class C(object):
|
||||
__slots__ = ["a", "a_b", "_a", "A0123456789Z"]
|
||||
|
||||
# Test leaks
|
||||
class Counted(object):
|
||||
counter = 0 # counts the number of instances alive
|
||||
@ -1094,6 +1133,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):
|
||||
|
@ -6,6 +6,12 @@ Type/class unification and new-style classes
|
||||
|
||||
Core and builtins
|
||||
|
||||
- Classes using __slots__ are now properly garbage collected.
|
||||
[SF bug 519621]
|
||||
|
||||
- Tightened the __slots__ rules: a slot name must be a valid Python
|
||||
identifier.
|
||||
|
||||
- The constructor for the module type now requires a name argument and
|
||||
takes an optional docstring argument. Previously, this constructor
|
||||
ignored its arguments. As a consequence, deriving a class from a
|
||||
|
@ -3,6 +3,20 @@
|
||||
#include "Python.h"
|
||||
#include "structmember.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
/* 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},
|
||||
@ -225,17 +239,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);
|
||||
}
|
||||
@ -243,14 +284,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;
|
||||
}
|
||||
|
||||
@ -329,41 +419,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);
|
||||
@ -385,8 +458,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) {
|
||||
@ -396,16 +469,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
|
||||
@ -890,6 +953,33 @@ static PyMethodDef bozo_ml = {"__getstate__", bozo_func, METH_VARARGS};
|
||||
|
||||
static PyObject *bozo_obj = NULL;
|
||||
|
||||
static int
|
||||
valid_identifier(PyObject *s)
|
||||
{
|
||||
char *p;
|
||||
int i, n;
|
||||
|
||||
if (!PyString_Check(s)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"__slots__ must be strings");
|
||||
return 0;
|
||||
}
|
||||
p = PyString_AS_STRING(s);
|
||||
n = PyString_GET_SIZE(s);
|
||||
/* We must reject an empty name. As a hack, we bump the
|
||||
length to 1 so that the loop will balk on the trailing \0. */
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
for (i = 0; i < n; i++, p++) {
|
||||
if (!(i == 0 ? isalpha(*p) : isalnum(*p)) && *p != '_') {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"__slots__ must be identifiers");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
@ -1004,13 +1094,10 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < nslots; i++) {
|
||||
if (!PyString_Check(PyTuple_GET_ITEM(slots, i))) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"__slots__ must be a sequence of strings");
|
||||
if (!valid_identifier(PyTuple_GET_ITEM(slots, i))) {
|
||||
Py_DECREF(slots);
|
||||
return NULL;
|
||||
}
|
||||
/* XXX Check against null bytes in name */
|
||||
}
|
||||
}
|
||||
if (slots != NULL) {
|
||||
@ -1145,6 +1232,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 *);
|
||||
@ -1194,7 +1282,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;
|
||||
|
Loading…
Reference in New Issue
Block a user