From 96a38d8229c301897de559a793d7be2c18ec213c Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 29 Jul 2026 12:05:11 +0530 Subject: [PATCH] gh-153531: fix thread safety of setting func.__doc__ and func.__module__ (GH-154851) (cherry picked from commit 9ccd5bb81edde823fb5fdbd51287f4a0ddfcd149) Co-authored-by: Kumar Aditya --- .../test_free_threading/test_functions.py | 6 ++ Objects/funcobject.c | 90 ++++++++++++------- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_free_threading/test_functions.py b/Lib/test/test_free_threading/test_functions.py index 65a90a17bd5b8d..7a59638d27dc9d 100644 --- a/Lib/test/test_free_threading/test_functions.py +++ b/Lib/test/test_free_threading/test_functions.py @@ -58,6 +58,12 @@ def test_annotate(self): 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 diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 49a28e8ad66714..0c1fab7f6d33a8 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -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