| #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_Free; | |
| 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_FormatUnraisable("Exception ignored while deallocating"); | |
| 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_FormatUnraisable("Exception ignored while calling " | |
| "deallocator %R", 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_call_no_args(const char *method) | |
| { | |
| PyObject *gc = PyImport_ImportModule("gc"); | |
| if (gc == NULL) { | |
| return -1; | |
| } | |
| PyObject *res = PyObject_CallMethod(gc, method, NULL); | |
| Py_DECREF(gc); | |
| if (res == NULL) { | |
| return -1; | |
| } | |
| Py_DECREF(res); | |
| return 0; | |
| } | |
| // gh-131740: frozen objects must be visited too. | |
| static PyObject * | |
| test_gc_visit_objects_frozen(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; | |
| } | |
| if (gc_call_no_args("freeze") < 0) { | |
| Py_DECREF(obj); | |
| return NULL; | |
| } | |
| state.target = obj; | |
| state.found = 0; | |
| PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state); | |
| int err = gc_call_no_args("unfreeze"); | |
| Py_DECREF(obj); | |
| if (err < 0) { | |
| return NULL; | |
| } | |
| if (!state.found) { | |
| PyErr_SetString( | |
| PyExc_AssertionError, | |
| "test_gc_visit_objects_frozen: Didn't find frozen 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", obj_extra_data_get, 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_frozen", test_gc_visit_objects_frozen, 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; | |
| } |