Skip to content

Commit 36ebb65

Browse files
[3.13] gh-158364: Don't report other interpreters' threads in sys._current_frames (GH-158369) (GH-158473)
(cherry picked from commit c3cb468) Co-authored-by: Himesh Rupchandani <rupchandanihimesh69@gmail.com>
1 parent 1c43d8e commit 36ebb65

3 files changed

Lines changed: 116 additions & 54 deletions

File tree

‎Lib/test/test_sys.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,38 @@ def g456():
587587
leave_g.set()
588588
t.join()
589589

590+
@support.cpython_only
591+
@requires_subinterpreters
592+
@threading_helper.requires_working_threading()
593+
def test_current_frames_other_interpreters(self):
594+
# gh-158364: sys._current_frames() would access frames of another
595+
# interpreter and crash
596+
import threading
597+
598+
entered = threading.Event()
599+
left = threading.Event()
600+
601+
def park():
602+
entered.set()
603+
left.wait()
604+
605+
t = threading.Thread(target=park)
606+
with threading_helper.start_threads([t], unlock=left.set):
607+
entered.wait()
608+
interp = interpreters.create()
609+
try:
610+
interp.exec(f"""if True:
611+
import sys
612+
import threading
613+
614+
frames = sys._current_frames()
615+
assert threading.get_ident() in frames, frames
616+
assert frames[threading.get_ident()].f_globals is globals()
617+
assert {t.ident} not in frames, frames
618+
""")
619+
finally:
620+
interp.close()
621+
590622
@threading_helper.reap_threads
591623
@threading_helper.requires_working_threading()
592624
def test_current_exceptions(self):
@@ -654,6 +686,39 @@ def g456():
654686
leave_g.set()
655687
t.join()
656688

689+
@support.cpython_only
690+
@requires_subinterpreters
691+
@threading_helper.requires_working_threading()
692+
def test_current_exceptions_other_interpreters(self):
693+
# gh-158364: sys._current_exceptions() would hand out exceptions of
694+
# another interpreter and crash
695+
import threading
696+
697+
entered = threading.Event()
698+
left = threading.Event()
699+
700+
def hold():
701+
# The thread has to be handling an exception, otherwise
702+
# sys._current_exceptions() has nothing to report for it.
703+
try:
704+
raise ValueError
705+
except ValueError:
706+
entered.set()
707+
left.wait()
708+
709+
t = threading.Thread(target=hold)
710+
with threading_helper.start_threads([t], unlock=left.set):
711+
entered.wait()
712+
interp = interpreters.create()
713+
try:
714+
interp.exec(f"""if True:
715+
import sys
716+
717+
assert {t.ident} not in sys._current_exceptions()
718+
""")
719+
finally:
720+
interp.close()
721+
657722
def test_attributes(self):
658723
self.assertIsInstance(sys.api_version, int)
659724
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: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2547,37 +2547,34 @@ _PyThread_CurrentFrames(void)
25472547
return NULL;
25482548
}
25492549

