#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; }