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]

Reply via email to