https://github.com/python/cpython/commit/9ccd5bb81edde823fb5fdbd51287f4a0ddfcd149
commit: 9ccd5bb81edde823fb5fdbd51287f4a0ddfcd149
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-29T12:05:11+05:30
summary:
gh-153531: fix thread safety of setting func.__doc__ and func.__module__
(#154851)
files:
M Lib/test/test_free_threading/test_functions.py
M Objects/funcobject.c
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
_______________________________________________
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]