diff --git a/Lib/test/test_free_threading/test_str.py b/Lib/test/test_free_threading/test_str.py index 72044e979b0f48..9a1ce3620ac4b2 100644 --- a/Lib/test/test_free_threading/test_str.py +++ b/Lib/test/test_free_threading/test_str.py @@ -69,6 +69,22 @@ def reader_func(): for reader in readers: reader.join() + def test_maketrans_dict_concurrent_modification(self): + for _ in range(5): + d = {2000: 'a'} + + def work(dct): + for i in range(100): + str.maketrans(dct) + dct[2000 + i] = chr(i % 16) + dct.pop(2000 + i, None) + + threading_helper.run_concurrently( + work, + nthreads=5, + args=(d,), + ) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-23-23-18-28.gh-issue-145142.T-XbVe.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-23-23-18-28.gh-issue-145142.T-XbVe.rst new file mode 100644 index 00000000000000..5f6043cc3d9660 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-23-23-18-28.gh-issue-145142.T-XbVe.rst @@ -0,0 +1,2 @@ +Fix a crash in the free-threaded build when the dictionary argument to +:meth:`str.maketrans` is concurrently modified. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index fdcbcf51cb62c2..9f90f7d27abe73 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13155,6 +13155,7 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) goto err; } /* copy entries into the new dict, converting string keys to int keys */ + Py_BEGIN_CRITICAL_SECTION(x); while (PyDict_Next(x, &i, &key, &value)) { if (PyUnicode_Check(key)) { /* convert string keys to integer keys */ @@ -13162,27 +13163,33 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) if (PyUnicode_GET_LENGTH(key) != 1) { PyErr_SetString(PyExc_ValueError, "string keys in translate " "table must be of length 1"); - goto err; + goto error; } kind = PyUnicode_KIND(key); data = PyUnicode_DATA(key); newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0)); if (!newkey) - goto err; + goto error; res = PyDict_SetItem(new, newkey, value); Py_DECREF(newkey); if (res < 0) - goto err; + goto error; } else if (PyLong_Check(key)) { /* just keep integer keys */ if (PyDict_SetItem(new, key, value) < 0) - goto err; + goto error; } else { PyErr_SetString(PyExc_TypeError, "keys in translate table must " "be strings or integers"); - goto err; + goto error; } } + goto done; + error: + Py_CLEAR(new); + done: + Py_END_CRITICAL_SECTION(); + return new; } return new; err: