https://github.com/python/cpython/commit/d76c56e958c9a603ded42d27b39ab51c1e3794e4 commit: d76c56e958c9a603ded42d27b39ab51c1e3794e4 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: colesbury <[email protected]> date: 2026-02-27T16:33:46Z summary:
[3.14] gh-145142: Make str.maketrans safe under free-threading (gh-145157) (#145320) Co-authored-by: VanshAgarwal24036 <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-02-23-23-18-28.gh-issue-145142.T-XbVe.rst M Lib/test/test_free_threading/test_str.py M Objects/unicodeobject.c 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 c71c5720ea090e..3835b8d462a10d 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13366,6 +13366,45 @@ unicode_swapcase_impl(PyObject *self) return case_operation(self, do_swapcase); } +static int +unicode_maketrans_from_dict(PyObject *x, PyObject *newdict) +{ + PyObject *key, *value; + Py_ssize_t i = 0; + int res; + while (PyDict_Next(x, &i, &key, &value)) { + if (PyUnicode_Check(key)) { + PyObject *newkey; + int kind; + const void *data; + if (PyUnicode_GET_LENGTH(key) != 1) { + PyErr_SetString(PyExc_ValueError, "string keys in translate" + "table must be of length 1"); + return -1; + } + kind = PyUnicode_KIND(key); + data = PyUnicode_DATA(key); + newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0)); + if (!newkey) + return -1; + res = PyDict_SetItem(newdict, newkey, value); + Py_DECREF(newkey); + if (res < 0) + return -1; + } + else if (PyLong_Check(key)) { + if (PyDict_SetItem(newdict, key, value) < 0) + return -1; + } + else { + PyErr_SetString(PyExc_TypeError, "keys in translate table must" + "be strings or integers"); + return -1; + } + } + return 0; +} + /*[clinic input] @staticmethod @@ -13451,9 +13490,6 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) } } } else { - int kind; - const void *data; - /* x must be a dict */ if (!PyDict_CheckExact(x)) { PyErr_SetString(PyExc_TypeError, "if you give only one argument " @@ -13461,34 +13497,12 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) goto err; } /* copy entries into the new dict, converting string keys to int keys */ - while (PyDict_Next(x, &i, &key, &value)) { - if (PyUnicode_Check(key)) { - /* convert string keys to integer keys */ - PyObject *newkey; - if (PyUnicode_GET_LENGTH(key) != 1) { - PyErr_SetString(PyExc_ValueError, "string keys in translate " - "table must be of length 1"); - goto err; - } - kind = PyUnicode_KIND(key); - data = PyUnicode_DATA(key); - newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0)); - if (!newkey) - goto err; - res = PyDict_SetItem(new, newkey, value); - Py_DECREF(newkey); - if (res < 0) - goto err; - } else if (PyLong_Check(key)) { - /* just keep integer keys */ - if (PyDict_SetItem(new, key, value) < 0) - goto err; - } else { - PyErr_SetString(PyExc_TypeError, "keys in translate table must " - "be strings or integers"); - goto err; - } - } + int errcode; + Py_BEGIN_CRITICAL_SECTION(x); + errcode = unicode_maketrans_from_dict(x, new); + Py_END_CRITICAL_SECTION(); + if (errcode < 0) + goto err; } return new; err: _______________________________________________ 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]
