Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9923ffe

Browse files
committed
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.
1 parent e22bc1e commit 9923ffe

3 files changed

Lines changed: 194 additions & 49 deletions

File tree

Lib/test/test_descr.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,45 @@ class C3(object):
10601060
vereq(x.b, 2)
10611061
vereq(x.c, 3)
10621062

1063+
# Make sure slot names are proper identifiers
1064+
try:
1065+
class C(object):
1066+
__slots__ = [None]
1067+
except TypeError:
1068+
pass
1069+
else:
1070+
raise TestFailed, "[None] slots not caught"
1071+
try:
1072+
class C(object):
1073+
__slots__ = ["foo bar"]
1074+
except TypeError:
1075+
pass
1076+
else:
1077+
raise TestFailed, "['foo bar'] slots not caught"
1078+
try:
1079+
class C(object):
1080+
__slots__ = ["foo\0bar"]
1081+
except TypeError:
1082+
pass
1083+
else:
1084+
raise TestFailed, "['foo\\0bar'] slots not caught"
1085+
try:
1086+
class C(object):
1087+
__slots__ = ["1"]
1088+
except TypeError:
1089+
pass
1090+
else:
1091+
raise TestFailed, "['1'] slots not caught"
1092+
try:
1093+
class C(object):
1094+
__slots__ = [""]
1095+
except TypeError:
1096+
pass
1097+
else:
1098+
raise TestFailed, "[''] slots not caught"
1099+
class C(object):
1100+
__slots__ = ["a", "a_b", "_a", "A0123456789Z"]
1101+
10631102
# Test leaks
10641103
class Counted(object):
10651104
counter = 0 # counts the number of instances alive
@@ -1094,6 +1133,18 @@ class E(D):
10941133
del x
10951134
vereq(Counted.counter, 0)
10961135

1136+
# Test cyclical leaks [SF bug 519621]
1137+
class F(object):
1138+
__slots__ = ['a', 'b']
1139+
log = []
1140+
s = F()
1141+
s.a = [Counted(), s]
1142+
vereq(Counted.counter, 1)
1143+
s = None
1144+
import gc
1145+
gc.collect()
1146+
vereq(Counted.counter, 0)
1147+
10971148
def dynamics():
10981149
if verbose: print "Testing class attribute propagation..."
10991150
class D(object):

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Type/class unification and new-style classes
66

77
Core and builtins
88

9+
- Classes using __slots__ are now properly garbage collected.
10+
[SF bug 519621]
11+
12+
- Tightened the __slots__ rules: a slot name must be a valid Python
13+
identifier.
14+
915
- The constructor for the module type now requires a name argument and
1016
takes an optional docstring argument. Previously, this constructor
1117
ignored its arguments. As a consequence, deriving a class from a

Objects/typeobject.c

Lines changed: 137 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
#include "Python.h"
44
#include "structmember.h"
55

