]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153531: fix thread safety of setting func.__doc__ and func.__module__ (#154851)
authorKumar Aditya <kumaraditya@python.org>
Wed, 29 Jul 2026 06:35:11 +0000 (12:05 +0530)
committerGitHub <noreply@github.com>
Wed, 29 Jul 2026 06:35:11 +0000 (12:05 +0530)
Lib/test/test_free_threading/test_functions.py
Objects/funcobject.c

index 65a90a17bd5b8db4cc17c55c6583195280d69c80..7a59638d27dc9d418d7c58ab1ce3c99748776db1 100644 (file)
@@ -58,6 +58,12 @@ class TestFTFunctionAttributes(TestCase):
     def test_type_params(self):
         self.stress_attribute("__type_params__", lambda: (random_string(),))
 
+    def test_doc(self):
+        self.stress_attribute("__doc__", random_string)
+
+    def test_module(self):
+        self.stress_attribute("__module__", random_string)
+
     def test_annotations_and_annotate(self):
         # The __annotations__ and __annotate__ setters clear each other.
         def target(): pass
index 49a28e8ad6671414cae26c18e3ed2012874cc821..0c1fab7f6d33a8a414be12b8a76ee366695ea33a 100644 (file)
@@ -628,9 +628,7 @@ PyFunction_SetAnnotations(PyObject *op, PyObject *annotations)
 
 static PyMemberDef func_memberlist[] = {
     {"__closure__",   _Py_T_OBJECT,     OFF(func_closure), Py_READONLY},
-    {"__doc__",       _Py_T_OBJECT,     OFF(func_doc), 0},
     {"__globals__",   _Py_T_OBJECT,     OFF(func_globals), Py_READONLY},
-    {"__module__",    _Py_T_OBJECT,     OFF(func_module), 0},
     {"__builtins__",  _Py_T_OBJECT,     OFF(func_builtins), Py_READONLY},
     {NULL}  /* Sentinel */
 };
@@ -762,6 +760,56 @@ func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
     return 0;
 }
 
+static PyObject *
+func_get_doc(PyObject *self, void *Py_UNUSED(ignored))
+{
+    PyFunctionObject *op = _PyFunction_CAST(self);
+    PyObject *doc = op->func_doc;
+    if (doc == NULL) {
+        doc = Py_None;
+    }
+    return Py_NewRef(doc);
+}
+
+static int
+func_set_doc(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
+{
+    /* Legal to del f.__doc__ or to set it to any object. */
+    PyFunctionObject *op = _PyFunction_CAST(self);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_doc = op->func_doc;
+    op->func_doc = Py_XNewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_doc);
+    return 0;
+}
+
+static PyObject *
+func_get_module(PyObject *self, void *Py_UNUSED(ignored))
+{
+    PyFunctionObject *op = _PyFunction_CAST(self);
+    PyObject *module = op->func_module;
+    if (module == NULL) {
+        module = Py_None;
+    }
+    return Py_NewRef(module);
+}
+
+static int
+func_set_module(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
+{
+    /* Legal to del f.__module__ or to set it to any object. */
+    PyFunctionObject *op = _PyFunction_CAST(self);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_module = op->func_module;
+    op->func_module = Py_XNewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_module);
+    return 0;
+}
+
 static PyObject *
 func_get_defaults(PyObject *self, void *Py_UNUSED(ignored))
 {
@@ -891,24 +939,12 @@ function___annotate___set_impl(PyFunctionObject *self, PyObject *value)
         return -1;
     }
     if (Py_IsNone(value)) {
-        PyInterpreterState *interp = _PyInterpreterState_GET();
-        _PyEval_StopTheWorld(interp);
-        PyObject *old_annotate = self->func_annotate;
-        self->func_annotate = Py_NewRef(value);
-        _PyEval_StartTheWorld(interp);
-        Py_XDECREF(old_annotate);
+        Py_XSETREF(self->func_annotate, Py_NewRef(value));
         return 0;
     }
     else if (PyCallable_Check(value)) {
-        PyInterpreterState *interp = _PyInterpreterState_GET();
-        _PyEval_StopTheWorld(interp);
-        PyObject *old_annotate = self->func_annotate;
-        self->func_annotate = Py_NewRef(value);
-        PyObject *old_annotations = self->func_annotations;
-        self->func_annotations = NULL;
-        _PyEval_StartTheWorld(interp);
-        Py_XDECREF(old_annotate);
-        Py_XDECREF(old_annotations);
+        Py_XSETREF(self->func_annotate, Py_NewRef(value));
+        Py_CLEAR(self->func_annotations);
         return 0;
     }
     else {
@@ -961,15 +997,8 @@ function___annotations___set_impl(PyFunctionObject *self, PyObject *value)
             "__annotations__ must be set to a dict object");
         return -1;
     }
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    _PyEval_StopTheWorld(interp);
-    PyObject *old_annotations = self->func_annotations;
-    self->func_annotations = Py_XNewRef(value);
-    PyObject *old_annotate = self->func_annotate;
-    self->func_annotate = NULL;
-    _PyEval_StartTheWorld(interp);
-    Py_XDECREF(old_annotations);
-    Py_XDECREF(old_annotate);
+    Py_XSETREF(self->func_annotations, Py_XNewRef(value));
+    Py_CLEAR(self->func_annotate);
     return 0;
 }
 
@@ -1010,12 +1039,7 @@ function___type_params___set_impl(PyFunctionObject *self, PyObject *value)
                         "__type_params__ must be set to a tuple");
         return -1;
     }
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    _PyEval_StopTheWorld(interp);
-    PyObject *old_typeparams = self->func_typeparams;
-    self->func_typeparams = Py_NewRef(value);
-    _PyEval_StartTheWorld(interp);
-    Py_XDECREF(old_typeparams);
+    Py_XSETREF(self->func_typeparams, Py_NewRef(value));
     return 0;
 }
 
@@ -1037,6 +1061,8 @@ static PyGetSetDef func_getsetlist[] = {
     FUNCTION___ANNOTATIONS___GETSETDEF
     FUNCTION___ANNOTATE___GETSETDEF
     {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
+    {"__doc__", func_get_doc, func_set_doc},
+    {"__module__", func_get_module, func_set_module},
     {"__name__", func_get_name, func_set_name},
     {"__qualname__", func_get_qualname, func_set_qualname},
     FUNCTION___TYPE_PARAMS___GETSETDEF