From dfad678d7024ab86d265d84ed45999e031a03691 Mon Sep 17 00:00:00 2001 From: Yukihiro Nakadaira Date: Thu, 26 Jan 2023 17:28:34 +0900 Subject: [PATCH] gh-99952: [ctypes] fix refcount issues in from_param() result. (#100169) Fixes a reference counting issue with `ctypes.Structure` when a `from_param()` method call is used and the structure size is larger than a C pointer `sizeof(void*)`. This problem existed for a very long time, but became more apparent in 3.8+ by change likely due to garbage collection cleanup timing changes. --- Lib/test/test_ctypes/test_parameters.py | 52 +++++++++++++++++++ ...2-12-11-14-38-59.gh-issue-99952.IYGLzr.rst | 2 + Modules/_ctypes/_ctypes.c | 3 ++ Modules/_ctypes/_ctypes_test.c | 6 +++ 4 files changed, 63 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-12-11-14-38-59.gh-issue-99952.IYGLzr.rst diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py index 22d290db1bcb7d..06cc95107b79fa 100644 --- a/Lib/test/test_ctypes/test_parameters.py +++ b/Lib/test/test_ctypes/test_parameters.py @@ -266,6 +266,58 @@ def test_parameter_repr(self): self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^$") self.assertRegex(repr(c_void_p.from_param(0x12)), r"^$") + @test.support.cpython_only + def test_from_param_result_refcount(self): + # Issue #99952 + import _ctypes_test + from ctypes import PyDLL, c_int, c_void_p, py_object, Structure + + class X(Structure): + """This struct size is <= sizeof(void*).""" + _fields_ = [("a", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, X] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + + class Y(Structure): + """This struct size is > sizeof(void*).""" + _fields_ = [("a", c_void_p), ("b", c_void_p)] + + def __del__(self): + trace.append(4) + + @classmethod + def from_param(cls, value): + trace.append(2) + return cls() + + PyList_Append = PyDLL(_ctypes_test.__file__)._testfunc_pylist_append + PyList_Append.restype = c_int + PyList_Append.argtypes = [py_object, py_object, Y] + + trace = [] + trace.append(1) + PyList_Append(trace, 3, "dummy") + trace.append(5) + + self.assertEqual(trace, [1, 2, 3, 4, 5]) + ################################################################ if __name__ == '__main__': diff --git a/Misc/NEWS.d/next/Library/2022-12-11-14-38-59.gh-issue-99952.IYGLzr.rst b/Misc/NEWS.d/next/Library/2022-12-11-14-38-59.gh-issue-99952.IYGLzr.rst new file mode 100644 index 00000000000000..09ec961249534f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-12-11-14-38-59.gh-issue-99952.IYGLzr.rst @@ -0,0 +1,2 @@ +Fix a reference undercounting issue in :class:`ctypes.Structure` with ``from_param()`` +results larger than a C pointer. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 272cafb5a9a381..8690f2c1b07852 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -412,6 +412,7 @@ _ctypes_alloc_format_string_with_shape(int ndim, const Py_ssize_t *shape, typedef struct { PyObject_HEAD void *ptr; + PyObject *keep; // If set, a reference to the original CDataObject. } StructParamObject; @@ -419,6 +420,7 @@ static void StructParam_dealloc(PyObject *myself) { StructParamObject *self = (StructParamObject *)myself; + Py_XDECREF(self->keep); PyMem_Free(self->ptr); Py_TYPE(self)->tp_free(myself); } @@ -466,6 +468,7 @@ StructUnionType_paramfunc(CDataObject *self) StructParamObject *struct_param = (StructParamObject *)obj; struct_param->ptr = ptr; + struct_param->keep = Py_NewRef(self); } else { ptr = self->b_ptr; obj = Py_NewRef(self); diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index e1f91b476a49fd..a8811d03cc91a2 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -1047,6 +1047,12 @@ EXPORT(long) _test_i38748_runCallback(_test_i38748_funcType callback, int a, int #endif +EXPORT(int) +_testfunc_pylist_append(PyObject *list, PyObject *item) +{ + return PyList_Append(list, item); +} + static struct PyModuleDef_Slot _ctypes_test_slots[] = { {0, NULL} };