Skip to content

Commit c3cb468

Browse files
gh-158364: Don't report other interpreters' threads in sys._current_frames (GH-158369)
1 parent 26d3f5d commit c3cb468

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
@@ -563,6 +563,38 @@ def g456():
563563
leave_g.set()
564564
t.join()
565565

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

664+
@support.cpython_only
665+
@requires_subinterpreters
666+
@threading_helper.requires_working_threading()
667+
def test_current_exceptions_other_interpreters(self):
668+
# gh-158364: sys._current_exceptions() would hand out exceptions of
669+
# another interpreter and crash
670+
import threading
671+
672+
entered = threading.Event()
673+
left = threading.Event()
674+
675+
def hold():
676+
# The thread has to be handling an exception, otherwise
677+
# sys._current_exceptions() has nothing to report for it.
678+
try:
679+
raise ValueError
680+
except ValueError:
681+
entered.set()
682+
left.wait()
683+
684+
t = threading.Thread(target=hold)
685+
with threading_helper.start_threads([t], unlock=left.set):
686+
entered.wait()
687+
interp = interpreters.create()
688+
try:
689+
interp.exec(f"""if True:
690+
import sys
691+
692+
assert {t.ident} not in sys._current_exceptions()
693+
""")
694+
finally:
695+
interp.close()
696+
632697
def test_attributes(self):
633698
self.assertIsInstance(sys.api_version, int)
634699
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
@@ -2801,36 +2801,33 @@ _PyThread_CurrentFrames(void)
28012801
return NULL;
28022802
}
28032803

2804-
/* for i in all interpreters:
2805-
* for t in all of i's thread states:
2806-
* if t's frame isn't NULL, map t's id to its frame
2804+
/* for t in all of the current interpreter's thread states:
2805+
* if t's frame isn't NULL, map t's id to its frame
28072806
* Because these lists can mutate even when the GIL is held, we
28082807
* need to grab head_mutex for the duration.
28092808
*/
2810-
_PyEval_StopTheWorldAll(runtime);
2809+
PyInterpreterState *interp = tstate->interp;
2810+
_PyEval_StopTheWorld(interp);
28112811
HEAD_LOCK(runtime);
2812-
PyInterpreterState *i;
2813-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2814-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2815-
_PyInterpreterFrame *frame = t->current_frame;
2816-
frame = _PyFrame_GetFirstComplete(frame);
2817-
if (frame == NULL) {
2818-
continue;
2819-
}
2820-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2821-
if (id == NULL) {
2822-
goto fail;
2823-
}
2824-
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2825-
if (frameobj == NULL) {
2826-
Py_DECREF(id);
2827-
goto fail;
2828-
}
2829-
int stat = PyDict_SetItem(result, id, frameobj);
2812+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2813+
_PyInterpreterFrame *frame = t->current_frame;
2814+
frame = _PyFrame_GetFirstComplete(frame);
2815+
if (frame == NULL) {
2816+
continue;
2817+
}
2818+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2819+
if (id == NULL) {
2820+
goto fail;
2821+
}
2822+
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2823+
if (frameobj == NULL) {
28302824
Py_DECREF(id);
2831-
if (stat < 0) {
2832-
goto fail;
2833-
}
2825+
goto fail;
2826+
}
2827+
int stat = PyDict_SetItem(result, id, frameobj);
2828+
Py_DECREF(id);
2829+
if (stat < 0) {
2830+
goto fail;
28342831
}
28352832
}
28362833
goto done;
@@ -2840,7 +2837,7 @@ _PyThread_CurrentFrames(void)
28402837

28412838
done:
28422839
HEAD_UNLOCK(runtime);
2843-
_PyEval_StartTheWorldAll(runtime);
2840+
_PyEval_StartTheWorld(interp);
28442841
return result;
28452842
}
28462843

@@ -2866,35 +2863,32 @@ _PyThread_CurrentExceptions(void)
28662863
return NULL;
28672864
}
28682865

2869-
/* for i in all interpreters:
2870-
* for t in all of i's thread states:
2871-
* if t's frame isn't NULL, map t's id to its frame
2866+
/* for t in all of the current interpreter's thread states:
2867+
* if t's frame isn't NULL, map t's id to its exception
28722868
* Because these lists can mutate even when the GIL is held, we
28732869
* need to grab head_mutex for the duration.
28742870
*/
2875-
_PyEval_StopTheWorldAll(runtime);
2871+
PyInterpreterState *interp = tstate->interp;
2872+
_PyEval_StopTheWorld(interp);
28762873
HEAD_LOCK(runtime);
2877-
PyInterpreterState *i;
2878-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2879-
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
2880-
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2881-
if (err_info == NULL) {
2882-
continue;
2883-
}
2884-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2885-
if (id == NULL) {
2886-
goto fail;
2887-
}
2888-
PyObject *exc = err_info->exc_value;
2889-
assert(exc == NULL ||
2890-
exc == Py_None ||
2891-
PyExceptionInstance_Check(exc));
2892-
2893-
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2894-
Py_DECREF(id);
2895-
if (stat < 0) {
2896-
goto fail;
2897-
}
2874+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
2875+
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2876+
if (err_info == NULL) {
2877+
continue;
2878+
}
2879+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2880+
if (id == NULL) {
2881+
goto fail;
2882+
}
2883+
PyObject *exc = err_info->exc_value;
2884+
assert(exc == NULL ||
2885+
exc == Py_None ||
2886+
PyExceptionInstance_Check(exc));
2887+
2888+
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2889+
Py_DECREF(id);
2890+
if (stat < 0) {
2891+
goto fail;
28982892
}
28992893
}
29002894
goto done;
@@ -2904,7 +2898,7 @@ _PyThread_CurrentExceptions(void)
29042898

29052899
done:
29062900
HEAD_UNLOCK(runtime);
2907-
_PyEval_StartTheWorldAll(runtime);
2901+
_PyEval_StartTheWorld(interp);
29082902
return result;
29092903
}
29102904

0 commit comments

Comments
 (0)