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.
This commit is contained in:
Victor Stinner 2021-09-21 23:04:34 +02:00 committed by GitHub
parent f604cf1c37
commit 79a3148099
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 5 deletions

View File

@ -90,6 +90,12 @@ extern PyObject* _Py_Offer_Suggestions(PyObject* exception);
PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b,
Py_ssize_t max_cost);
PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
const char *func,
const char *message);
#define _Py_FatalRefcountError(message) _Py_FatalRefcountErrorFunc(__func__, message)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,4 @@
Add a deallocator to the bool type to detect refcount bugs in C extensions
which call Py_DECREF(Py_True) or Py_DECREF(Py_False) by mistake. Detect also
refcount bugs when the empty tuple singleton is destroyed by mistake. Patch
by Victor Stinner.

View File

@ -1,6 +1,7 @@
/* Boolean type, a subtype of int */
#include "Python.h"
#include "pycore_pyerrors.h" // _Py_FatalRefcountError()
#include "longintrepr.h"
/* We define bool_repr to return "False" or "True" */
@ -156,8 +157,7 @@ static PyNumberMethods bool_as_number = {
static void _Py_NO_RETURN
bool_dealloc(PyObject* Py_UNUSED(ignore))
{
Py_FatalError("deallocating True or False likely caused by "
"a refcount bug in a C extension");
_Py_FatalRefcountError("deallocating True or False");
}
/* The type object for bool. Note that this cannot be subclassed! */

View File

@ -1563,8 +1563,7 @@ none_repr(PyObject *op)
static void _Py_NO_RETURN
none_dealloc(PyObject* Py_UNUSED(ignore))
{
Py_FatalError("deallocating None likely caused by a refcount bug "
"in a C extension");
_Py_FatalRefcountError("deallocating None");
}
static PyObject *

View File

@ -6,6 +6,7 @@
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_object.h" // _PyObject_GC_TRACK()
#include "pycore_pyerrors.h" // _Py_FatalRefcountError()
/*[clinic input]
class tuple "PyTupleObject *" "&PyTuple_Type"
@ -287,6 +288,17 @@ tupledealloc(PyTupleObject *op)
}
#endif
}
#if defined(Py_DEBUG) && PyTuple_MAXSAVESIZE > 0
else {
assert(len == 0);
struct _Py_tuple_state *state = get_tuple_state();
// The empty tuple singleton must only be deallocated by
// _PyTuple_Fini(): not before, not after
if (op == state->free_list[0] && state->numfree[0] != 0) {
_Py_FatalRefcountError("deallocating the empty tuple singleton");
}
}
#endif
Py_TYPE(op)->tp_free((PyObject *)op);
#if PyTuple_MAXSAVESIZE > 0
@ -1048,11 +1060,16 @@ _PyTuple_Fini(PyInterpreterState *interp)
struct _Py_tuple_state *state = &interp->tuple;
// The empty tuple singleton must not be tracked by the GC
assert(!_PyObject_GC_IS_TRACKED(state->free_list[0]));
#ifdef Py_DEBUG
state->numfree[0] = 0;
#endif
Py_CLEAR(state->free_list[0]);
_PyTuple_ClearFreeList(interp);
#ifdef Py_DEBUG
state->numfree[0] = -1;
#endif
_PyTuple_ClearFreeList(interp);
#endif
}

View File

@ -2805,6 +2805,16 @@ _Py_FatalErrorFormat(const char *func, const char *format, ...)
}
void _Py_NO_RETURN
_Py_FatalRefcountErrorFunc(const char *func, const char *msg)
{
_Py_FatalErrorFormat(func,
"%s: bug likely caused by a refcount error "
"in a C extension",
msg);
}
void _Py_NO_RETURN
Py_ExitStatusException(PyStatus status)
{