0
0
mirror of https://github.com/python/cpython.git synced 2024-11-24 08:52:25 +01:00
cpython/Modules/_testcapi/gc.c
Dino Viehland 5a1618a2c8
gh-118362: Fix thread safety around lookups from the type cache in the face of concurrent mutators (#118454)
Add _PyType_LookupRef and use incref before setting attribute on type
Makes setting an attribute on a class and signaling type modified atomic
Avoid adding re-entrancy exposing the type cache in an inconsistent state by decrefing after type is updated
2024-05-06 10:50:35 -07:00

344 lines
8.3 KiB
C

#include "parts.h"
static PyObject*
test_gc_control(PyObject *self, PyObject *Py_UNUSED(ignored))
{
int orig_enabled = PyGC_IsEnabled();
const char* msg = "ok";
int old_state;
old_state = PyGC_Enable();
msg = "Enable(1)";
if (old_state != orig_enabled) {
goto failed;
}
msg = "IsEnabled(1)";
if (!PyGC_IsEnabled()) {
goto failed;
}
old_state = PyGC_Disable();
msg = "disable(2)";
if (!old_state) {
goto failed;
}
msg = "IsEnabled(2)";
if (PyGC_IsEnabled()) {
goto failed;
}
old_state = PyGC_Enable();
msg = "enable(3)";
if (old_state) {
goto failed;
}
msg = "IsEnabled(3)";
if (!PyGC_IsEnabled()) {
goto failed;
}
if (!orig_enabled) {
old_state = PyGC_Disable();
msg = "disable(4)";
if (old_state) {
goto failed;
}
msg = "IsEnabled(4)";
if (PyGC_IsEnabled()) {
goto failed;
}
}
Py_RETURN_NONE;
failed:
/* Try to clean up if we can. */
if (orig_enabled) {
PyGC_Enable();
} else {
PyGC_Disable();
}
PyErr_Format(PyExc_ValueError, "GC control failed in %s", msg);
return NULL;
}
static PyObject *
without_gc(PyObject *Py_UNUSED(self), PyObject *obj)
{
PyTypeObject *tp = (PyTypeObject*)obj;
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
return PyErr_Format(PyExc_TypeError, "heap type expected, got %R", obj);
}
if (PyType_IS_GC(tp)) {
// Don't try this at home, kids:
tp->tp_flags -= Py_TPFLAGS_HAVE_GC;
tp->tp_free = PyObject_Del;
tp->tp_traverse = NULL;
tp->tp_clear = NULL;
}
assert(!PyType_IS_GC(tp));
return Py_NewRef(obj);
}
static void
slot_tp_del(PyObject *self)
{
PyObject *del, *res;
/* Temporarily resurrect the object. */
assert(Py_REFCNT(self) == 0);
Py_SET_REFCNT(self, 1);
/* Save the current exception, if any. */
PyObject *exc = PyErr_GetRaisedException();
PyObject *tp_del = PyUnicode_InternFromString("__tp_del__");
if (tp_del == NULL) {
PyErr_WriteUnraisable(NULL);
PyErr_SetRaisedException(exc);
return;
}
/* Execute __del__ method, if any. */
del = _PyType_LookupRef(Py_TYPE(self), tp_del);
Py_DECREF(tp_del);
if (del != NULL) {
res = PyObject_CallOneArg(del, self);
Py_DECREF(del);
if (res == NULL)
PyErr_WriteUnraisable(del);
else
Py_DECREF(res);
}
/* Restore the saved exception. */
PyErr_SetRaisedException(exc);
/* Undo the temporary resurrection; can't use DECREF here, it would
* cause a recursive call.
*/
assert(Py_REFCNT(self) > 0);
Py_SET_REFCNT(self, Py_REFCNT(self) - 1);
if (Py_REFCNT(self) == 0) {
/* this is the normal path out */
return;
}
/* __del__ resurrected it! Make it look like the original Py_DECREF
* never happened.
*/
{
_Py_ResurrectReference(self);
}
assert(!PyType_IS_GC(Py_TYPE(self)) || PyObject_GC_IsTracked(self));
}
static PyObject *
with_tp_del(PyObject *self, PyObject *args)
{
PyObject *obj;
PyTypeObject *tp;
if (!PyArg_ParseTuple(args, "O:with_tp_del", &obj))
return NULL;
tp = (PyTypeObject *) obj;
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
PyErr_Format(PyExc_TypeError,
"heap type expected, got %R", obj);
return NULL;
}
tp->tp_del = slot_tp_del;
return Py_NewRef(obj);
}
struct gc_visit_state_basic {
PyObject *target;
int found;
};
static int
gc_visit_callback_basic(PyObject *obj, void *arg)
{
struct gc_visit_state_basic *state = (struct gc_visit_state_basic *)arg;
if (obj == state->target) {
state->found = 1;
return 0;
}
return 1;
}
static PyObject *
test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
PyObject *obj;
struct gc_visit_state_basic state;
obj = PyList_New(0);
if (obj == NULL) {
return NULL;
}
state.target = obj;
state.found = 0;
PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
Py_DECREF(obj);
if (!state.found) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_basic: Didn't find live list");
return NULL;
}
Py_RETURN_NONE;
}
static int
gc_visit_callback_exit_early(PyObject *obj, void *arg)
{
int *visited_i = (int *)arg;
(*visited_i)++;
if (*visited_i == 2) {
return 0;
}
return 1;
}
static PyObject *
test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
int visited_i = 0;
PyUnstable_GC_VisitObjects(gc_visit_callback_exit_early, &visited_i);
if (visited_i != 2) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_exit_early: did not exit when expected");
}
Py_RETURN_NONE;
}
typedef struct {
PyObject_HEAD
} ObjExtraData;
static PyObject *
obj_extra_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
size_t extra_size = sizeof(PyObject *);
PyObject *obj = PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
if (obj == NULL) {
return PyErr_NoMemory();
}
PyObject_GC_Track(obj);
return obj;
}
static PyObject **
obj_extra_data_get_extra_storage(PyObject *self)
{
return (PyObject **)((char *)self + Py_TYPE(self)->tp_basicsize);
}
static PyObject *
obj_extra_data_get(PyObject *self, void *Py_UNUSED(ignored))
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
PyObject *value = *extra_storage;
if (!value) {
Py_RETURN_NONE;
}
return Py_NewRef(value);
}
static int
obj_extra_data_set(PyObject *self, PyObject *newval, void *Py_UNUSED(ignored))
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
Py_CLEAR(*extra_storage);
if (newval) {
*extra_storage = Py_NewRef(newval);
}
return 0;
}
static PyGetSetDef obj_extra_data_getset[] = {
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
{NULL}
};
static int
obj_extra_data_traverse(PyObject *self, visitproc visit, void *arg)
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
PyObject *value = *extra_storage;
Py_VISIT(value);
return 0;
}
static int
obj_extra_data_clear(PyObject *self)
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
Py_CLEAR(*extra_storage);
return 0;
}
static void
obj_extra_data_dealloc(PyObject *self)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
obj_extra_data_clear(self);
tp->tp_free(self);
Py_DECREF(tp);
}
static PyType_Slot ObjExtraData_Slots[] = {
{Py_tp_getset, obj_extra_data_getset},
{Py_tp_dealloc, obj_extra_data_dealloc},
{Py_tp_traverse, obj_extra_data_traverse},
{Py_tp_clear, obj_extra_data_clear},
{Py_tp_new, obj_extra_data_new},
{Py_tp_free, PyObject_GC_Del},
{0, NULL},
};
static PyType_Spec ObjExtraData_TypeSpec = {
.name = "_testcapi.ObjExtraData",
.basicsize = sizeof(ObjExtraData),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.slots = ObjExtraData_Slots,
};
static PyMethodDef test_methods[] = {
{"test_gc_control", test_gc_control, METH_NOARGS},
{"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL},
{"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL},
{"without_gc", without_gc, METH_O, NULL},
{"with_tp_del", with_tp_del, METH_VARARGS, NULL},
{NULL}
};
int _PyTestCapi_Init_GC(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
PyObject *ObjExtraData_Type = PyType_FromModuleAndSpec(
mod, &ObjExtraData_TypeSpec, NULL);
if (ObjExtraData_Type == 0) {
return -1;
}
int ret = PyModule_AddType(mod, (PyTypeObject*)ObjExtraData_Type);
Py_DECREF(ObjExtraData_Type);
if (ret < 0) {
return ret;
}
return 0;
}