https://github.com/python/cpython/commit/ba45e5cdd41a39ce0b3de08bdcfa9d8e28e0e4f3
commit: ba45e5cdd41a39ce0b3de08bdcfa9d8e28e0e4f3
branch: main
author: Peter Bierma <[email protected]>
committer: encukou <[email protected]>
date: 2024-12-20T14:02:46+01:00
summary:

gh-127946: Use a critical section for `CFuncPtr` attributes (GH-128109)

files:
A Misc/NEWS.d/next/Library/2024-12-19-20-46-01.gh-issue-127946.4lM3Op.rst
M Lib/test/test_ctypes/test_cfuncs.py
M Modules/_ctypes/_ctypes.c
M Modules/_ctypes/clinic/_ctypes.c.h

diff --git a/Lib/test/test_ctypes/test_cfuncs.py 
b/Lib/test/test_ctypes/test_cfuncs.py
index 48330c4b0a763b..e0c124607cb2e9 100644
--- a/Lib/test/test_ctypes/test_cfuncs.py
+++ b/Lib/test/test_ctypes/test_cfuncs.py
@@ -5,7 +5,8 @@
                     c_short, c_ushort, c_int, c_uint,
                     c_long, c_ulong, c_longlong, c_ulonglong,
                     c_float, c_double, c_longdouble)
-from test.support import import_helper
+from test import support
+from test.support import import_helper, threading_helper
 _ctypes_test = import_helper.import_module("_ctypes_test")
 
 
@@ -191,6 +192,23 @@ def test_void(self):
         self.assertEqual(self._dll.tv_i(-42), None)
         self.assertEqual(self.S(), -42)
 
+    @threading_helper.requires_working_threading()
+    @support.requires_resource("cpu")
+    @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful on 
free-threading")
+    def test_thread_safety(self):
+        from threading import Thread
+
+        def concurrent():
+            for _ in range(100):
+                self._dll.tf_b.restype = c_byte
+                self._dll.tf_b.argtypes = (c_byte,)
+
+        with threading_helper.catch_threading_exception() as exc:
+            with threading_helper.start_threads((Thread(target=concurrent) for 
_ in range(10))):
+                pass
+
+            self.assertIsNone(exc.exc_value)
+
 
 # The following repeats the above tests with stdcall functions (where
 # they are available)
diff --git 
a/Misc/NEWS.d/next/Library/2024-12-19-20-46-01.gh-issue-127946.4lM3Op.rst 
b/Misc/NEWS.d/next/Library/2024-12-19-20-46-01.gh-issue-127946.4lM3Op.rst
new file mode 100644
index 00000000000000..faf1ec042bc2b9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-19-20-46-01.gh-issue-127946.4lM3Op.rst
@@ -0,0 +1,2 @@
+Fix crash when modifying :class:`ctypes._CFuncPtr` objects concurrently on
+the :term:`free threaded <free threading>` build.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 3a3b1da5084a67..dcdb7b2052a11e 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -128,8 +128,9 @@ bytes(cdata)
 
 /*[clinic input]
 module _ctypes
+class _ctypes.CFuncPtr "PyCFuncPtrObject *" "&PyCFuncPtr_Type"
 [clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=476a19c49b31a75c]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=58e8c99474bc631e]*/
 
 #define clinic_state() (get_module_state_by_class(cls))
 #define clinic_state_sub() (get_module_state_by_class(cls->tp_base))
