-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
6 changed files
with
353 additions
and
327 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,344 @@ | ||
#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_Lookup(Py_TYPE(self), tp_del); | ||
Py_DECREF(tp_del); | ||
if (del != NULL) { | ||
res = PyObject_CallOneArg(del, self); | ||
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_ssize_t refcnt = Py_REFCNT(self); | ||
_Py_NewReferenceNoTotal(self); | ||
Py_SET_REFCNT(self, refcnt); | ||
} | ||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.