6+
#include <ctype.h>
7+
8+
/* The *real* layout of a type object when allocated on the heap */
9+
/* XXX Should we publish this in a header file? */
10+
typedef struct {
11+
PyTypeObject type;
12+
PyNumberMethods as_number;
13+
PySequenceMethods as_sequence;
14+
PyMappingMethods as_mapping;
15+
PyBufferProcs as_buffer;
16+
PyObject *name, *slots;
17+
PyMemberDef members[1];
18+
} etype;
19+
620
static PyMemberDef type_members[] = {
721
{"__basicsize__", T_INT, offsetof(PyTypeObject,tp_basicsize),READONLY},
822
{"__itemsize__", T_INT, offsetof(PyTypeObject, tp_itemsize), READONLY},
@@ -225,32 +239,108 @@ PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
225239

226240
/* Helpers for subtyping */
227241

242+
static int
243+
traverse_slots(PyTypeObject *type, PyObject *self, visitproc visit, void *arg)
244+
{
245+
int i, n;
246+
PyMemberDef *mp;
247+
248+
n = type->ob_size;
249+
mp = ((etype *)type)->members;
250+
for (i = 0; i < n; i++, mp++) {
251+
if (mp->type == T_OBJECT_EX) {
252+
char *addr = (char *)self + mp->offset;
253+
PyObject *obj = *(PyObject **)addr;
254+
if (obj != NULL) {
255+
int err = visit(obj, arg);
256+
if (err)
257+
return err;
258+
}
259+
}
260+
}
261+
return 0;
262+
}
263+
228264
static int
229265
subtype_traverse(PyObject *self, visitproc visit, void *arg)
230266
{
231267
PyTypeObject *type, *base;
232-
traverseproc f;
233-
int err;
268+
traverseproc basetraverse;
234269

235-
/* Find the nearest base with a different tp_traverse */
270+
/* Find the nearest base with a different tp_traverse,
271+
and traverse slots while we're at it */
236272
type = self->ob_type;
237-
base = type->tp_base;
238-
while ((f = base->tp_traverse) == subtype_traverse) {
273+
base = type;
274+
while ((basetraverse = base->tp_traverse) == subtype_traverse) {
275+
if (base->ob_size) {
276+
int err = traverse_slots(base, self, visit, arg);
277+
if (err)
278+
return err;
279+
}
239280
base = base->tp_base;
240281
assert(base);
241282
}
242283

243284
if (type->tp_dictoffset != base->tp_dictoffset) {
244285
PyObject **dictptr = _PyObject_GetDictPtr(self);
245286
if (dictptr && *dictptr) {
246-
err = visit(*dictptr, arg);
287+
int err = visit(*dictptr, arg);
247288
if (err)
248289
return err;
249290
}
250291
}
251292

252-
if (f)
253-
return f(self, visit, arg);
293+
if (basetraverse)
294+
return basetraverse(self, visit, arg);
295+
return 0;
296+
}
297+
298+
static void
299+
clear_slots(PyTypeObject *type, PyObject *self)
300+
{
301+
int i, n;
302+
PyMemberDef *mp;
303+
304+
n = type->ob_size;
305+
mp = ((etype *)type)->members;
306+
for (i = 0; i < n; i++, mp++) {
307+
if (mp->type == T_OBJECT_EX && !(mp->flags & READONLY)) {
308+
char *addr = (char *)self + mp->offset;
309+
PyObject *obj = *(PyObject **)addr;
310+
if (obj != NULL) {
311+
Py_DECREF(obj);
312+
*(PyObject **)addr = NULL;
313+
}
314+
}
315+
}
316+
}
317+
318+
static int
319+
subtype_clear(PyObject *self)
320+
{
321+
PyTypeObject *type, *base;
322+
inquiry baseclear;
323+
324+
/* Find the nearest base with a different tp_clear
325+
and clear slots while we're at it */
326+
type = self->ob_type;
327+
base = type;
328+
while ((baseclear = base->tp_clear) == subtype_clear) {
329+
if (base->ob_size)
330+
clear_slots(base, self);
331+
base = base->tp_base;
332+
assert(base);
333+
}
334+
335+
if (type->tp_dictoffset != base->tp_dictoffset) {
336+
PyObject **dictptr = _PyObject_GetDictPtr(self);
337+
if (dictptr && *dictptr) {
338+
PyDict_Clear(*dictptr);
339+
}
340+
}
341+
342+
if (baseclear)
343+
return baseclear(self);
254344
return 0;
255345
}
256346

@@ -329,41 +419,24 @@ static void
329419
subtype_dealloc(PyObject *self)
330420
{
331421
PyTypeObject *type, *base;
332-
destructor f;
422+
destructor basedealloc;
333423

334424
/* This exists so we can DECREF self->ob_type */
335425

336426
if (call_finalizer(self) < 0)
337427
return;
338428

339-
/* Find the nearest base with a different tp_dealloc */
429+
/* Find the nearest base with a different tp_dealloc
430+
and clear slots while we're at it */
340431
type = self->ob_type;
341-
base = type->tp_base;
342-
while ((f = base->tp_dealloc) == subtype_dealloc) {
432+
base = type;
433+
while ((basedealloc = base->tp_dealloc) == subtype_dealloc) {
434+
if (base->ob_size)
435+
clear_slots(base, self);
343436
base = base->tp_base;
344437
assert(base);
345438
}
346439

347-
/* Clear __slots__ variables */
348-
if (type->tp_basicsize != base->tp_basicsize &&
349-
type->tp_itemsize == 0)
350-
{
351-
char *addr = ((char *)self);
352-
char *p = addr + base->tp_basicsize;
353-
char *q = addr + type->tp_basicsize;
354-
for (; p < q; p += sizeof(PyObject *)) {
355-
PyObject **pp;
356-
if (p == addr + type->tp_dictoffset ||
357-
p == addr + type->tp_weaklistoffset)
358-
continue;
359-
pp = (PyObject **)p;
360-
if (*pp != NULL) {
361-
Py_DECREF(*pp);
362-
*pp = NULL;
363-
}
364-
}
365-
}
366-
367440
/* If we added a dict, DECREF it */
368441
if (type->tp_dictoffset && !base->tp_dictoffset) {
369442
PyObject **dictptr = _PyObject_GetDictPtr(self);
@@ -385,8 +458,8 @@ subtype_dealloc(PyObject *self)
385458
_PyObject_GC_UNTRACK(self);
386459

387460
/* Call the base tp_dealloc() */
388-
assert(f);
389-
f(self);
461+
assert(basedealloc);
462+
basedealloc(self);
390463

391464
/* Can't reference self beyond this point */
392465
if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
@@ -396,16 +469,6 @@ subtype_dealloc(PyObject *self)
396469

397470
staticforward PyTypeObject *solid_base(PyTypeObject *type);
398471

399-
typedef struct {
400-
PyTypeObject type;
401-
PyNumberMethods as_number;
402-
PySequenceMethods as_sequence;
403-
PyMappingMethods as_mapping;
404-
PyBufferProcs as_buffer;
405-
PyObject *name, *slots;
406-
PyMemberDef members[1];
407-
} etype;
408-
409472
/* type test with subclassing support */
410473

411474
int
@@ -890,6 +953,33 @@ static PyMethodDef bozo_ml = {"__getstate__", bozo_func, METH_VARARGS};
890953

891954
static PyObject *bozo_obj = NULL;
892955

956+
static int
957+
valid_identifier(PyObject *s)
958+
{
959+
char *p;
960+
int i, n;
961+
962+
if (!PyString_Check(s)) {
963+
PyErr_SetString(PyExc_TypeError,
964+
"__slots__ must be strings");
965+
return 0;
966+
}
967+
p = PyString_AS_STRING(s);
968+
n = PyString_GET_SIZE(s);
969+
/* We must reject an empty name. As a hack, we bump the
970+
length to 1 so that the loop will balk on the trailing \0. */
971+
if (n == 0)
972+
n = 1;
973+
for (i = 0; i < n; i++, p++) {
974+
if (!(i == 0 ? isalpha(*p) : isalnum(*p)) && *p != '_') {
975+
PyErr_SetString(PyExc_TypeError,
976+
"__slots__ must be identifiers");
977+
return 0;
978+
}
979+
}
980+
return 1;
981+
}
982+
893983
static PyObject *
894984
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
895985
{
@@ -1004,13 +1094,10 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
10041094
return NULL;
10051095
}
10061096
for (i = 0; i < nslots; i++) {
1007-
if (!PyString_Check(PyTuple_GET_ITEM(slots, i))) {
1008-
PyErr_SetString(PyExc_TypeError,
1009-
"__slots__ must be a sequence of strings");
1097+
if (!valid_identifier(PyTuple_GET_ITEM(slots, i))) {
10101098
Py_DECREF(slots);
10111099
return NULL;
10121100
}
1013-
/* XXX Check against null bytes in name */
10141101
}
10151102
}
10161103
if (slots != NULL) {
@@ -1145,6 +1232,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
11451232
if (base->tp_weaklistoffset == 0 &&
11461233
strcmp(mp->name, "__weakref__") == 0) {
11471234
mp->type = T_OBJECT;
1235+
mp->flags = READONLY;
11481236
type->tp_weaklistoffset = slotoffset;
11491237
}
11501238
slotoffset += sizeof(PyObject *);
@@ -1194,7 +1282,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
11941282
if (type->tp_flags & Py_TPFLAGS_HAVE_GC) {
11951283
type->tp_free = PyObject_GC_Del;
11961284
type->tp_traverse = subtype_traverse;
1197-
type->tp_clear = base->tp_clear;
1285+
type->tp_clear = subtype_clear;
11981286
}
11991287
else
12001288
type->tp_free = PyObject_Del;

0 commit comments

Comments
 (0)