| #include "parts.h" |
| #include "util.h" |
| |
| static PyObject * |
| call_pyobject_print(PyObject *self, PyObject * args) |
| { |
| PyObject *object; |
| PyObject *filename; |
| PyObject *print_raw; |
| FILE *fp; |
| int flags = 0; |
| |
| if (!PyArg_UnpackTuple(args, "call_pyobject_print", 3, 3, |
| &object, &filename, &print_raw)) { |
| return NULL; |
| } |
| |
| fp = Py_fopen(filename, "w+"); |
| if (fp == NULL) { |
| return NULL; |
| } |
| |
| if (Py_IsTrue(print_raw)) { |
| flags = Py_PRINT_RAW; |
| } |
| |
| if (PyObject_Print(object, fp, flags) < 0) { |
| fclose(fp); |
| return NULL; |
| } |
| |
| fclose(fp); |
| |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| pyobject_print_null(PyObject *self, PyObject *filename) |
| { |
| FILE *fp; |
| |
| fp = Py_fopen(filename, "w+"); |
| if (fp == NULL) { |
| return NULL; |
| } |
| |
| if (PyObject_Print(NULL, fp, 0) < 0) { |
| fclose(fp); |
| return NULL; |
| } |
| |
| fclose(fp); |
| |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| pyobject_print_noref_object(PyObject *self, PyObject *filename) |
| { |
| PyObject *test_string; |
| FILE *fp; |
| char correct_string[100]; |
| |
| test_string = PyUnicode_FromString("Spam spam spam"); |
| if (test_string == NULL) { |
| return NULL; |
| } |
| |
| Py_SET_REFCNT(test_string, 0); |
| |
| PyOS_snprintf(correct_string, 100, "<refcnt %zd at %p>", |
| Py_REFCNT(test_string), (void *)test_string); |
| |
| fp = Py_fopen(filename, "w+"); |
| if (fp == NULL) { |
| Py_SET_REFCNT(test_string, 1); |
| Py_DECREF(test_string); |
| return NULL; |
| } |
| |
| if (PyObject_Print(test_string, fp, 0) < 0){ |
| fclose(fp); |
| Py_SET_REFCNT(test_string, 1); |
| Py_DECREF(test_string); |
| return NULL; |
| } |
| |
| fclose(fp); |
| |
| Py_SET_REFCNT(test_string, 1); |
| Py_DECREF(test_string); |
| |
| return PyUnicode_FromString(correct_string); |
| } |
| |
| static PyObject * |
| pyobject_print_os_error(PyObject *self, PyObject *filename) |
| { |
| PyObject *test_string; |
| FILE *fp; |
| |
| test_string = PyUnicode_FromString("Spam spam spam"); |
| if (test_string == NULL) { |
| return NULL; |
| } |
| |
| // open file in read mode to induce OSError |
| fp = Py_fopen(filename, "r"); |
| if (fp == NULL) { |
| Py_DECREF(test_string); |
| return NULL; |
| } |
| |
| if (PyObject_Print(test_string, fp, 0) < 0) { |
| fclose(fp); |
| Py_DECREF(test_string); |
| return NULL; |
| } |
| |
| fclose(fp); |
| Py_DECREF(test_string); |
| |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| pyobject_clear_weakrefs_no_callbacks(PyObject *self, PyObject *obj) |
| { |
| PyUnstable_Object_ClearWeakRefsNoCallbacks(obj); |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) |
| { |
| int result = PyUnstable_Object_EnableDeferredRefcount(obj); |
| return PyLong_FromLong(result); |
| } |
| |
| static PyObject * |
| pyobject_is_unique_temporary(PyObject *self, PyObject *obj) |
| { |
| int result = PyUnstable_Object_IsUniqueReferencedTemporary(obj); |
| return PyLong_FromLong(result); |
| } |
| |
| static PyObject * |
| pyobject_is_unique_temporary_new_object(PyObject *self, PyObject *unused) |
| { |
| PyObject *obj = PyList_New(0); |
| int result = PyUnstable_Object_IsUniqueReferencedTemporary(obj); |
| Py_DECREF(obj); |
| return PyLong_FromLong(result); |
| } |
| |
| static int MyObject_dealloc_called = 0; |
| |
| static void |
| MyObject_dealloc(PyObject *op) |
| { |
| // PyUnstable_TryIncRef should return 0 if object is being deallocated |
| assert(Py_REFCNT(op) == 0); |
| assert(!PyUnstable_TryIncRef(op)); |
| assert(Py_REFCNT(op) == 0); |
| |
| MyObject_dealloc_called++; |
| Py_TYPE(op)->tp_free(op); |
| } |
| |
| static PyTypeObject MyType = { |
| PyVarObject_HEAD_INIT(NULL, 0) |
| .tp_name = "MyType", |
| .tp_basicsize = sizeof(PyObject), |
| .tp_dealloc = MyObject_dealloc, |
| }; |
| |
| static PyObject * |
| test_py_try_inc_ref(PyObject *self, PyObject *unused) |
| { |
| if (PyType_Ready(&MyType) < 0) { |
| return NULL; |
| } |
| |
| MyObject_dealloc_called = 0; |
| |
| PyObject *op = PyObject_New(PyObject, &MyType); |
| if (op == NULL) { |
| return NULL; |
| } |
| |
| PyUnstable_EnableTryIncRef(op); |
| #ifdef Py_GIL_DISABLED |
| // PyUnstable_EnableTryIncRef sets the shared flags to |
| // `_Py_REF_MAYBE_WEAKREF` if the flags are currently zero to ensure that |
| // the shared reference count is merged on deallocation. |
| assert((op->ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) >= _Py_REF_MAYBE_WEAKREF); |
| #endif |
| |
| if (!PyUnstable_TryIncRef(op)) { |
| PyErr_SetString(PyExc_AssertionError, "PyUnstable_TryIncRef failed"); |
| Py_DECREF(op); |
| return NULL; |
| } |
| Py_DECREF(op); // undo try-incref |
| Py_DECREF(op); // dealloc |
| assert(MyObject_dealloc_called == 1); |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| test_py_set_immortal(PyObject *self, PyObject *unused) |
| { |
| // the object is allocated on C stack as otherwise, |
| // it would trip the refleak checker when the object |
| // is made immortal and leak memory, for the same |
| // reason we cannot call PyObject_Init() on it. |
| PyObject object = {0}; |
| #ifdef Py_GIL_DISABLED |
| object.ob_tid = _Py_ThreadId(); |
| object.ob_gc_bits = 0; |
| object.ob_ref_local = 1; |
| object.ob_ref_shared = 0; |
| #else |
| object.ob_refcnt = 1; |
| #endif |
| object.ob_type = &PyBaseObject_Type; |
| |
| assert(!PyUnstable_IsImmortal(&object)); |
| int rc = PyUnstable_SetImmortal(&object); |
| assert(rc == 1); |
| assert(PyUnstable_IsImmortal(&object)); |
| Py_DECREF(&object); // should not dealloc |
| assert(PyUnstable_IsImmortal(&object)); |
| |
| // Check already immortal object |
| rc = PyUnstable_SetImmortal(&object); |
| assert(rc == 0); |
| |
| // Check unicode objects |
| PyObject *unicode = PyUnicode_FromString("test"); |
| assert(!PyUnstable_IsImmortal(unicode)); |
| rc = PyUnstable_SetImmortal(unicode); |
| assert(rc == 0); |
| assert(!PyUnstable_IsImmortal(unicode)); |
| Py_DECREF(unicode); |
| Py_RETURN_NONE; |
| } |
| |
| #ifdef Py_REF_DEBUG |
| static PyObject * |
| negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) |
| { |
| PyObject *obj = PyUnicode_FromString("negative_refcount"); |
| if (obj == NULL) { |
| return NULL; |
| } |
| assert(Py_REFCNT(obj) == 1); |
| |
| Py_SET_REFCNT(obj, 0); |
| /* Py_DECREF() must call _Py_NegativeRefcount() and abort Python */ |
| Py_DECREF(obj); |
| |
| Py_RETURN_NONE; |
| } |
| |
| |
| static PyObject * |
| decref_freed_object(PyObject *self, PyObject *Py_UNUSED(args)) |
| { |
| PyObject *obj = PyUnicode_FromString("decref_freed_object"); |
| if (obj == NULL) { |
| return NULL; |
| } |
| assert(Py_REFCNT(obj) == 1); |
| |
| // Deallocate the memory |
| Py_DECREF(obj); |
| // obj is a now a dangling pointer |
| |
| // gh-109496: If Python is built in debug mode, Py_DECREF() must call |
| // _Py_NegativeRefcount() and abort Python. |
| Py_DECREF(obj); |
| |
| Py_RETURN_NONE; |
| } |
| #endif |
| |
| |
| static PyObject * |
| clear_managed_dict(PyObject *self, PyObject *obj) |
| { |
| PyObject_ClearManagedDict(obj); |
| Py_RETURN_NONE; |
| } |
| |
| |
| static PyObject * |
| is_uniquely_referenced(PyObject *self, PyObject *op) |
| { |
| return PyBool_FromLong(PyUnstable_Object_IsUniquelyReferenced(op)); |
| } |
| |
| |
| static PyObject * |
| pyobject_dump(PyObject *self, PyObject *args) |
| { |
| PyObject *op; |
| int release_gil = 0; |
| |
| if (!PyArg_ParseTuple(args, "O|i", &op, &release_gil)) { |
| return NULL; |
| } |
| NULLABLE(op); |
| |
| if (release_gil) { |
| Py_BEGIN_ALLOW_THREADS |
| PyObject_Dump(op); |
| Py_END_ALLOW_THREADS |
| |
| } |
| else { |
| PyObject_Dump(op); |
| } |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| pysentinel_new(PyObject *self, PyObject *args) |
| { |
| const char *name; |
| const char *module_name = NULL; |
| const char *repr = NULL; |
| if (!PyArg_ParseTuple(args, "s|ss", &name, &module_name, &repr)) { |
| return NULL; |
| } |
| return PySentinel_New(name, module_name, repr); |
| } |
| |
| static PyObject * |
| pysentinel_check(PyObject *self, PyObject *obj) |
| { |
| return PyBool_FromLong(PySentinel_Check(obj)); |
| } |
| |
| static PyObject * |
| pysentinel_checkexact(PyObject *self, PyObject *obj) |
| { |
| return PyBool_FromLong(PySentinel_CheckExact(obj)); |
| } |
| |
| |
| static PyMethodDef test_methods[] = { |
| {"call_pyobject_print", call_pyobject_print, METH_VARARGS}, |
| {"pyobject_print_null", pyobject_print_null, METH_O}, |
| {"pyobject_print_noref_object", pyobject_print_noref_object, METH_O}, |
| {"pyobject_print_os_error", pyobject_print_os_error, METH_O}, |
| {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O}, |
| {"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O}, |
| {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O}, |
| {"pyobject_is_unique_temporary_new_object", pyobject_is_unique_temporary_new_object, METH_NOARGS}, |
| {"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS}, |
| {"test_py_set_immortal", test_py_set_immortal, METH_NOARGS}, |
| #ifdef Py_REF_DEBUG |
| {"negative_refcount", negative_refcount, METH_NOARGS}, |
| {"decref_freed_object", decref_freed_object, METH_NOARGS}, |
| #endif |
| {"clear_managed_dict", clear_managed_dict, METH_O, NULL}, |
| {"is_uniquely_referenced", is_uniquely_referenced, METH_O}, |
| {"pyobject_dump", pyobject_dump, METH_VARARGS}, |
| {"pysentinel_new", pysentinel_new, METH_VARARGS}, |
| {"pysentinel_check", pysentinel_check, METH_O}, |
| {"pysentinel_checkexact", pysentinel_checkexact, METH_O}, |
| {NULL}, |
| }; |
| |
| int |
| _PyTestCapi_Init_Object(PyObject *m) |
| { |
| return PyModule_AddFunctions(m, test_methods); |
| } |