Skip to content

Commit 5dd07eb

Browse files
authored
[3.12] gh-116510: Fix a crash due to shared immortal interned strings. (gh-124541)
Fix a crash caused by immortal interned strings being shared between sub-interpreters that use basic single-phase init. In that case, the string can be used by an interpreter that outlives the interpreter that created and interned it. For interpreters that share obmalloc state, also share the interned dict with the main interpreter.
1 parent 69fd1f2 commit 5dd07eb

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 interpeter 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
@@ -266,6 +266,23 @@ hashtable_unicode_compare(const void *key1, const void *key2)
266266
}
267267
}
268268

269+
/* Return true if this interpreter should share the main interpreter's
270+
intern_dict. That's important for interpreters which load basic
271+
single-phase init extension modules (m_size == -1). There could be interned
272+
immortal strings that are shared between interpreters, due to the
273+
PyDict_Update(mdict, m_copy) call in import_find_extension().
274+
275+
It's not safe to deallocate those strings until all interpreters that
276+
potentially use them are freed. By storing them in the main interpreter, we
277+
ensure they get freed after all other interpreters are freed.
278+
*/
279+
static bool
280+
has_shared_intern_dict(PyInterpreterState *interp)
281+
{
282+
PyInterpreterState *main_interp = _PyInterpreterState_Main();
283+
return interp != main_interp && interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC;
284+
}
285+
269286
static int
270287
init_interned_dict(PyInterpreterState *interp)
271288
{
@@ -284,9 +301,16 @@ init_interned_dict(PyInterpreterState *interp)
284301
}
285302
}
286303
assert(get_interned_dict(interp) == NULL);
287-
PyObject *interned = interned = PyDict_New();
288-
if (interned == NULL) {
289-
return -1;
304+
PyObject *interned;
305+
if (has_shared_intern_dict(interp)) {
306+
interned = get_interned_dict(_PyInterpreterState_Main());
307+
Py_INCREF(interned);
308+
}
309+
else {
310+
interned = PyDict_New();
311+
if (interned == NULL) {
312+
return -1;
313+
}
290314
}
291315
_Py_INTERP_CACHED_OBJECT(interp, interned_strings) = interned;
292316
return 0;
@@ -297,7 +321,10 @@ clear_interned_dict(PyInterpreterState *interp)
297321
{
298322
PyObject *interned = get_interned_dict(interp);
299323
if (interned != NULL) {
300-
PyDict_Clear(interned);
324+
if (!has_shared_intern_dict(interp)) {
325+
// only clear if the dict belongs to this interpreter
326+
PyDict_Clear(interned);
327+
}
301328
Py_DECREF(interned);
302329
_Py_INTERP_CACHED_OBJECT(interp, interned_strings) = NULL;
303330
}
@@ -14861,6 +14888,13 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
1486114888
}
1486214889
assert(PyDict_CheckExact(interned));
1486314890

14891+
if (has_shared_intern_dict(interp)) {
14892+
// the dict doesn't belong to this interpreter, skip the debug
14893+
// checks on it and just clear the pointer to it
14894+
clear_interned_dict(interp);
14895+
return;
14896+
}
14897+
1486414898
/* TODO:
1486514899
* Currently, the runtime is not able to guarantee that it can exit without
1486614900
* allocations that carry over to a future initialization of Python within
@@ -15364,8 +15398,10 @@ _PyUnicode_Fini(PyInterpreterState *interp)
1536415398
{
1536515399
struct _Py_unicode_state *state = &interp->unicode;
1536615400

15367-
// _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini()
15368-
assert(get_interned_dict(interp) == NULL);
15401+
if (!has_shared_intern_dict(interp)) {
15402+
// _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini()
15403+
assert(get_interned_dict(interp) == NULL);
15404+
}
1536915405

1537015406
_PyUnicode_FiniEncodings(&state->fs_codec);
1537115407

0 commit comments

Comments
 (0)