Skip to content

bpo-45061: Detect refcount bug on empty tuple singleton #28503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Include/internal/pycore_pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions Objects/boolobject.c
Original file line number Diff line number Diff line change
@@ -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" */
Expand Down Expand Up @@ -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! */
Expand Down
3 changes: 1 addition & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
19 changes: 18 additions & 1 deletion Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 10 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down