Skip to content

Commit 2eea952

Browse files
authored
bpo-31095: fix potential crash during GC (GH-3195)
(cherry picked from commit a6296d3)
1 parent 7d8282d commit 2eea952

File tree

14 files changed

+60
-13
lines changed

14 files changed

+60
-13
lines changed

Doc/extending/newtypes.rst

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,9 @@ functions. With :c:func:`Py_VISIT`, :c:func:`Noddy_traverse` can be simplified:
728728
uniformity across these boring implementations.
729729

730730
We also need to provide a method for clearing any subobjects that can
731-
participate in cycles. We implement the method and reimplement the deallocator
732-
to use it::
731+
participate in cycles.
732+
733+
::
733734

734735
static int
735736
Noddy_clear(Noddy *self)
@@ -747,13 +748,6 @@ to use it::
747748
return 0;
748749
}
749750

750-
static void
751-
Noddy_dealloc(Noddy* self)
752-
{
753-
Noddy_clear(self);
754-
Py_TYPE(self)->tp_free((PyObject*)self);
755-
}
756-
757751
Notice the use of a temporary variable in :c:func:`Noddy_clear`. We use the
758752
temporary variable so that we can set each member to *NULL* before decrementing
759753
its reference count. We do this because, as was discussed earlier, if the
@@ -776,6 +770,23 @@ be simplified::
776770
return 0;
777771
}
778772

773+
Note that :c:func:`Noddy_dealloc` may call arbitrary functions through
774+
``__del__`` method or weakref callback. It means circular GC can be
775+
triggered inside the function. Since GC assumes reference count is not zero,
776+
we need to untrack the object from GC by calling :c:func:`PyObject_GC_UnTrack`
777+
before clearing members. Here is reimplemented deallocator which uses
778+
:c:func:`PyObject_GC_UnTrack` and :c:func:`Noddy_clear`.
779+
780+
::
781+
782+
static void
783+
Noddy_dealloc(Noddy* self)
784+
{
785+
PyObject_GC_UnTrack(self);
786+
Noddy_clear(self);
787+
Py_TYPE(self)->tp_free((PyObject*)self);
788+
}
789+
779790
Finally, we add the :const:`Py_TPFLAGS_HAVE_GC` flag to the class flags::
780791

781792
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */

