Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,38 @@ def g456():
leave_g.set()
t.join()

@support.cpython_only
@requires_subinterpreters
@threading_helper.requires_working_threading()
def test_current_frames_other_interpreters(self):
# gh-158364: sys._current_frames() would access frames of another
# interpreter and crash
import threading

entered = threading.Event()
left = threading.Event()

def park():
entered.set()
left.wait()

t = threading.Thread(target=park)
with threading_helper.start_threads([t], unlock=left.set):
entered.wait()
interp = interpreters.create()
try:
interp.exec(f"""if True:
import sys
import threading

frames = sys._current_frames()
assert threading.get_ident() in frames, frames
assert frames[threading.get_ident()].f_globals is globals()
assert {t.ident} not in frames, frames
""")
finally:
interp.close()

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_current_exceptions(self):
Expand Down Expand Up @@ -629,6 +661,39 @@ def g456():
leave_g.set()
t.join()

@support.cpython_only
@requires_subinterpreters
@threading_helper.requires_working_threading()
def test_current_exceptions_other_interpreters(self):
# gh-158364: sys._current_exceptions() would hand out exceptions of
# another interpreter and crash
import threading
Comment thread
ZeroIntensity marked this conversation as resolved.

entered = threading.Event()
left = threading.Event()

def hold():
# The thread has to be handling an exception, otherwise
# sys._current_exceptions() has nothing to report for it.
try:
raise ValueError
except ValueError:
Comment thread
ZeroIntensity marked this conversation as resolved.
entered.set()
left.wait()

t = threading.Thread(target=hold)
with threading_helper.start_threads([t], unlock=left.set):
entered.wait()
interp = interpreters.create()
try:
interp.exec(f"""if True:
import sys

assert {t.ident} not in sys._current_exceptions()
""")
finally:
interp.close()

def test_attributes(self):
self.assertIsInstance(sys.api_version, int)
self.assertIsInstance(sys.argv, list)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix crash when :func:`sys._current_frames` or
:func:`sys._current_exceptions` is called while another interpreter is
running.
98 changes: 46 additions & 52 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -2801,36 +2801,33 @@ _PyThread_CurrentFrames(void)
return NULL;
}

/* for i in all interpreters:
* for t in all of i's thread states:
* if t's frame isn't NULL, map t's id to its frame
/* for t in all of the current interpreter's thread states:
* if t's frame isn't NULL, map t's id to its frame
* Because these lists can mutate even when the GIL is held, we
* need to grab head_mutex for the duration.
*/
_PyEval_StopTheWorldAll(runtime);
PyInterpreterState *interp = tstate->interp;
_PyEval_StopTheWorld(interp);
HEAD_LOCK(runtime);
PyInterpreterState *i;
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
_PyInterpreterFrame *frame = t->current_frame;
frame = _PyFrame_GetFirstComplete(frame);
if (frame == NULL) {
continue;
}
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
if (id == NULL) {
goto fail;
}
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
if (frameobj == NULL) {
Py_DECREF(id);
goto fail;
}
int stat = PyDict_SetItem(result, id, frameobj);
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
_PyInterpreterFrame *frame = t->current_frame;
frame = _PyFrame_GetFirstComplete(frame);
if (frame == NULL) {
continue;
}
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
if (id == NULL) {
goto fail;
}
PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
if (frameobj == NULL) {
Py_DECREF(id);
if (stat < 0) {
goto fail;
}
goto fail;
}
int stat = PyDict_SetItem(result, id, frameobj);
Py_DECREF(id);
if (stat < 0) {
goto fail;
}
}
goto done;
Expand All @@ -2840,7 +2837,7 @@ _PyThread_CurrentFrames(void)

done:
HEAD_UNLOCK(runtime);
_PyEval_StartTheWorldAll(runtime);
_PyEval_StartTheWorld(interp);
return result;
}

Expand All @@ -2866,35 +2863,32 @@ _PyThread_CurrentExceptions(void)
return NULL;
}

/* for i in all interpreters:
* for t in all of i's thread states:
* if t's frame isn't NULL, map t's id to its frame
/* for t in all of the current interpreter's thread states:
* if t's frame isn't NULL, map t's id to its exception
* Because these lists can mutate even when the GIL is held, we
* need to grab head_mutex for the duration.
*/
_PyEval_StopTheWorldAll(runtime);
PyInterpreterState *interp = tstate->interp;
_PyEval_StopTheWorld(interp);
HEAD_LOCK(runtime);
PyInterpreterState *i;
for (i = runtime->interpreters.head; i != NULL; i = i->next) {
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
if (err_info == NULL) {
continue;
}
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
if (id == NULL) {
goto fail;
}
PyObject *exc = err_info->exc_value;
assert(exc == NULL ||
exc == Py_None ||
PyExceptionInstance_Check(exc));

int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
Py_DECREF(id);
if (stat < 0) {
goto fail;
}
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
_PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
if (err_info == NULL) {
continue;
}
PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
if (id == NULL) {
goto fail;
}
PyObject *exc = err_info->exc_value;
assert(exc == NULL ||
exc == Py_None ||
PyExceptionInstance_Check(exc));

int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
Py_DECREF(id);
if (stat < 0) {
goto fail;
}
}
goto done;
Expand All @@ -2904,7 +2898,7 @@ _PyThread_CurrentExceptions(void)

done:
HEAD_UNLOCK(runtime);
_PyEval_StartTheWorldAll(runtime);
_PyEval_StartTheWorld(interp);
return result;
}

Expand Down
Loading