Skip to content

Commit d197b2b

Browse files
bpo-41984: GC track all user classes (GH-22701/GH-22702)
(cherry picked from commit c13b847)
1 parent f07448b commit d197b2b

File tree

5 files changed

+52
-21
lines changed

5 files changed

+52
-21
lines changed

Lib/test/test_finalization.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ def __new__(cls, *args, **kwargs):
1616
raise TypeError('requires _testcapi.with_tp_del')
1717
return C
1818

19+
try:
20+
from _testcapi import without_gc
21+
except ImportError:
22+
def without_gc(cls):
23+
class C:
24+
def __new__(cls, *args, **kwargs):
25+
raise TypeError('requires _testcapi.without_gc')
26+
return C
27+
1928
from test import support
2029

2130

@@ -94,9 +103,11 @@ def check_sanity(self):
94103
assert self.id_ == id(self)
95104

96105

106+
@without_gc
97107
class NonGC(NonGCSimpleBase):
98108
__slots__ = ()
99109

110+
@without_gc
100111
class NonGCResurrector(NonGCSimpleBase):
101112
__slots__ = ()
102113

@@ -109,8 +120,14 @@ def side_effect(self):
109120
class Simple(SimpleBase):
110121
pass
111122

112-
class SimpleResurrector(NonGCResurrector, SimpleBase):
113-
pass
123+
# Can't inherit from NonGCResurrector, in case importing without_gc fails.
124+
class SimpleResurrector(SimpleBase):
125+
126+
def side_effect(self):
127+
"""
128+
Resurrect self by storing self in a class-wide list.
129+
"""
130+
self.survivors.append(self)
114131

115132

116133
class TestBase:
@@ -178,6 +195,7 @@ def test_simple_resurrect(self):
178195
self.assert_survivors([])
179196
self.assertIs(wr(), None)
180197

198+
@support.cpython_only
181199
def test_non_gc(self):
182200
with SimpleBase.test():
183201
s = NonGC()
@@ -191,6 +209,7 @@ def test_non_gc(self):
191209
self.assert_del_calls(ids)
192210
self.assert_survivors([])
193211

212+
@support.cpython_only
194213
def test_non_gc_resurrect(self):
195214
with SimpleBase.test():
196215
s = NonGCResurrector()

Lib/test/test_gc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,9 @@ class UserIntSlots(int):
581581
self.assertTrue(gc.is_tracked(UserInt()))
582582
self.assertTrue(gc.is_tracked([]))
583583
self.assertTrue(gc.is_tracked(set()))
584-
self.assertFalse(gc.is_tracked(UserClassSlots()))
585-
self.assertFalse(gc.is_tracked(UserFloatSlots()))
586-
self.assertFalse(gc.is_tracked(UserIntSlots()))
584+
self.assertTrue(gc.is_tracked(UserClassSlots()))
585+
self.assertTrue(gc.is_tracked(UserFloatSlots()))
586+
self.assertTrue(gc.is_tracked(UserIntSlots()))
587587

588588
def test_is_finalized(self):
589589
# Objects not tracked by the always gc return false
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The garbage collector now tracks all user-defined classes. Patch by Brandt
2+
Bucher.

Modules/_testcapimodule.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3629,6 +3629,25 @@ with_tp_del(PyObject *self, PyObject *args)
36293629
return obj;
36303630
}
36313631

3632+
static PyObject *
3633+
without_gc(PyObject *Py_UNUSED(self), PyObject *obj)
3634+
{
3635+
PyTypeObject *tp = (PyTypeObject*)obj;
3636+
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
3637+
return PyErr_Format(PyExc_TypeError, "heap type expected, got %R", obj);
3638+
}
3639+
if (PyType_IS_GC(tp)) {
3640+
// Don't try this at home, kids:
3641+
tp->tp_flags -= Py_TPFLAGS_HAVE_GC;
3642+
tp->tp_free = PyObject_Del;
3643+
tp->tp_traverse = NULL;
3644+
tp->tp_clear = NULL;
3645+
}
3646+
assert(!PyType_IS_GC(tp));
3647+
Py_INCREF(obj);
3648+
return obj;
3649+
}
3650+
36323651
static PyMethodDef ml;
36333652

36343653
static PyObject *
@@ -5535,6 +5554,7 @@ static PyMethodDef TestMethods[] = {
55355554
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL},
55365555
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS},
55375556
{"pynumber_tobase", pynumber_tobase, METH_VARARGS},
5557+
{"without_gc", without_gc, METH_O},
55385558
{NULL, NULL} /* sentinel */
55395559
};
55405560

Objects/typeobject.c

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,10 +2605,10 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
26052605
slots = NULL;
26062606

26072607
/* Initialize tp_flags */
2608+
// All heap types need GC, since we can create a reference cycle by storing
2609+
// an instance on one of its parents:
26082610
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
2609-
Py_TPFLAGS_BASETYPE;
2610-
if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
2611-
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
2611+
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC;
26122612

26132613
/* Initialize essential fields */
26142614
type->tp_as_async = &et->as_async;
@@ -2808,21 +2808,11 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
28082808
}
28092809
type->tp_dealloc = subtype_dealloc;
28102810

2811-
/* Enable GC unless this class is not adding new instance variables and
2812-
the base class did not use GC. */
2813-
if ((base->tp_flags & Py_TPFLAGS_HAVE_GC) ||
2814-
type->tp_basicsize > base->tp_basicsize)
2815-
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
2816-
28172811
/* Always override allocation strategy to use regular heap */
28182812
type->tp_alloc = PyType_GenericAlloc;
2819-
if (type->tp_flags & Py_TPFLAGS_HAVE_GC) {
2820-
type->tp_free = PyObject_GC_Del;
2821-
type->tp_traverse = subtype_traverse;
2822-
type->tp_clear = subtype_clear;
2823-
}
2824-
else
2825-
type->tp_free = PyObject_Del;
2813+
type->tp_free = PyObject_GC_Del;
2814+
type->tp_traverse = subtype_traverse;
2815+
type->tp_clear = subtype_clear;
28262816

28272817
/* store type in class' cell if one is supplied */
28282818
cell = _PyDict_GetItemIdWithError(dict, &PyId___classcell__);

0 commit comments

Comments
 (0)