Skip to content

Commit a249795

Browse files
gh-145142: Make str.maketrans safe under free-threading (gh-145157)
1 parent 8775f90 commit a249795

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
@@ -13060,6 +13060,45 @@ unicode_swapcase_impl(PyObject *self)
1306013060
return case_operation(self, do_swapcase);
1306113061
}
1306213062

13063+
static int
13064+
unicode_maketrans_from_dict(PyObject *x, PyObject *newdict)
13065+
{
13066+
PyObject *key, *value;
13067+
Py_ssize_t i = 0;
13068+
int res;
13069+
while (PyDict_Next(x, &i, &key, &value)) {
13070+
if (PyUnicode_Check(key)) {
13071+
PyObject *newkey;
13072+
int kind;
13073+
const void *data;
13074+
if (PyUnicode_GET_LENGTH(key) != 1) {
13075+
PyErr_SetString(PyExc_ValueError, "string keys in translate"
13076+
"table must be of length 1");
13077+
return -1;
13078+
}
13079+
kind = PyUnicode_KIND(key);
13080+
data = PyUnicode_DATA(key);
13081+
newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0));
13082+
if (!newkey)
13083+
return -1;
13084+
res = PyDict_SetItem(newdict, newkey, value);
13085+
Py_DECREF(newkey);
13086+
if (res < 0)
13087+
return -1;
13088+
}
13089+
else if (PyLong_Check(key)) {
13090+
if (PyDict_SetItem(newdict, key, value) < 0)
13091+
return -1;
13092+
}
13093+
else {
13094+
PyErr_SetString(PyExc_TypeError, "keys in translate table must"
13095+
"be strings or integers");
13096+
return -1;
13097+
}
13098+
}
13099+
return 0;
13100+
}
13101+
1306313102
/*[clinic input]
1306413103
1306513104
@staticmethod
@@ -13145,44 +13184,19 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z)
1314513184
}
1314613185
}
1314713186
} else {
13148-
int kind;
13149-
const void *data;
13150-
1315113187
/* x must be a dict */
1315213188
if (!PyAnyDict_CheckExact(x)) {
1315313189
PyErr_SetString(PyExc_TypeError, "if you give only one argument "
1315413190
"to maketrans it must be a dict");
1315513191
goto err;
1315613192
}
1315713193
/* copy entries into the new dict, converting string keys to int keys */
13158-
while (PyDict_Next(x, &i, &key, &value)) {
13159-
if (PyUnicode_Check(key)) {
13160-
/* convert string keys to integer keys */
13161-
PyObject *newkey;
13162-
if (PyUnicode_GET_LENGTH(key) != 1) {
13163-
PyErr_SetString(PyExc_ValueError, "string keys in translate "
13164-
"table must be of length 1");
13165-
goto err;
13166-
}
13167-
kind = PyUnicode_KIND(key);
13168-
data = PyUnicode_DATA(key);
13169-
newkey = PyLong_FromLong(PyUnicode_READ(kind, data, 0));
13170-
if (!newkey)
13171-
goto err;
13172-
res = PyDict_SetItem(new, newkey, value);
13173-
Py_DECREF(newkey);
13174-
if (res < 0)
13175-
goto err;
13176-
} else if (PyLong_Check(key)) {
13177-
/* just keep integer keys */
13178-
if (PyDict_SetItem(new, key, value) < 0)
13179-
goto err;
13180-
} else {
13181-
PyErr_SetString(PyExc_TypeError, "keys in translate table must "
13182-
"be strings or integers");
13183-
goto err;
13184-
}
13185-
}
13194+
int errcode;
13195+
Py_BEGIN_CRITICAL_SECTION(x);
13196+
errcode = unicode_maketrans_from_dict(x, new);
13197+
Py_END_CRITICAL_SECTION();
13198+
if (errcode < 0)
13199+
goto err;
1318613200
}
1318713201
return new;
1318813202
err:

0 commit comments

Comments
 (0)