2550-
/* for i in all interpreters:
2551-
* for t in all of i's thread states:
2552-
* if t's frame isn't NULL, map t's id to its frame
2550+
/* for t in all of the current interpreter's thread states:
2551+
* if t's frame isn't NULL, map t's id to its frame
25532552
* Because these lists can mutate even when the GIL is held, we
25542553
* need to grab head_mutex for the duration.
25552554
*/
2556-
_PyEval_StopTheWorldAll(runtime);
2555+
PyInterpreterState *interp = tstate->interp;
2556+
_PyEval_StopTheWorld(interp);
25572557
HEAD_LOCK(runtime);
2558-
PyInterpreterState *i;
2559-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2560-
PyThreadState *t;
2561-
for (t = i->threads.head; t != NULL; t = t->next) {
2562-
_PyInterpreterFrame *frame = t->current_frame;
2563-
frame = _PyFrame_GetFirstComplete(frame);
2564-
if (frame == NULL) {
2565-
continue;
2566-
}
2567-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2568-
if (id == NULL) {
2569-
goto fail;
2570-
}
2571-
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2572-
if (frameobj == NULL) {
2573-
Py_DECREF(id);
2574-
goto fail;
2575-
}
2576-
int stat = PyDict_SetItem(result, id, frameobj);
2558+
PyThreadState *t;
2559+
for (t = interp->threads.head; t != NULL; t = t->next) {
2560+
_PyInterpreterFrame *frame = t->current_frame;
2561+
frame = _PyFrame_GetFirstComplete(frame);
2562+
if (frame == NULL) {
2563+
continue;
2564+
}
2565+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2566+
if (id == NULL) {
2567+
goto fail;
2568+
}
2569+
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
2570+
if (frameobj == NULL) {
25772571
Py_DECREF(id);
2578-
if (stat < 0) {
2579-
goto fail;
2580-
}
2572+
goto fail;
2573+
}
2574+
int stat = PyDict_SetItem(result, id, frameobj);
2575+
Py_DECREF(id);
2576+
if (stat < 0) {
2577+
goto fail;
25812578
}
25822579
}
25832580
goto done;
@@ -2587,7 +2584,7 @@ _PyThread_CurrentFrames(void)
25872584

25882585
done:
25892586
HEAD_UNLOCK(runtime);
2590-
_PyEval_StartTheWorldAll(runtime);
2587+
_PyEval_StartTheWorld(interp);
25912588
return result;
25922589
}
25932590

@@ -2613,36 +2610,33 @@ _PyThread_CurrentExceptions(void)
26132610
return NULL;
26142611
}
26152612

2616-
/* for i in all interpreters:
2617-
* for t in all of i's thread states:
2618-
* if t's frame isn't NULL, map t's id to its frame
2613+
/* for t in all of the current interpreter's thread states:
2614+
* if t's frame isn't NULL, map t's id to its exception
26192615
* Because these lists can mutate even when the GIL is held, we
26202616
* need to grab head_mutex for the duration.
26212617
*/
2622-
_PyEval_StopTheWorldAll(runtime);
2618+
PyInterpreterState *interp = tstate->interp;
2619+
_PyEval_StopTheWorld(interp);
26232620
HEAD_LOCK(runtime);
2624-
PyInterpreterState *i;
2625-
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
2626-
PyThreadState *t;
2627-
for (t = i->threads.head; t != NULL; t = t->next) {
2628-
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2629-
if (err_info == NULL) {
2630-
continue;
2631-
}
2632-
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2633-
if (id == NULL) {
2634-
goto fail;
2635-
}
2636-
PyObject *exc = err_info->exc_value;
2637-
assert(exc == NULL ||
2638-
exc == Py_None ||
2639-
PyExceptionInstance_Check(exc));
2640-
2641-
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2642-
Py_DECREF(id);
2643-
if (stat < 0) {
2644-
goto fail;
2645-
}
2621+
PyThreadState *t;
2622+
for (t = interp->threads.head; t != NULL; t = t->next) {
2623+
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
2624+
if (err_info == NULL) {
2625+
continue;
2626+
}
2627+
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
2628+
if (id == NULL) {
2629+
goto fail;
2630+
}
2631+
PyObject *exc = err_info->exc_value;
2632+
assert(exc == NULL ||
2633+
exc == Py_None ||
2634+
PyExceptionInstance_Check(exc));
2635+
2636+
int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
2637+
Py_DECREF(id);
2638+
if (stat < 0) {
2639+
goto fail;
26462640
}
26472641
}
26482642
goto done;
@@ -2652,7 +2646,7 @@ _PyThread_CurrentExceptions(void)
26522646

26532647
done:
26542648
HEAD_UNLOCK(runtime);
2655-
_PyEval_StartTheWorldAll(runtime);
2649+
_PyEval_StartTheWorld(interp);
26562650
return result;
26572651
}
26582652

0 commit comments

Comments
 (0)