Skip to content

Commit 545124f

Browse files
[3.15] gh-158364: Don't report other interpreters' threads in sys._current_frames (GH-158369) (#158471)
Co-authored-by: Himesh Rupchandani <rupchandanihimesh69@gmail.com>
1 parent 3740f74 commit 545124f

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
@@ -2725,36 +2725,33 @@ _PyThread_CurrentFrames(void)
27252725
return NULL;
27262726
}
27272727

2728-
/* for i in all interpreters:
2729-
* for t in all of i's thread states:
2730-
* if t's frame isn't NULL, map t's id to its frame
2728+
/* for t in all of the current interpreter's thread states:
2729+
* if t's frame isn't NULL, map t's id to its frame
27312730
* Because these lists can mutate even when the GIL is held, we
27322731
* need to grab head_mutex for the duration.
27332732
*/
2734-
_PyEval_StopTheWorldAll(runtime);
2733+
PyInterpreterState *interp = tstate->interp;
2734+
_PyEval_StopTheWorld(interp);
27352735
HEAD_LOCK(runtime);
2736-
PyInterpreterState *i;
2737-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2738-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2739-
_PyInterpreterFrame *frame = t->current_frame;
2740-
frame = _PyFrame_GetFirstComplete(frame);
2741-
if (frame == NULL) {
2742-
continue;
2743-
}
2744-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2745-
if (id == NULL) {
2746-
goto fail;
2747-
}
2748-
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2749-
if (frameobj == NULL) {
2750-
Py_DECREF(id);
2751-
goto fail;
2752-
}
2753-
int stat = PyDict_SetItem(result, id, frameobj);
2736+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2737+
_PyInterpreterFrame *frame = t->current_frame;
2738+
frame = _PyFrame_GetFirstComplete(frame);
2739+
if (frame == NULL) {
2740+
continue;
2741+
}
2742+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2743+
if (id == NULL) {
2744+
goto fail;
2745+
}
2746+
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2747+
if (frameobj == NULL) {
27542748
Py_DECREF(id);
2755-
if (stat < 0) {
2756-
goto fail;
2757-
}
2749+
goto fail;
2750+
}
2751+
int stat = PyDict_SetItem(result, id, frameobj);
2752+
Py_DECREF(id);
2753+
if (stat < 0) {
2754+
goto fail;
27582755
}
27592756
}
27602757
goto done;
@@ -2764,7 +2761,7 @@ _PyThread_CurrentFrames(void)
27642761

27652762
done:
27662763
HEAD_UNLOCK(runtime);
2767-
_PyEval_StartTheWorldAll(runtime);
2764+
_PyEval_StartTheWorld(interp);
27682765
return result;
27692766
}
27702767

@@ -2790,35 +2787,32 @@ _PyThread_CurrentExceptions(void)
27902787
return NULL;
27912788
}
27922789

2793-
/* for i in all interpreters:
2794-
* for t in all of i's thread states:
2795-
* if t's frame isn't NULL, map t's id to its frame
2790+
/* for t in all of the current interpreter's thread states:
2791+
* if t's frame isn't NULL, map t's id to its exception
27962792
* Because these lists can mutate even when the GIL is held, we
27972793
* need to grab head_mutex for the duration.
27982794
*/
2799-
_PyEval_StopTheWorldAll(runtime);
2795+
PyInterpreterState *interp = tstate->interp;
2796+
_PyEval_StopTheWorld(interp);
28002797
HEAD_LOCK(runtime);
2801-
PyInterpreterState *i;
2802-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2803-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2804-
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2805-
if (err_info == NULL) {
2806-
continue;
2807-
}
2808-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2809-
if (id == NULL) {
2810-
goto fail;
2811-
}
2812-
PyObject *exc = err_info->exc_value;
2813-
assert(exc == NULL ||
2814-
exc == Py_None ||
2815-
PyExceptionInstance_Check(exc));
2816-
2817-
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2818-
Py_DECREF(id);
2819-
if (stat < 0) {
2820-
goto fail;
2821-
}
2798+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2799+
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2800+
if (err_info == NULL) {
2801+
continue;
2802+
}
2803+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2804+
if (id == NULL) {
2805+
goto fail;
2806+
}
2807+
PyObject *exc = err_info->exc_value;
2808+
assert(exc == NULL ||
2809+
exc == Py_None ||
2810+
PyExceptionInstance_Check(exc));
2811+
2812+
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2813+
Py_DECREF(id);
2814+
if (stat < 0) {
2815+
goto fail;
28222816
}
28232817
}
28242818
goto done;
@@ -2828,7 +2822,7 @@ _PyThread_CurrentExceptions(void)
28282822

28292823
done:
28302824
HEAD_UNLOCK(runtime);
2831-
_PyEval_StartTheWorldAll(runtime);
2825+
_PyEval_StartTheWorld(interp);
28322826
return result;
28332827
}
28342828

0 commit comments

Comments
 (0)