Skip to content

Commit ba45e5c

Browse files
gh-127946: Use a critical section for CFuncPtr attributes (GH-128109)
1 parent cbfe302 commit ba45e5c

File tree

4 files changed

+266
-30
lines changed

4 files changed

+266
-30
lines changed

Lib/test/test_ctypes/test_cfuncs.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
c_short, c_ushort, c_int, c_uint,
66
c_long, c_ulong, c_longlong, c_ulonglong,
77
c_float, c_double, c_longdouble)
8-
from test.support import import_helper
8+
from test import support
9+
from test.support import import_helper, threading_helper
910
_ctypes_test = import_helper.import_module("_ctypes_test")
1011

1112

@@ -191,6 +192,23 @@ def test_void(self):
191192
self.assertEqual(self._dll.tv_i(-42), None)
192193
self.assertEqual(self.S(), -42)
193194

195+
@threading_helper.requires_working_threading()
196+
@support.requires_resource("cpu")
197+
@unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful on free-threading")
198+
def test_thread_safety(self):
199+
from threading import Thread
200+
201+
def concurrent():
202+
for _ in range(100):
203+
self._dll.tf_b.restype = c_byte
204+
self._dll.tf_b.argtypes = (c_byte,)
205+
206+
with threading_helper.catch_threading_exception() as exc:
207+
with threading_helper.start_threads((Thread(target=concurrent) for _ in range(10))):
208+
pass
209+
210+
self.assertIsNone(exc.exc_value)
211+
194212

195213
# The following repeats the above tests with stdcall functions (where
196214
# they are available)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash when modifying :class:`ctypes._CFuncPtr` objects concurrently on
2+
the :term:`free threaded <free threading>` build.

Modules/_ctypes/_ctypes.c

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,9 @@ bytes(cdata)
128128

129129
/*[clinic input]
130130
module _ctypes
131+
class _ctypes.CFuncPtr "PyCFuncPtrObject *" "&PyCFuncPtr_Type"
131132
[clinic start generated code]*/
132-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=476a19c49b31a75c]*/
133+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=58e8c99474bc631e]*/
133134

