Skip to content

Commit 439ef00

Browse files
Himesh-rupchandanimiss-islington
authored andcommitted
gh-158364: Don't report other interpreters' threads in sys._current_frames (GH-158369)
(cherry picked from commit c3cb468) Co-authored-by: Himesh Rupchandani <rupchandanihimesh69@gmail.com>
1 parent fa6bc44 commit 439ef00

3 files changed

Lines changed: 114 additions & 52 deletions

File tree

‎Lib/test/test_sys.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,38 @@ def g456():
562562
leave_g.set()
563563
t.join()
564564

565+
@support.cpython_only
566+
@requires_subinterpreters
567+
@threading_helper.requires_working_threading()
568+
def test_current_frames_other_interpreters(self):
569+
# gh-158364: sys._current_frames() would access frames of another
570+
# interpreter and crash
571+
import threading
572+
573+
entered = threading.Event()
574+
left = threading.Event()
575+
576+
def park():
577+
entered.set()
578+
left.wait()
579+
580+
t = threading.Thread(target=park)
581+
with threading_helper.start_threads([t], unlock=left.set):
582+
entered.wait()
583+
interp = interpreters.create()
584+
try:
585+
interp.exec(f"""if True:
586+
import sys
587+
import threading
588+
589+
frames = sys._current_frames()
590+
assert threading.get_ident() in frames, frames
591+
assert frames[threading.get_ident()].f_globals is globals()
592+
assert {t.ident} not in frames, frames
593+
""")
594+
finally:
595+
interp.close()
596+
565597
@threading_helper.reap_threads
566598
@threading_helper.requires_working_threading()
567599
def test_current_exceptions(self):
@@ -628,6 +660,39 @@ def g456():
628660
leave_g.set()
629661
t.join()
630662

663+
@support.cpython_only
664+
@requires_subinterpreters
665+
@threading_helper.requires_working_threading()
666+
def test_current_exceptions_other_interpreters(self):
667+
# gh-158364: sys._current_exceptions() would hand out exceptions of
668+
# another interpreter and crash
669+
import threading
670+
671+
entered = threading.Event()
672+
left = threading.Event()
673+
674+
def hold():
675+
# The thread has to be handling an exception, otherwise
676+
# sys._current_exceptions() has nothing to report for it.
677+
try:
678+
raise ValueError
679+
except ValueError:
680+
entered.set()
681+
left.wait()
682+
683+
t = threading.Thread(target=hold)
684+
with threading_helper.start_threads([t], unlock=left.set):
685+
entered.wait()
686+
interp = interpreters.create()
687+
try:
688+
interp.exec(f"""if True:
689+
import sys
690+
691+
assert {t.ident} not in sys._current_exceptions()
692+
""")
693+
finally:
694+
interp.close()
695+
631696
def test_attributes(self):
632697
self.assertIsInstance(sys.api_version, int)
633698
self.assertIsInstance(sys.argv, list)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash when :func:`sys._current_frames` or
2+
:func:`sys._current_exceptions` is called while another interpreter is
3+
running.

‎Python/pystate.c‎

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,36 +2692,33 @@ _PyThread_CurrentFrames(void)
26922692
return NULL;
26932693
}
26942694

2695-
/* for i in all interpreters:
2696-
* for t in all of i's thread states:
2697-
* if t's frame isn't NULL, map t's id to its frame
2695+
/* for t in all of the current interpreter's thread states:
2696+
* if t's frame isn't NULL, map t's id to its frame
26982697
* Because these lists can mutate even when the GIL is held, we
26992698
* need to grab head_mutex for the duration.
27002699
*/
2701-
_PyEval_StopTheWorldAll(runtime);
2700+
PyInterpreterState *interp = tstate->interp;
2701+
_PyEval_StopTheWorld(interp);
27022702
HEAD_LOCK(runtime);
2703-
PyInterpreterState *i;
2704-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2705-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2706-
_PyInterpreterFrame *frame = t->current_frame;
2707-
frame = _PyFrame_GetFirstComplete(frame);
2708-
if (frame == NULL) {
2709-
continue;
2710-
}
2711-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2712-
if (id == NULL) {
2713-
goto fail;
2714-
}
2715-
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2716-
if (frameobj == NULL) {
2717-
Py_DECREF(id);
2718-
goto fail;
2719-
}
2720-
int stat = PyDict_SetItem(result, id, frameobj);
2703+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2704+
_PyInterpreterFrame *frame = t->current_frame;
2705+
frame = _PyFrame_GetFirstComplete(frame);
2706+
if (frame == NULL) {
2707+
continue;
2708+
}
2709+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2710+
if (id == NULL) {
2711+
goto fail;
2712+
}
2713+
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2714+
if (frameobj == NULL) {
27212715
Py_DECREF(id);
2722-
if (stat < 0) {
2723-
goto fail;
2724-
}
2716+
goto fail;
2717+
}
2718+
int stat = PyDict_SetItem(result, id, frameobj);
2719+
Py_DECREF(id);
2720+
if (stat < 0) {
2721+
goto fail;
27252722
}
27262723
}
27272724
goto done;
@@ -2731,7 +2728,7 @@ _PyThread_CurrentFrames(void)
27312728

27322729
done:
27332730
HEAD_UNLOCK(runtime);
2734-
_PyEval_StartTheWorldAll(runtime);
2731+
_PyEval_StartTheWorld(interp);
27352732
return result;
27362733
}
27372734

@@ -2757,35 +2754,32 @@ _PyThread_CurrentExceptions(void)
27572754
return NULL;
27582755
}
27592756

2760-
/* for i in all interpreters:
2761-
* for t in all of i's thread states:
2762-
* if t's frame isn't NULL, map t's id to its frame
2757+
/* for t in all of the current interpreter's thread states:
2758+
* if t's frame isn't NULL, map t's id to its exception
27632759
* Because these lists can mutate even when the GIL is held, we
27642760
* need to grab head_mutex for the duration.
27652761
*/
2766-
_PyEval_StopTheWorldAll(runtime);
2762+
PyInterpreterState *interp = tstate->interp;
2763+
_PyEval_StopTheWorld(interp);
27672764
HEAD_LOCK(runtime);
2768-
PyInterpreterState *i;
2769-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2770-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2771-
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2772-
if (err_info == NULL) {
2773-
continue;
2774-
}
2775-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2776-
if (id == NULL) {
2777-
goto fail;
2778-
}
2779-
PyObject *exc = err_info->exc_value;
2780-
assert(exc == NULL ||
2781-
exc == Py_None ||
2782-
PyExceptionInstance_Check(exc));
2783-
2784-
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2785-
Py_DECREF(id);
2786-
if (stat < 0) {
2787-
goto fail;
2788-
}
2765+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2766+
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2767+
if (err_info == NULL) {
2768+
continue;
2769+
}
2770+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2771+
if (id == NULL) {
2772+
goto fail;
2773+
}
2774+
PyObject *exc = err_info->exc_value;
2775+
assert(exc == NULL ||
2776+
exc == Py_None ||
2777+
PyExceptionInstance_Check(exc));
2778+
2779+
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2780+
Py_DECREF(id);
2781+
if (stat < 0) {
2782+
goto fail;
27892783
}
27902784
}
27912785
goto done;
@@ -2795,7 +2789,7 @@ _PyThread_CurrentExceptions(void)
27952789

27962790
done:
27972791
HEAD_UNLOCK(runtime);
2798-
_PyEval_StartTheWorldAll(runtime);
2792+
_PyEval_StartTheWorld(interp);
27992793
return result;
28002794
}
28012795

0 commit comments

Comments
 (0)