Doc/includes/noddy4.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Noddy_clear(Noddy *self)
4646
static void
4747
Noddy_dealloc(Noddy* self)
4848
{
49+
PyObject_GC_UnTrack(self);
4950
Noddy_clear(self);
5051
Py_TYPE(self)->tp_free((PyObject*)self);
5152
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix potential crash during GC caused by ``tp_dealloc`` which doesn't call
2+
``PyObject_GC_UnTrack()``.

Modules/_collectionsmodule.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,8 @@ dequeiter_traverse(dequeiterobject *dio, visitproc visit, void *arg)
17061706
static void
17071707
dequeiter_dealloc(dequeiterobject *dio)
17081708
{
1709+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1710+
PyObject_GC_UnTrack(dio);
17091711
Py_XDECREF(dio->deque);
17101712
PyObject_GC_Del(dio);
17111713
}
@@ -2086,6 +2088,8 @@ static PyMemberDef defdict_members[] = {
20862088
static void
20872089
defdict_dealloc(defdictobject *dd)
20882090
{
2091+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2092+
PyObject_GC_UnTrack(dd);
20892093
Py_CLEAR(dd->default_factory);
20902094
PyDict_Type.tp_dealloc((PyObject *)dd);
20912095
}

Modules/_elementtree.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ element_gc_clear(ElementObject *self)
627627
static void
628628
element_dealloc(ElementObject* self)
629629
{
630+
/* bpo-31095: UnTrack is needed before calling any callbacks */
630631
PyObject_GC_UnTrack(self);
631632
Py_TRASHCAN_SAFE_BEGIN(self)
632633

@@ -2048,14 +2049,15 @@ elementiter_dealloc(ElementIterObject *it)
20482049
{
20492050
Py_ssize_t i = it->parent_stack_used;
20502051
it->parent_stack_used = 0;
2052+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2053+
PyObject_GC_UnTrack(it);
20512054
while (i--)
20522055
Py_XDECREF(it->parent_stack[i].parent);
20532056
PyMem_Free(it->parent_stack);
20542057

20552058
Py_XDECREF(it->sought_tag);
20562059
Py_XDECREF(it->root_element);
20572060

2058-
PyObject_GC_UnTrack(it);
20592061
PyObject_GC_Del(it);
20602062
}
20612063

Modules/_functoolsmodule.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
116116
static void
117117
partial_dealloc(partialobject *pto)
118118
{
119+
/* bpo-31095: UnTrack is needed before calling any callbacks */
119120
PyObject_GC_UnTrack(pto);
120121
if (pto->weakreflist != NULL)
121122
PyObject_ClearWeakRefs((PyObject *) pto);
@@ -1038,7 +1039,11 @@ lru_cache_clear_list(lru_list_elem *link)
10381039
static void
10391040
lru_cache_dealloc(lru_cache_object *obj)
10401041
{
1041-
lru_list_elem *list = lru_cache_unlink_list(obj);
1042+
lru_list_elem *list;
1043+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1044+
PyObject_GC_UnTrack(obj);
1045+
1046+
list = lru_cache_unlink_list(obj);
10421047
Py_XDECREF(obj->maxsize_O);
10431048
Py_XDECREF(obj->func);
10441049
Py_XDECREF(obj->cache);

Modules/_io/bytesio.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,8 @@ bytesiobuf_traverse(bytesiobuf *self, visitproc visit, void *arg)
11311131
static void
11321132
bytesiobuf_dealloc(bytesiobuf *self)
11331133
{
1134+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1135+
PyObject_GC_UnTrack(self);
11341136
Py_CLEAR(self->source);
11351137
Py_TYPE(self)->tp_free(self);
11361138
}

Modules/_json.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,8 @@ py_encode_basestring(PyObject* self UNUSED, PyObject *pystr)
655655
static void
656656
scanner_dealloc(PyObject *self)
657657
{
658-
/* Deallocate scanner object */
658+
/* bpo-31095: UnTrack is needed before calling any callbacks */
659+
PyObject_GC_UnTrack(self);
659660
scanner_clear(self);
660661
Py_TYPE(self)->tp_free(self);
661662
}
@@ -1793,7 +1794,8 @@ encoder_listencode_list(PyEncoderObject *s, _PyAccu *acc,
17931794
static void
17941795
encoder_dealloc(PyObject *self)
17951796
{
1796-
/* Deallocate Encoder */
1797+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1798+
PyObject_GC_UnTrack(self);
17971799
encoder_clear(self);
17981800
Py_TYPE(self)->tp_free(self);
17991801
}

Modules/_ssl.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2776,6 +2776,8 @@ context_clear(PySSLContext *self)
27762776
static void
27772777
context_dealloc(PySSLContext *self)
27782778
{
2779+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2780+
PyObject_GC_UnTrack(self);
27792781
context_clear(self);
27802782
SSL_CTX_free(self->ctx);
27812783
#ifdef OPENSSL_NPN_NEGOTIATED
@@ -4284,6 +4286,7 @@ static PyTypeObject PySSLMemoryBIO_Type = {
42844286
static void
42854287
PySSLSession_dealloc(PySSLSession *self)
42864288
{
4289+
/* bpo-31095: UnTrack is needed before calling any callbacks */
42874290
PyObject_GC_UnTrack(self);
42884291
Py_XDECREF(self->ctx);
42894292
if (self->session != NULL) {

Modules/_struct.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,8 @@ typedef struct {
16051605
static void
16061606
unpackiter_dealloc(unpackiterobject *self)
16071607
{
1608+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1609+
PyObject_GC_UnTrack(self);
16081610
Py_XDECREF(self->so);
16091611
PyBuffer_Release(&self->buf);
16101612
PyObject_GC_Del(self);

Objects/dictobject.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,8 @@ dict_dealloc(PyDictObject *mp)
20062006
PyObject **values = mp->ma_values;
20072007
PyDictKeysObject *keys = mp->ma_keys;
20082008
Py_ssize_t i, n;
2009+
2010+
/* bpo-31095: UnTrack is needed before calling any callbacks */
20092011
PyObject_GC_UnTrack(mp);
20102012
Py_TRASHCAN_SAFE_BEGIN(mp)
20112013
if (values != NULL) {
@@ -3432,6 +3434,8 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
34323434
static void
34333435
dictiter_dealloc(dictiterobject *di)
34343436
{
3437+
/* bpo-31095: UnTrack is needed before calling any callbacks */
3438+
_PyObject_GC_UNTRACK(di);
34353439
Py_XDECREF(di->di_dict);
34363440
Py_XDECREF(di->di_result);
34373441
PyObject_GC_Del(di);
@@ -3800,6 +3804,8 @@ dictiter_reduce(dictiterobject *di)
38003804
static void
38013805
dictview_dealloc(_PyDictViewObject *dv)
38023806
{
3807+
/* bpo-31095: UnTrack is needed before calling any callbacks */
3808+
_PyObject_GC_UNTRACK(dv);
38033809
Py_XDECREF(dv->dv_dict);
38043810
PyObject_GC_Del(dv);
38053811
}

Objects/setobject.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ set_dealloc(PySetObject *so)
556556
setentry *entry;
557557
Py_ssize_t used = so->used;
558558

559+
/* bpo-31095: UnTrack is needed before calling any callbacks */
559560
PyObject_GC_UnTrack(so);
560561
Py_TRASHCAN_SAFE_BEGIN(so)
561562
if (so->weakreflist != NULL)
@@ -812,6 +813,8 @@ typedef struct {
812813
static void
813814
setiter_dealloc(setiterobject *si)
814815
{
816+
/* bpo-31095: UnTrack is needed before calling any callbacks */
817+
_PyObject_GC_UNTRACK(si);
815818
Py_XDECREF(si->si_set);
816819
PyObject_GC_Del(si);
817820
}

Parser/asdl_c.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,8 @@ def visitModule(self, mod):
630630
static void
631631
ast_dealloc(AST_object *self)
632632
{
633+
/* bpo-31095: UnTrack is needed before calling any callbacks */
634+
PyObject_GC_UnTrack(self);
633635
Py_CLEAR(self->dict);
634636
Py_TYPE(self)->tp_free(self);
635637
}

Python/Python-ast.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ typedef struct {
512512
static void
513513
ast_dealloc(AST_object *self)
514514
{
515+
/* bpo-31095: UnTrack is needed before calling any callbacks */
516+
PyObject_GC_UnTrack(self);
515517
Py_CLEAR(self->dict);
516518
Py_TYPE(self)->tp_free(self);
517519
}

0 commit comments

Comments
 (0)