Skip to content

Commit 79a3148

Browse files
authored
bpo-45061: Detect refcount bug on empty tuple singleton (GH-28503)
Detect refcount bugs in C extensions when the empty tuple singleton is destroyed by mistake. Add the _Py_FatalRefcountErrorFunc() function.
1 parent f604cf1 commit 79a3148

File tree

6 files changed

+41
-5
lines changed

6 files changed

+41
-5
lines changed

Include/internal/pycore_pyerrors.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ extern PyObject* _Py_Offer_Suggestions(PyObject* exception);
9090
PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b,
9191
Py_ssize_t max_cost);
9292

93+
PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
94+
const char *func,
95+
const char *message);
96+
97+
#define _Py_FatalRefcountError(message) _Py_FatalRefcountErrorFunc(__func__, message)
98+
9399
#ifdef __cplusplus
94100
}
95101
#endif
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add a deallocator to the bool type to detect refcount bugs in C extensions
2+
which call Py_DECREF(Py_True) or Py_DECREF(Py_False) by mistake. Detect also
3+
refcount bugs when the empty tuple singleton is destroyed by mistake. Patch
4+
by Victor Stinner.

Objects/boolobject.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* Boolean type, a subtype of int */
22

33
#include "Python.h"
4+
#include "pycore_pyerrors.h" // _Py_FatalRefcountError()
45
#include "longintrepr.h"
56

67
/* We define bool_repr to return "False" or "True" */
@@ -156,8 +157,7 @@ static PyNumberMethods bool_as_number = {
156157
static void _Py_NO_RETURN
157158
bool_dealloc(PyObject* Py_UNUSED(ignore))
158159
{
159-
Py_FatalError("deallocating True or False likely caused by "
160-
"a refcount bug in a C extension");
160+
_Py_FatalRefcountError("deallocating True or False");
161161
}
162162

163163
/* The type object for bool. Note that this cannot be subclassed! */

Objects/object.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,8 +1563,7 @@ none_repr(PyObject *op)
15631563
static void _Py_NO_RETURN
15641564
none_dealloc(PyObject* Py_UNUSED(ignore))
15651565
{
1566-
Py_FatalError("deallocating None likely caused by a refcount bug "
1567-
"in a C extension");
1566+
_Py_FatalRefcountError("deallocating None");
15681567
}
15691568

15701569
static PyObject *

Objects/tupleobject.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
77
#include "pycore_initconfig.h" // _PyStatus_OK()
88
#include "pycore_object.h" // _PyObject_GC_TRACK()
9+
#include "pycore_pyerrors.h" // _Py_FatalRefcountError()
910

1011
/*[clinic input]
1112
class tuple "PyTupleObject *" "&PyTuple_Type"
@@ -287,6 +288,17 @@ tupledealloc(PyTupleObject *op)
287288
}
288289
#endif
289290
}
291+
#if defined(Py_DEBUG) && PyTuple_MAXSAVESIZE > 0
292+
else {
293+
assert(len == 0);
294+
struct _Py_tuple_state *state = get_tuple_state();
295+
// The empty tuple singleton must only be deallocated by
296+
// _PyTuple_Fini(): not before, not after
297+
if (op == state->free_list[0] && state->numfree[0] != 0) {
298+
_Py_FatalRefcountError("deallocating the empty tuple singleton");
299+
}
300+
}
301+
#endif
290302
Py_TYPE(op)->tp_free((PyObject *)op);
291303

292304
#if PyTuple_MAXSAVESIZE > 0
@@ -1048,11 +1060,16 @@ _PyTuple_Fini(PyInterpreterState *interp)
10481060
struct _Py_tuple_state *state = &interp->tuple;
10491061
// The empty tuple singleton must not be tracked by the GC
10501062
assert(!_PyObject_GC_IS_TRACKED(state->free_list[0]));
1063+
1064+
#ifdef Py_DEBUG
1065+
state->numfree[0] = 0;
1066+
#endif
10511067
Py_CLEAR(state->free_list[0]);
1052-
_PyTuple_ClearFreeList(interp);
10531068
#ifdef Py_DEBUG
10541069
state->numfree[0] = -1;
10551070
#endif
1071+
1072+
_PyTuple_ClearFreeList(interp);
10561073
#endif
10571074
}
10581075

Python/pylifecycle.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,6 +2805,16 @@ _Py_FatalErrorFormat(const char *func, const char *format, ...)
28052805
}
28062806

28072807

2808+
void _Py_NO_RETURN
2809+
_Py_FatalRefcountErrorFunc(const char *func, const char *msg)
2810+
{
2811+
_Py_FatalErrorFormat(func,
2812+
"%s: bug likely caused by a refcount error "
2813+
"in a C extension",
2814+
msg);
2815+
}
2816+
2817+
28082818
void _Py_NO_RETURN
28092819
Py_ExitStatusException(PyStatus status)
28102820
{

0 commit comments

Comments
 (0)