https://github.com/python/cpython/commit/a2497955387bc463f05111b803599a92dcfcae29
commit: a2497955387bc463f05111b803599a92dcfcae29
branch: main
author: VanshAgarwal24036 <[email protected]>
committer: colesbury <[email protected]>
date: 2026-02-27T16:08:15Z
summary:
gh-145142: Make str.maketrans safe under free-threading (gh-145157)
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 988e5f95573fe1..213bae5ca86cd4 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -13060,6 +13060,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
@@ -13145,9 +13184,6 @@ unicode_maketrans_impl(PyObject *x, PyObject *y,
PyObject *z)
}
}
} else {
- int kind;
- const void *data;
-
/* x must be a dict */
if (!PyAnyDict_CheckExact(x)) {
PyErr_SetString(PyExc_TypeError, "if you give only one argument "
@@ -13155,34 +13191,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]