@@ -3422,21 +3423,37 @@ generic_pycdata_new(ctypes_state *st,
   PyCFuncPtr_Type
 */
 
+/*[clinic input]
+@critical_section
+@setter
+_ctypes.CFuncPtr.errcheck
+[clinic start generated code]*/
+
 static int
-PyCFuncPtr_set_errcheck(PyCFuncPtrObject *self, PyObject *ob, void 
*Py_UNUSED(ignored))
+_ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value)
+/*[clinic end generated code: output=6580cf1ffdf3b9fb input=84930bb16c490b33]*/
 {
-    if (ob && !PyCallable_Check(ob)) {
+    if (value && !PyCallable_Check(value)) {
         PyErr_SetString(PyExc_TypeError,
                         "the errcheck attribute must be callable");
         return -1;
     }
-    Py_XINCREF(ob);
-    Py_XSETREF(self->errcheck, ob);
+    Py_XINCREF(value);
+    Py_XSETREF(self->errcheck, value);
     return 0;
 }
 
+/*[clinic input]
+@critical_section
+@getter
+_ctypes.CFuncPtr.errcheck
+
+a function to check for errors
+[clinic start generated code]*/
+
 static PyObject *
-PyCFuncPtr_get_errcheck(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
+_ctypes_CFuncPtr_errcheck_get_impl(PyCFuncPtrObject *self)
+/*[clinic end generated code: output=dfa6fb5c6f90fd14 input=4672135fef37819f]*/
 {
     if (self->errcheck) {
         return Py_NewRef(self->errcheck);
@@ -3444,11 +3461,18 @@ PyCFuncPtr_get_errcheck(PyCFuncPtrObject *self, void 
*Py_UNUSED(ignored))
     Py_RETURN_NONE;
 }
 
+/*[clinic input]
+@setter
+@critical_section
+_ctypes.CFuncPtr.restype
+[clinic start generated code]*/
+
 static int
-PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void 
*Py_UNUSED(ignored))
+_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
+/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/
 {
     PyObject *checker, *oldchecker;
-    if (ob == NULL) {
+    if (value == NULL) {
         oldchecker = self->checker;
         self->checker = NULL;
         Py_CLEAR(self->restype);
@@ -3457,27 +3481,36 @@ PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject 
*ob, void *Py_UNUSED(ign
     }
     ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
     StgInfo *info;
-    if (PyStgInfo_FromType(st, ob, &info) < 0) {
+    if (PyStgInfo_FromType(st, value, &info) < 0) {
         return -1;
     }
-    if (ob != Py_None && !info && !PyCallable_Check(ob)) {
+    if (value != Py_None && !info && !PyCallable_Check(value)) {
         PyErr_SetString(PyExc_TypeError,
                         "restype must be a type, a callable, or None");
         return -1;
     }
-    if (PyObject_GetOptionalAttr(ob, &_Py_ID(_check_retval_), &checker) < 0) {
+    if (PyObject_GetOptionalAttr(value, &_Py_ID(_check_retval_), &checker) < 
0) {
         return -1;
     }
     oldchecker = self->checker;
     self->checker = checker;
-    Py_INCREF(ob);
-    Py_XSETREF(self->restype, ob);
+    Py_INCREF(value);
+    Py_XSETREF(self->restype, value);
     Py_XDECREF(oldchecker);
     return 0;
 }
 
+/*[clinic input]
+@getter
+@critical_section
+_ctypes.CFuncPtr.restype
+
+specify the result type
+[clinic start generated code]*/
+
 static PyObject *
-PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
+_ctypes_CFuncPtr_restype_get_impl(PyCFuncPtrObject *self)
+/*[clinic end generated code: output=c8f44cd16f1dee5e input=5e3ed95116204fd2]*/
 {
     if (self->restype) {
         return Py_NewRef(self->restype);
@@ -3495,28 +3528,44 @@ PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void 
*Py_UNUSED(ignored))
     }
 }
 
+/*[clinic input]
+@setter
+@critical_section
+_ctypes.CFuncPtr.argtypes
+[clinic start generated code]*/
+
 static int
-PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void 
*Py_UNUSED(ignored))
+_ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value)
+/*[clinic end generated code: output=596a36e2ae89d7d1 input=c4627573e980aa8b]*/
 {
     PyObject *converters;
 
-    if (ob == NULL || ob == Py_None) {
+    if (value == NULL || value == Py_None) {
         Py_CLEAR(self->converters);
         Py_CLEAR(self->argtypes);
     } else {
         ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
-        converters = converters_from_argtypes(st, ob);
+        converters = converters_from_argtypes(st, value);
         if (!converters)
             return -1;
         Py_XSETREF(self->converters, converters);
-        Py_INCREF(ob);
-        Py_XSETREF(self->argtypes, ob);
+        Py_INCREF(value);
+        Py_XSETREF(self->argtypes, value);
     }
     return 0;
 }
 
+/*[clinic input]
+@getter
+@critical_section
+_ctypes.CFuncPtr.argtypes
+
+specify the argument types
+[clinic start generated code]*/
+
 static PyObject *
-PyCFuncPtr_get_argtypes(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
+_ctypes_CFuncPtr_argtypes_get_impl(PyCFuncPtrObject *self)
+/*[clinic end generated code: output=c46b05a1b0f99172 input=37a8a545a56f8ae2]*/
 {
     if (self->argtypes) {
         return Py_NewRef(self->argtypes);
@@ -3535,13 +3584,9 @@ PyCFuncPtr_get_argtypes(PyCFuncPtrObject *self, void 
*Py_UNUSED(ignored))
 }
 
 static PyGetSetDef PyCFuncPtr_getsets[] = {
-    { "errcheck", (getter)PyCFuncPtr_get_errcheck, 
(setter)PyCFuncPtr_set_errcheck,
-      "a function to check for errors", NULL },
-    { "restype", (getter)PyCFuncPtr_get_restype, 
(setter)PyCFuncPtr_set_restype,
-      "specify the result type", NULL },
-    { "argtypes", (getter)PyCFuncPtr_get_argtypes,
-      (setter)PyCFuncPtr_set_argtypes,
-      "specify the argument types", NULL },
+    _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF
+    _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF
+    _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF
     { NULL, NULL }
 };
 
@@ -5054,7 +5099,6 @@ class _ctypes.Simple "PyObject *" 
"clinic_state()->Simple_Type"
 [clinic start generated code]*/
 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=016c476c7aa8b8a8]*/
 
-
 static int
 Simple_set_value(CDataObject *self, PyObject *value, void *Py_UNUSED(ignored))
 {
diff --git a/Modules/_ctypes/clinic/_ctypes.c.h 
b/Modules/_ctypes/clinic/_ctypes.c.h
index 1332ba04cdfecd..405a3c9238d77d 100644
--- a/Modules/_ctypes/clinic/_ctypes.c.h
+++ b/Modules/_ctypes/clinic/_ctypes.c.h
@@ -6,6 +6,7 @@ preserve
 #  include "pycore_runtime.h"     // _Py_SINGLETON()
 #endif
 #include "pycore_abstract.h"      // _PyNumber_Index()
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
 #include "pycore_modsupport.h"    // _PyArg_UnpackKeywords()
 
 PyDoc_STRVAR(_ctypes_CType_Type___sizeof____doc__,
@@ -601,6 +602,177 @@ PyCData_reduce(PyObject *myself, PyTypeObject *cls, 
PyObject *const *args, Py_ss
     return PyCData_reduce_impl(myself, cls);
 }
 
+#if !defined(_ctypes_CFuncPtr_errcheck_DOCSTR)
+#  define _ctypes_CFuncPtr_errcheck_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF
+#  define _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF {"errcheck", 
(getter)_ctypes_CFuncPtr_errcheck_get, (setter)_ctypes_CFuncPtr_errcheck_set, 
_ctypes_CFuncPtr_errcheck_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF {"errcheck", NULL, 
(setter)_ctypes_CFuncPtr_errcheck_set, NULL},
+#endif
+
+static int
+_ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value);
+
+static int
+_ctypes_CFuncPtr_errcheck_set(PyCFuncPtrObject *self, PyObject *value, void 
*Py_UNUSED(context))
+{
+    int return_value;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_errcheck_set_impl(self, value);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(_ctypes_CFuncPtr_errcheck__doc__,
+"a function to check for errors");
+#if defined(_ctypes_CFuncPtr_errcheck_DOCSTR)
+#   undef _ctypes_CFuncPtr_errcheck_DOCSTR
+#endif
+#define _ctypes_CFuncPtr_errcheck_DOCSTR _ctypes_CFuncPtr_errcheck__doc__
+
+#if !defined(_ctypes_CFuncPtr_errcheck_DOCSTR)
+#  define _ctypes_CFuncPtr_errcheck_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF
+#  define _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF {"errcheck", 
(getter)_ctypes_CFuncPtr_errcheck_get, (setter)_ctypes_CFuncPtr_errcheck_set, 
_ctypes_CFuncPtr_errcheck_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF {"errcheck", 
(getter)_ctypes_CFuncPtr_errcheck_get, NULL, _ctypes_CFuncPtr_errcheck_DOCSTR},
+#endif
+
+static PyObject *
+_ctypes_CFuncPtr_errcheck_get_impl(PyCFuncPtrObject *self);
+
+static PyObject *
+_ctypes_CFuncPtr_errcheck_get(PyCFuncPtrObject *self, void *Py_UNUSED(context))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_errcheck_get_impl(self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+#if !defined(_ctypes_CFuncPtr_restype_DOCSTR)
+#  define _ctypes_CFuncPtr_restype_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_RESTYPE_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF
+#  define _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF {"restype", 
(getter)_ctypes_CFuncPtr_restype_get, (setter)_ctypes_CFuncPtr_restype_set, 
_ctypes_CFuncPtr_restype_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF {"restype", NULL, 
(setter)_ctypes_CFuncPtr_restype_set, NULL},
+#endif
+
+static int
+_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value);
+
+static int
+_ctypes_CFuncPtr_restype_set(PyCFuncPtrObject *self, PyObject *value, void 
*Py_UNUSED(context))
+{
+    int return_value;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_restype_set_impl(self, value);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(_ctypes_CFuncPtr_restype__doc__,
+"specify the result type");
+#if defined(_ctypes_CFuncPtr_restype_DOCSTR)
+#   undef _ctypes_CFuncPtr_restype_DOCSTR
+#endif
+#define _ctypes_CFuncPtr_restype_DOCSTR _ctypes_CFuncPtr_restype__doc__
+
+#if !defined(_ctypes_CFuncPtr_restype_DOCSTR)
+#  define _ctypes_CFuncPtr_restype_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_RESTYPE_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF
+#  define _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF {"restype", 
(getter)_ctypes_CFuncPtr_restype_get, (setter)_ctypes_CFuncPtr_restype_set, 
_ctypes_CFuncPtr_restype_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_RESTYPE_GETSETDEF {"restype", 
(getter)_ctypes_CFuncPtr_restype_get, NULL, _ctypes_CFuncPtr_restype_DOCSTR},
+#endif
+
+static PyObject *
+_ctypes_CFuncPtr_restype_get_impl(PyCFuncPtrObject *self);
+
+static PyObject *
+_ctypes_CFuncPtr_restype_get(PyCFuncPtrObject *self, void *Py_UNUSED(context))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_restype_get_impl(self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+#if !defined(_ctypes_CFuncPtr_argtypes_DOCSTR)
+#  define _ctypes_CFuncPtr_argtypes_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF
+#  define _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF {"argtypes", 
(getter)_ctypes_CFuncPtr_argtypes_get, (setter)_ctypes_CFuncPtr_argtypes_set, 
_ctypes_CFuncPtr_argtypes_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF {"argtypes", NULL, 
(setter)_ctypes_CFuncPtr_argtypes_set, NULL},
+#endif
+
+static int
+_ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value);
+
+static int
+_ctypes_CFuncPtr_argtypes_set(PyCFuncPtrObject *self, PyObject *value, void 
*Py_UNUSED(context))
+{
+    int return_value;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_argtypes_set_impl(self, value);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
+PyDoc_STRVAR(_ctypes_CFuncPtr_argtypes__doc__,
+"specify the argument types");
+#if defined(_ctypes_CFuncPtr_argtypes_DOCSTR)
+#   undef _ctypes_CFuncPtr_argtypes_DOCSTR
+#endif
+#define _ctypes_CFuncPtr_argtypes_DOCSTR _ctypes_CFuncPtr_argtypes__doc__
+
+#if !defined(_ctypes_CFuncPtr_argtypes_DOCSTR)
+#  define _ctypes_CFuncPtr_argtypes_DOCSTR NULL
+#endif
+#if defined(_CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF)
+#  undef _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF
+#  define _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF {"argtypes", 
(getter)_ctypes_CFuncPtr_argtypes_get, (setter)_ctypes_CFuncPtr_argtypes_set, 
_ctypes_CFuncPtr_argtypes_DOCSTR},
+#else
+#  define _CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF {"argtypes", 
(getter)_ctypes_CFuncPtr_argtypes_get, NULL, _ctypes_CFuncPtr_argtypes_DOCSTR},
+#endif
+
+static PyObject *
+_ctypes_CFuncPtr_argtypes_get_impl(PyCFuncPtrObject *self);
+
+static PyObject *
+_ctypes_CFuncPtr_argtypes_get(PyCFuncPtrObject *self, void *Py_UNUSED(context))
+{
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _ctypes_CFuncPtr_argtypes_get_impl(self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
+}
+
 PyDoc_STRVAR(Simple_from_outparm__doc__,
 "__ctypes_from_outparam__($self, /)\n"
 "--\n"
@@ -621,4 +793,4 @@ Simple_from_outparm(PyObject *self, PyTypeObject *cls, 
PyObject *const *args, Py
     }
     return Simple_from_outparm_impl(self, cls);
 }
-/*[clinic end generated code: output=52724c091e3a8b8d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=cb3583522a2c5ce5 input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to