Skip to content

Commit dc09a0c

Browse files
[3.13] gh-116510: Fix crash due to shared immortal interned strings. (gh-124646) (#124648)
gh-116510: Fix crash due to shared immortal interned strings. (gh-124646) (cherry picked from commit 98b2ed7) Co-authored-by: Neil Schemenauer <[email protected]>
1 parent 1cd2b97 commit dc09a0c

File tree

2 files changed

+47
-6
lines changed

2 files changed

+47
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a crash caused by immortal interned strings being shared between
2+
sub-interpreters that use basic single-phase init. In that case, the string
3+
can be used by an interpreter that outlives the interpreter that created and
4+
interned it. For interpreters that share obmalloc state, also share the
5+
interned dict with the main interpreter.

Objects/unicodeobject.c

+42-6
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,37 @@ hashtable_unicode_compare(const void *key1, const void *key2)
277277
}
278278
}
279279

280+
/* Return true if this interpreter should share the main interpreter's
281+
intern_dict. That's important for interpreters which load basic
282+
single-phase init extension modules (m_size == -1). There could be interned
283+
immortal strings that are shared between interpreters, due to the
284+
PyDict_Update(mdict, m_copy) call in import_find_extension().
285+
286+
It's not safe to deallocate those strings until all interpreters that
287+
potentially use them are freed. By storing them in the main interpreter, we
288+
ensure they get freed after all other interpreters are freed.
289+
*/
290+
static bool
291+
has_shared_intern_dict(PyInterpreterState *interp)
292+
{
293+
PyInterpreterState *main_interp = _PyInterpreterState_Main();
294+
return interp != main_interp && interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC;
295+
}
296+
280297
static int
281298
init_interned_dict(PyInterpreterState *interp)
282299
{
283300
assert(get_interned_dict(interp) == NULL);
284-
PyObject *interned = interned = PyDict_New();
285-
if (interned == NULL) {
286-
return -1;
301+
PyObject *interned;
302+
if (has_shared_intern_dict(interp)) {
303+
interned = get_interned_dict(_PyInterpreterState_Main());
304+
Py_INCREF(interned);
305+
}
306+
else {
307+
interned = PyDict_New();
308+
if (interned == NULL) {
309+
return -1;
310+
}
287311
}
288312
_Py_INTERP_CACHED_OBJECT(interp, interned_strings) = interned;
289313
return 0;
@@ -294,7 +318,10 @@ clear_interned_dict(PyInterpreterState *interp)
294318
{
295319
PyObject *interned = get_interned_dict(interp);
296320
if (interned != NULL) {
297-
PyDict_Clear(interned);
321+
if (!has_shared_intern_dict(interp)) {
322+
// only clear if the dict belongs to this interpreter
323+
PyDict_Clear(interned);
324+
}
298325
Py_DECREF(interned);
299326
_Py_INTERP_CACHED_OBJECT(interp, interned_strings) = NULL;
300327
}
@@ -15306,6 +15333,13 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1530615333
}
1530715334
assert(PyDict_CheckExact(interned));
1530815335

15336+
if (has_shared_intern_dict(interp)) {
15337+
// the dict doesn't belong to this interpreter, skip the debug
15338+
// checks on it and just clear the pointer to it
15339+
clear_interned_dict(interp);
15340+
return;
15341+
}
15342+
1530915343
#ifdef INTERNED_STATS
1531015344
fprintf(stderr, "releasing %zd interned strings\n",
1531115345
PyDict_GET_SIZE(interned));
@@ -15827,8 +15861,10 @@ _PyUnicode_Fini(PyInterpreterState *interp)
1582715861
{
1582815862
struct _Py_unicode_state *state = &interp->unicode;
1582915863

15830-
// _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini()
15831-
assert(get_interned_dict(interp) == NULL);
15864+
if (!has_shared_intern_dict(interp)) {
15865+
// _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini()
15866+
assert(get_interned_dict(interp) == NULL);
15867+
}
1583215868

1583315869
_PyUnicode_FiniEncodings(&state->fs_codec);
1583415870

0 commit comments

Comments
 (0)