134135
#define clinic_state() (get_module_state_by_class(cls))
135136
#define clinic_state_sub() (get_module_state_by_class(cls->tp_base))
@@ -3422,33 +3423,56 @@ generic_pycdata_new(ctypes_state *st,
34223423
PyCFuncPtr_Type
34233424
*/
34243425

3426+
/*[clinic input]
3427+
@critical_section
3428+
@setter
3429+
_ctypes.CFuncPtr.errcheck
3430+
[clinic start generated code]*/
3431+
34253432
static int
3426-
PyCFuncPtr_set_errcheck(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
3433+
_ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value)
3434+
/*[clinic end generated code: output=6580cf1ffdf3b9fb input=84930bb16c490b33]*/
34273435
{
3428-
if (ob && !PyCallable_Check(ob)) {
3436+
if (value && !PyCallable_Check(value)) {
34293437
PyErr_SetString(PyExc_TypeError,
34303438
"the errcheck attribute must be callable");
34313439
return -1;
34323440
}
3433-
Py_XINCREF(ob);
3434-
Py_XSETREF(self->errcheck, ob);
3441+
Py_XINCREF(value);
3442+
Py_XSETREF(self->errcheck, value);
34353443
return 0;
34363444
}
34373445

3446+
/*[clinic input]
3447+
@critical_section
3448+
@getter
3449+
_ctypes.CFuncPtr.errcheck
3450+
3451+
a function to check for errors
3452+
[clinic start generated code]*/
3453+
34383454
static PyObject *
3439-
PyCFuncPtr_get_errcheck(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
3455+
_ctypes_CFuncPtr_errcheck_get_impl(PyCFuncPtrObject *self)
3456+
/*[clinic end generated code: output=dfa6fb5c6f90fd14 input=4672135fef37819f]*/
34403457
{
34413458
if (self->errcheck) {
34423459
return Py_NewRef(self->errcheck);
34433460
}
34443461
Py_RETURN_NONE;
34453462
}
34463463

3464+
/*[clinic input]
3465+
@setter
3466+
@critical_section
3467+
_ctypes.CFuncPtr.restype
3468+
[clinic start generated code]*/
3469+
34473470
static int
3448-
PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
3471+
_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
3472+
/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/
34493473
{
34503474
PyObject *checker, *oldchecker;
3451-
if (ob == NULL) {
3475+
if (value == NULL) {
34523476
oldchecker = self->checker;
34533477
self->checker = NULL;
34543478
Py_CLEAR(self->restype);
@@ -3457,27 +3481,36 @@ PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ign
34573481
}
34583482
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
34593483
StgInfo *info;
3460-
if (PyStgInfo_FromType(st, ob, &info) < 0) {
3484+
if (PyStgInfo_FromType(st, value, &info) < 0) {
34613485
return -1;
34623486
}
3463-
if (ob != Py_None && !info && !PyCallable_Check(ob)) {
3487+
if (value != Py_None && !info && !PyCallable_Check(value)) {
34643488
PyErr_SetString(PyExc_TypeError,
34653489
"restype must be a type, a callable, or None");
34663490
return -1;
34673491
}
3468-
if (PyObject_GetOptionalAttr(ob, &_Py_ID(_check_retval_), &checker) < 0) {
3492+
if (PyObject_GetOptionalAttr(value, &_Py_ID(_check_retval_), &checker) < 0) {
34693493
return -1;
34703494
}
34713495
oldchecker = self->checker;
34723496
self->checker = checker;
3473-
Py_INCREF(ob);
3474-
Py_XSETREF(self->restype, ob);
3497+
Py_INCREF(value);
3498+
Py_XSETREF(self->restype, value);
34753499
Py_XDECREF(oldchecker);
34763500
return 0;
34773501
}
34783502

3503+
/*[clinic input]
3504+
@getter
3505+
@critical_section
3506+
_ctypes.CFuncPtr.restype
3507+
3508+
specify the result type
3509+
[clinic start generated code]*/
3510+
34793511
static PyObject *
3480-
PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
3512+
_ctypes_CFuncPtr_restype_get_impl(PyCFuncPtrObject *self)
3513+
/*[clinic end generated code: output=c8f44cd16f1dee5e input=5e3ed95116204fd2]*/
34813514
{
34823515
if (self->restype) {
34833516
return Py_NewRef(self->restype);
@@ -3495,28 +3528,44 @@ PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
34953528
}
34963529
}
34973530

3531+
/*[clinic input]
3532+
@setter
3533+
@critical_section
3534+
_ctypes.CFuncPtr.argtypes
3535+
[clinic start generated code]*/
3536+
34983537
static int
3499-
PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
3538+
_ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value)
3539+
/*[clinic end generated code: output=596a36e2ae89d7d1 input=c4627573e980aa8b]*/
35003540
{
35013541
PyObject *converters;
35023542

3503-
if (ob == NULL || ob == Py_None) {
3543+
if (value == NULL || value == Py_None) {
35043544
Py_CLEAR(self->converters);
35053545
Py_CLEAR(self->argtypes);
35063546
} else {
35073547
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
3508-
converters = converters_from_argtypes(st, ob);
3548+
converters = converters_from_argtypes(st, value);
35093549
if (!converters)
35103550
return -1;
35113551
Py_XSETREF(self->converters, converters);
3512-
Py_INCREF(ob);
3513-
Py_XSETREF(self->argtypes, ob);
3552+
Py_INCREF(value);
3553+
Py_XSETREF(self->argtypes, value);
35143554
}
35153555
return 0;
35163556
}
35173557

3558+
/*[clinic input]
3559+
@getter
3560+
@critical_section
3561+
_ctypes.CFuncPtr.argtypes
3562+
3563+
specify the argument types
3564+
[clinic start generated code]*/
3565+
35183566
static PyObject *
3519-
PyCFuncPtr_get_argtypes(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
3567+
_ctypes_CFuncPtr_argtypes_get_impl(PyCFuncPtrObject *self)
3568+
/*[clinic end generated code: output=c46b05a1b0f99172 input=37a8a545a56f8ae2]*/
35203569
{
35213570
if (self->argtypes) {
35223571
return Py_NewRef(self->argtypes);
@@ -3535,13 +3584,9 @@ PyCFuncPtr_get_argtypes(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
35353584
}
35363585

35373586
static PyGetSetDef PyCFuncPtr_getsets[] = {
3538-
{ "errcheck", (getter)PyCFuncPtr_get_errcheck, (setter)PyCFuncPtr_set_errcheck,
3539-
"a function to check for errors", NULL },
3540-
{ "restype", (getter)PyCFuncPtr_get_restype, (setter)PyCFuncPtr_set_restype,
3541-
"specify the result type", NULL },
3542-
{ "argtypes", (getter)PyCFuncPtr_get_argtypes,
3543-
(setter)PyCFuncPtr_set_argtypes,
3544-
"specify the argument types", NULL },
3587+
_CTYPES_CFUNCPTR_ERRCHECK_GETSETDEF
3588+
_CTYPES_CFUNCPTR_RESTYPE_GETSETDEF
3589+
_CTYPES_CFUNCPTR_ARGTYPES_GETSETDEF
35453590
{ NULL, NULL }
35463591
};
35473592

@@ -5054,7 +5099,6 @@ class _ctypes.Simple "PyObject *" "clinic_state()->Simple_Type"
50545099
[clinic start generated code]*/
50555100
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=016c476c7aa8b8a8]*/
50565101

5057-
50585102
static int
50595103
Simple_set_value(CDataObject *self, PyObject *value, void *Py_UNUSED(ignored))
50605104
{

0 commit comments

Comments
 (0)