diff --git a/Lib/test/test_free_threading/test_func_annotations.py b/Lib/test/test_free_threading/test_function.py similarity index 66% rename from Lib/test/test_free_threading/test_func_annotations.py rename to Lib/test/test_free_threading/test_function.py index 0fde0136d77c7fa..753f6b0f631dfcb 100644 --- a/Lib/test/test_free_threading/test_func_annotations.py +++ b/Lib/test/test_free_threading/test_function.py @@ -2,9 +2,8 @@ import unittest import inspect from threading import Barrier -from unittest import TestCase -from test.support import threading_helper, Py_GIL_DISABLED +from test.support import threading_helper threading_helper.requires_working_threading(module=True) @@ -25,11 +24,48 @@ def set_func_annotation(f, b): return f.__annotations__ -@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build") -class TestFTFuncAnnotations(TestCase): +class TestFunction(unittest.TestCase): NUM_THREADS = 4 - def test_concurrent_read(self): + def test_name_attribute_race(self): + # gh-153297 + def shared_func(): + return 1 + + def setter(): + for i in range(2000): + shared_func.__name__ = "q_%d_%d" % (id(i), i & 15) + + def reader(): + for _ in range(2000): + _ = shared_func.__name__ + repr(shared_func) + + threading_helper.run_concurrently([ + *[setter for _ in range(6)], + *[reader for _ in range(4)], + ]) + + def test_qualname_attribute_race(self): + # gh-153297 + def shared_func(): + return 1 + + def setter(): + for i in range(2000): + shared_func.__qualname__ = "q_%d_%d" % (id(i), i & 15) + + def reader(): + for _ in range(2000): + _ = shared_func.__qualname__ + repr(shared_func) + + threading_helper.run_concurrently([ + *[setter for _ in range(6)], + *[reader for _ in range(4)], + ]) + + def test_concurrent_read_annotations(self): def f(x: int) -> int: return x + 1 @@ -50,7 +86,7 @@ def f(x: int) -> int: self.assertIsNotNone(annotate) self.assertEqual(annotate, {'x': int, 'return': int}) - def test_concurrent_write(self): + def test_concurrent_write_annotations(self): def bar(x: int, y: float) -> float: return y ** x diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst new file mode 100644 index 000000000000000..b853f75bb949b2f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst @@ -0,0 +1,2 @@ +Fix data race on assigning ``__name__`` and ``__qualname__`` attrs to +function objects on FT builds. diff --git a/Objects/clinic/funcobject.c.h b/Objects/clinic/funcobject.c.h index fec743146466a45..ed3d3f9c3aa1d18 100644 --- a/Objects/clinic/funcobject.c.h +++ b/Objects/clinic/funcobject.c.h @@ -9,6 +9,156 @@ preserve #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +#if !defined(function___code___DOCSTR) +# define function___code___DOCSTR NULL +#endif +#if defined(FUNCTION___CODE___GETSETDEF) +# undef FUNCTION___CODE___GETSETDEF +# define FUNCTION___CODE___GETSETDEF {"__code__", (getter)function___code___get, (setter)function___code___set, function___code___DOCSTR}, +#else +# define FUNCTION___CODE___GETSETDEF {"__code__", (getter)function___code___get, NULL, function___code___DOCSTR}, +#endif + +static PyObject * +function___code___get_impl(PyFunctionObject *self); + +static PyObject * +function___code___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___code___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___code___DOCSTR) +# define function___code___DOCSTR NULL +#endif +#if defined(FUNCTION___CODE___GETSETDEF) +# undef FUNCTION___CODE___GETSETDEF +# define FUNCTION___CODE___GETSETDEF {"__code__", (getter)function___code___get, (setter)function___code___set, function___code___DOCSTR}, +#else +# define FUNCTION___CODE___GETSETDEF {"__code__", NULL, (setter)function___code___set, NULL}, +#endif + +static int +function___code___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___code___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___code___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___name___DOCSTR) +# define function___name___DOCSTR NULL +#endif +#if defined(FUNCTION___NAME___GETSETDEF) +# undef FUNCTION___NAME___GETSETDEF +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, (setter)function___name___set, function___name___DOCSTR}, +#else +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, NULL, function___name___DOCSTR}, +#endif + +static PyObject * +function___name___get_impl(PyFunctionObject *self); + +static PyObject * +function___name___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___name___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___name___DOCSTR) +# define function___name___DOCSTR NULL +#endif +#if defined(FUNCTION___NAME___GETSETDEF) +# undef FUNCTION___NAME___GETSETDEF +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, (setter)function___name___set, function___name___DOCSTR}, +#else +# define FUNCTION___NAME___GETSETDEF {"__name__", NULL, (setter)function___name___set, NULL}, +#endif + +static int +function___name___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___name___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___name___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___qualname___DOCSTR) +# define function___qualname___DOCSTR NULL +#endif +#if defined(FUNCTION___QUALNAME___GETSETDEF) +# undef FUNCTION___QUALNAME___GETSETDEF +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, (setter)function___qualname___set, function___qualname___DOCSTR}, +#else +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, NULL, function___qualname___DOCSTR}, +#endif + +static PyObject * +function___qualname___get_impl(PyFunctionObject *self); + +static PyObject * +function___qualname___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___qualname___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___qualname___DOCSTR) +# define function___qualname___DOCSTR NULL +#endif +#if defined(FUNCTION___QUALNAME___GETSETDEF) +# undef FUNCTION___QUALNAME___GETSETDEF +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, (setter)function___qualname___set, function___qualname___DOCSTR}, +#else +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", NULL, (setter)function___qualname___set, NULL}, +#endif + +static int +function___qualname___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___qualname___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___qualname___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(function___annotate____doc__, "Get the code object for a function."); #if defined(function___annotate___DOCSTR) @@ -290,4 +440,4 @@ func_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=12cb900088d41bdb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a13df804c994bf0b input=a9049054013a1b77]*/ diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 0fffd36ad462dab..4e42457323aabd9 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -630,22 +630,33 @@ class function "PyFunctionObject *" "&PyFunction_Type" #include "clinic/funcobject.c.h" +/*[clinic input] +@critical_section +@getter +function.__code__ +[clinic start generated code]*/ + static PyObject * -func_get_code(PyObject *self, void *Py_UNUSED(ignored)) +function___code___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=da514d8da1cae70f input=71be2f0bec958b7e]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - if (PySys_Audit("object.__getattr__", "Os", op, "__code__") < 0) { + if (PySys_Audit("object.__getattr__", "Os", self, "__code__") < 0) { return NULL; } - return Py_NewRef(op->func_code); + return Py_NewRef(self->func_code); } +/*[clinic input] +@critical_section +@setter +function.__code__ +[clinic start generated code]*/ + static int -func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___code___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=3a90ece2bfc881d9 input=19f6eba9ab5d7b28]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - /* Not legal to del f.func_code or to set it to anything * other than a code object. */ if (value == NULL || !PyCode_Check(value)) { @@ -655,23 +666,23 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) } if (PySys_Audit("object.__setattr__", "OsO", - op, "__code__", value) < 0) { + self, "__code__", value) < 0) { return -1; } int nfree = ((PyCodeObject *)value)->co_nfreevars; - Py_ssize_t nclosure = (op->func_closure == NULL ? 0 : - PyTuple_GET_SIZE(op->func_closure)); + Py_ssize_t nclosure = (self->func_closure == NULL ? 0 : + PyTuple_GET_SIZE(self->func_closure)); if (nclosure != nfree) { PyErr_Format(PyExc_ValueError, "%U() requires a code object with %zd free vars," " not %d", - op->func_name, + self->func_name, nclosure, nfree); return -1; } - PyObject *func_code = PyFunction_GET_CODE(op); + PyObject *func_code = PyFunction_GET_CODE(self); int old_flags = ((PyCodeObject *)func_code)->co_flags; int new_flags = ((PyCodeObject *)value)->co_flags; int mask = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR; @@ -684,23 +695,35 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) } } - handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value); - _PyFunction_ClearVersion(op); - Py_XSETREF(op->func_code, Py_NewRef(value)); + handle_func_event(PyFunction_EVENT_MODIFY_CODE, self, value); + _PyFunction_ClearVersion(self); + Py_XSETREF(self->func_code, Py_NewRef(value)); return 0; } +/*[clinic input] +@critical_section +@getter +function.__name__ +[clinic start generated code]*/ + static PyObject * -func_get_name(PyObject *self, void *Py_UNUSED(ignored)) +function___name___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=436852c5b4f6d259 input=3f1d1304d3fd103b]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - return Py_NewRef(op->func_name); + return Py_NewRef(self->func_name); } +/*[clinic input] +@critical_section +@setter +function.__name__ +[clinic start generated code]*/ + static int -func_set_name(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___name___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=2c571635b003b9cc input=705634aafaa00198]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); /* Not legal to del f.func_name or to set it to anything * other than a string object. */ if (value == NULL || !PyUnicode_Check(value)) { @@ -708,21 +731,33 @@ func_set_name(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) "__name__ must be set to a string object"); return -1; } - Py_XSETREF(op->func_name, Py_NewRef(value)); + Py_XSETREF(self->func_name, Py_NewRef(value)); return 0; } +/*[clinic input] +@critical_section +@getter +function.__qualname__ +[clinic start generated code]*/ + static PyObject * -func_get_qualname(PyObject *self, void *Py_UNUSED(ignored)) +function___qualname___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=8fbd60e64464da5f input=761d5ab45fc13493]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - return Py_NewRef(op->func_qualname); + return Py_NewRef(self->func_qualname); } +/*[clinic input] +@critical_section +@setter +function.__qualname__ +[clinic start generated code]*/ + static int -func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___qualname___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=4cc5e270fd55f139 input=c803ac4dfdf04c87]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); /* Not legal to del f.__qualname__ or to set it to anything * other than a string object. */ if (value == NULL || !PyUnicode_Check(value)) { @@ -730,8 +765,8 @@ func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) "__qualname__ must be set to a string object"); return -1; } - handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) op, value); - Py_XSETREF(op->func_qualname, Py_NewRef(value)); + handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, self, value); + Py_XSETREF(self->func_qualname, Py_NewRef(value)); return 0; } @@ -970,14 +1005,14 @@ _Py_set_function_type_params(PyThreadState *Py_UNUSED(ignored), PyObject *func, } static PyGetSetDef func_getsetlist[] = { - {"__code__", func_get_code, func_set_code}, + FUNCTION___CODE___GETSETDEF {"__defaults__", func_get_defaults, func_set_defaults}, {"__kwdefaults__", func_get_kwdefaults, func_set_kwdefaults}, FUNCTION___ANNOTATIONS___GETSETDEF FUNCTION___ANNOTATE___GETSETDEF {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict}, - {"__name__", func_get_name, func_set_name}, - {"__qualname__", func_get_qualname, func_set_qualname}, + FUNCTION___NAME___GETSETDEF + FUNCTION___QUALNAME___GETSETDEF FUNCTION___TYPE_PARAMS___GETSETDEF {NULL} /* Sentinel */ }; @@ -1147,8 +1182,12 @@ static PyObject* func_repr(PyObject *self) { PyFunctionObject *op = _PyFunction_CAST(self); - return PyUnicode_FromFormat("", - op->func_qualname, op); + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(self); + result = PyUnicode_FromFormat("", + op->func_qualname, op); + Py_END_CRITICAL_SECTION(); + return result; } static int diff --git a/Objects/genobject.c b/Objects/genobject.c index 3cdc06733363d3e..9b8bb9eba5eabcc 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1106,10 +1106,12 @@ make_gen(PyTypeObject *type, PyFunctionObject *func) gen->gi_exc_state.exc_value = NULL; gen->gi_exc_state.previous_item = NULL; gen->gi_iframe.f_executable = PyStackRef_None; + Py_BEGIN_CRITICAL_SECTION((PyObject *)func); assert(func->func_name != NULL); gen->gi_name = Py_NewRef(func->func_name); assert(func->func_qualname != NULL); gen->gi_qualname = Py_NewRef(func->func_qualname); + Py_END_CRITICAL_SECTION(); _PyObject_GC_TRACK(gen); return (PyObject *)gen; }