| 
									
										
										
										
											2023-05-12 11:26:07 +02:00
										 |  |  | #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. */ | 
					
						
							| 
									
										
										
										
											2024-05-06 10:50:35 -07:00
										 |  |  |     del = _PyType_LookupRef(Py_TYPE(self), tp_del); | 
					
						
							| 
									
										
										
										
											2023-05-12 11:26:07 +02:00
										 |  |  |     Py_DECREF(tp_del); | 
					
						
							|  |  |  |     if (del != NULL) { | 
					
						
							|  |  |  |         res = PyObject_CallOneArg(del, self); | 
					
						
							| 
									
										
										
										
											2024-05-06 10:50:35 -07:00
										 |  |  |         Py_DECREF(del); | 
					
						
							| 
									
										
										
										
											2023-05-12 11:26:07 +02:00
										 |  |  |         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. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-01-05 22:12:26 -05:00
										 |  |  |         _Py_ResurrectReference(self); | 
					
						
							| 
									
										
										
										
											2023-05-12 11:26:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     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; | 
					
						
							|  |  |  | } |