Skip to content

Commit d76c56e

Browse files
[3.14] gh-145142: Make str.maketrans safe under free-threading (gh-145157) (#145320)
Co-authored-by: VanshAgarwal24036 <148854295+VanshAgarwal24036@users.noreply.github.com>
1 parent 1e4b4a6 commit d76c56e

File tree

3 files changed

+63
-31
lines changed

3 files changed

+63
-31
lines changed

Lib/test/test_free_threading/test_str.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ def reader_func():
6969
for reader in readers:
7070
reader.join()
7171

72+
def test_maketrans_dict_concurrent_modification(self):
73+
for _ in range(5):
74+
d = {2000: 'a'}
75+
76+
def work(dct):
77+
for i in range(100):
78+
str.maketrans(dct)
79+
dct[2000 + i] = chr(i % 16)
80+
dct.pop(2000 + i, None)
81+
82+
threading_helper.run_concurrently(
83+
work,
84+
nthreads=5,
85+
args=(d,),
86+
)
87+
7288

7389
if __name__ == "__main__":
7490
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in the free-threaded build when the dictionary argument to
2+
:meth:`str.maketrans` is concurrently modified.

Objects/unicodeobject.c

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13366,6 +13366,45 @@ unicode_swapcase_impl(PyObject *self)
1336613366
return case_operation(self, do_swapcase);
1336713367
}
1336813368

13369+
static int
13370+
unicode_maketrans_from_dict(PyObject *x, PyObject *newdict)
13371+
{
13372+
PyObject *key, *value;
13373+
Py_ssize_t i = 0;
13374+
int res;
13375+
while (PyDict_Next(x, &i, &key, &value)) {
13376+
if (PyUnicode_Check(key)) {
13377+
PyObject *newkey;
13378+
int kind;
13379+
const void *data;
13380+
if (PyUnicode_GET_LENGTH(key) != 1) {
13381+
PyErr_SetString(PyExc_ValueError, "string keys in translate"
13382+
"table must be of length 1");
13383+
return -1;
13384+
}
13385+
kind = PyUnicode_KIND(key);
13386+
data = PyUnicode_DATA(key);
13387+
newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0));
13388+
if (!newkey)
13389+
return -1;
13390+
res = PyDict_SetItem(newdict, newkey, value);
13391+
Py_DECREF(newkey);
13392+
if (res < 0)
13393+
return -1;
13394+
}
13395+
else if (PyLong_Check(key)) {
13396+
if (PyDict_SetItem(newdict, key, value) < 0)
13397+
return -1;
13398+
}
13399+
else {
13400+
PyErr_SetString(PyExc_TypeError, "keys in translate table must"
13401+
"be strings or integers");
13402+
return -1;
13403+
}
13404+
}
13405+
return 0;
13406+
}
13407+
1336913408
/*[clinic input]
1337013409
1337113410
@staticmethod
@@ -13451,44 +13490,19 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z)
1345113490
}
1345213491
}
1345313492
} else {
13454-
int kind;
13455-
const void *data;
13456-
1345713493
/* x must be a dict */
1345813494
if (!PyDict_CheckExact(x)) {
1345913495
PyErr_SetString(PyExc_TypeError, "if you give only one argument "
1346013496
"to maketrans it must be a dict");
1346113497
goto err;
1346213498
}
1346313499
/* copy entries into the new dict, converting string keys to int keys */
13464-
while (PyDict_Next(x, &i, &key, &value)) {
13465-
if (PyUnicode_Check(key)) {
13466-
/* convert string keys to integer keys */
13467-
PyObject *newkey;
13468-
if (PyUnicode_GET_LENGTH(key) != 1) {
13469-
PyErr_SetString(PyExc_ValueError, "string keys in translate "
13470-
"table must be of length 1");
13471-
goto err;
13472-
}
13473-
kind = PyUnicode_KIND(key);
13474-
data = PyUnicode_DATA(key);
13475-
newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0));
13476-
if (!newkey)
13477-
goto err;
13478-
res = PyDict_SetItem(new, newkey, value);
13479-
Py_DECREF(newkey);
13480-
if (res < 0)
13481-
goto err;
13482-
} else if (PyLong_Check(key)) {
13483-
/* just keep integer keys */
13484-
if (PyDict_SetItem(new, key, value) < 0)
13485-
goto err;
13486-
} else {
13487-
PyErr_SetString(PyExc_TypeError, "keys in translate table must "
13488-
"be strings or integers");
13489-
goto err;
13490-
}
13491-
}
13500+
int errcode;
13501+
Py_BEGIN_CRITICAL_SECTION(x);
13502+
errcode = unicode_maketrans_from_dict(x, new);
13503+
Py_END_CRITICAL_SECTION();
13504+
if (errcode < 0)
13505+
goto err;
1349213506
}
1349313507
return new;
1349413508
err:

0 commit comments

Comments
 (0)