Skip to content

Commit 0fcc033

Browse files
methanelarryhastings
authored andcommitted
bpo-31095: fix potential crash during GC (GH-2974) (#3196)
(cherry picked from commit a6296d3)
1 parent 44c1b62 commit 0fcc033

File tree

14 files changed

+60
-14
lines changed

14 files changed

+60
-14
lines changed

Doc/extending/newtypes.rst

+20-9
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

+1
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
}
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

+4
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,8 @@ dequeiter_traverse(dequeiterobject *dio, visitproc visit, void *arg)
16411641
static void
16421642
dequeiter_dealloc(dequeiterobject *dio)
16431643
{
1644+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1645+
PyObject_GC_UnTrack(dio);
16441646
Py_XDECREF(dio->deque);
16451647
PyObject_GC_Del(dio);
16461648
}
@@ -2021,6 +2023,8 @@ static PyMemberDef defdict_members[] = {
20212023
static void
20222024
defdict_dealloc(defdictobject *dd)
20232025
{
2026+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2027+
PyObject_GC_UnTrack(dd);
20242028
Py_CLEAR(dd->default_factory);
20252029
PyDict_Type.tp_dealloc((PyObject *)dd);
20262030
}

Modules/_elementtree.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ element_gc_clear(ElementObject *self)
653653
static void
654654
element_dealloc(ElementObject* self)
655655
{
656+
/* bpo-31095: UnTrack is needed before calling any callbacks */
656657
PyObject_GC_UnTrack(self);
657658
Py_TRASHCAN_SAFE_BEGIN(self)
658659

@@ -2025,6 +2026,9 @@ static void
20252026
elementiter_dealloc(ElementIterObject *it)
20262027
{
20272028
ParentLocator *p = it->parent_stack;
2029+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2030+
PyObject_GC_UnTrack(it);
2031+
20282032
while (p) {
20292033
ParentLocator *temp = p;
20302034
Py_XDECREF(p->parent);
@@ -2034,8 +2038,6 @@ elementiter_dealloc(ElementIterObject *it)
20342038

20352039
Py_XDECREF(it->sought_tag);
20362040
Py_XDECREF(it->root_element);
2037-
2038-
PyObject_GC_UnTrack(it);
20392041
PyObject_GC_Del(it);
20402042
}
20412043

Modules/_functoolsmodule.c

+6-1
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);
@@ -1039,7 +1040,11 @@ lru_cache_clear_list(lru_list_elem *link)
10391040
static void
10401041
lru_cache_dealloc(lru_cache_object *obj)
10411042
{
1042-
lru_list_elem *list = lru_cache_unlink_list(obj);
1043+
lru_list_elem *list;
1044+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1045+
PyObject_GC_UnTrack(obj);
1046+
1047+
list = lru_cache_unlink_list(obj);
10431048
Py_XDECREF(obj->maxsize_O);
10441049
Py_XDECREF(obj->func);
10451050
Py_XDECREF(obj->cache);

Modules/_io/bytesio.c

+2
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,8 @@ bytesiobuf_traverse(bytesiobuf *self, visitproc visit, void *arg)
11331133
static void
11341134
bytesiobuf_dealloc(bytesiobuf *self)
11351135
{
1136+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1137+
PyObject_GC_UnTrack(self);
11361138
Py_CLEAR(self->source);
11371139
Py_TYPE(self)->tp_free(self);
11381140
}

Modules/_json.c

+4-2
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

+2
Original file line numberDiff line numberDiff line change
@@ -2465,6 +2465,8 @@ context_clear(PySSLContext *self)
24652465
static void
24662466
context_dealloc(PySSLContext *self)
24672467
{
2468+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2469+
PyObject_GC_UnTrack(self);
24682470
context_clear(self);
24692471
SSL_CTX_free(self->ctx);
24702472
#ifdef OPENSSL_NPN_NEGOTIATED

Modules/_struct.c

+2
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,8 @@ typedef struct {
15811581
static void
15821582
unpackiter_dealloc(unpackiterobject *self)
15831583
{
1584+
/* bpo-31095: UnTrack is needed before calling any callbacks */
1585+
PyObject_GC_UnTrack(self);
15841586
Py_XDECREF(self->so);
15851587
PyBuffer_Release(&self->buf);
15861588
PyObject_GC_Del(self);

Objects/dictobject.c

+6
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,8 @@ dict_dealloc(PyDictObject *mp)
16361636
PyObject **values = mp->ma_values;
16371637
PyDictKeysObject *keys = mp->ma_keys;
16381638
Py_ssize_t i, n;
1639+
1640+
/* bpo-31095: UnTrack is needed before calling any callbacks */
16391641
PyObject_GC_UnTrack(mp);
16401642
Py_TRASHCAN_SAFE_BEGIN(mp)
16411643
if (values != NULL) {
@@ -2961,6 +2963,8 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
29612963
static void
29622964
dictiter_dealloc(dictiterobject *di)
29632965
{
2966+
/* bpo-31095: UnTrack is needed before calling any callbacks */
2967+
_PyObject_GC_UNTRACK(di);
29642968
Py_XDECREF(di->di_dict);
29652969
Py_XDECREF(di->di_result);
29662970
PyObject_GC_Del(di);
@@ -3319,6 +3323,8 @@ dictiter_reduce(dictiterobject *di)
33193323
static void
33203324
dictview_dealloc(_PyDictViewObject *dv)
33213325
{
3326+
/* bpo-31095: UnTrack is needed before calling any callbacks */
3327+
_PyObject_GC_UNTRACK(dv);
33223328
Py_XDECREF(dv->dv_dict);
33233329
PyObject_GC_Del(dv);
33243330
}

Objects/setobject.c

+3
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ set_dealloc(PySetObject *so)
472472
setentry *entry;
473473
Py_ssize_t used = so->used;
474474

475+
/* bpo-31095: UnTrack is needed before calling any callbacks */
475476
PyObject_GC_UnTrack(so);
476477
Py_TRASHCAN_SAFE_BEGIN(so)
477478
if (so->weakreflist != NULL)
@@ -736,6 +737,8 @@ typedef struct {
736737
static void
737738
setiter_dealloc(setiterobject *si)
738739
{
740+
/* bpo-31095: UnTrack is needed before calling any callbacks */
741+
_PyObject_GC_UNTRACK(si);
739742
Py_XDECREF(si->si_set);
740743
PyObject_GC_Del(si);
741744
}

Parser/asdl_c.py

+2
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

+2
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,8 @@ typedef struct {
486486
static void
487487
ast_dealloc(AST_object *self)
488488
{
489+
/* bpo-31095: UnTrack is needed before calling any callbacks */
490+
PyObject_GC_UnTrack(self);
489491
Py_CLEAR(self->dict);
490492
Py_TYPE(self)->tp_free(self);
491493
}

0 commit comments

Comments
 (0)