Issue #14432: Remove the thread state field from the frame structure. Fix a

crash when a generator is created in a C thread that is destroyed while the
generator is still used. The issue was that a generator contains a frame, and
the frame kept a reference to the Python state of the destroyed C thread. The
crash occurs when a trace function is setup.
This commit is contained in:
Victor Stinner 2013-12-13 02:01:38 +01:00
parent 62ca10051b
commit fdeb6ec45a
8 changed files with 179 additions and 45 deletions

View File

@ -39,7 +39,6 @@ typedef struct _frame {
/* Borrowed reference to a generator, or NULL */ /* Borrowed reference to a generator, or NULL */
PyObject *f_gen; PyObject *f_gen;
PyThreadState *f_tstate;
int f_lasti; /* Last instruction if called */ int f_lasti; /* Last instruction if called */
/* Call PyFrame_GetLineNumber() instead of reading this field /* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is directly. As of 2.3 f_lineno is only valid when tracing is

View File

@ -818,7 +818,7 @@ class SizeofTest(unittest.TestCase):
nfrees = len(x.f_code.co_freevars) nfrees = len(x.f_code.co_freevars)
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
ncells + nfrees - 1 ncells + nfrees - 1
check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) check(x, vsize('12P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
# function # function
def func(): pass def func(): pass
check(func, size('12P')) check(func, size('12P'))

View File

@ -662,6 +662,44 @@ class ThreadTests(BaseTestCase):
self.assertRegex(err.rstrip(), self.assertRegex(err.rstrip(),
b"^sys:1: ResourceWarning: unclosed file ") b"^sys:1: ResourceWarning: unclosed file ")
def test_frame_tstate_tracing(self):
# Issue #14432: Crash when a generator is created in a C thread that is
# destroyed while the generator is still used. The issue was that a
# generator contains a frame, and the frame kept a reference to the
# Python state of the destroyed C thread. The crash occurs when a trace
# function is setup.
def noop_trace(frame, event, arg):
# no operation
return noop_trace
def generator():
while 1:
yield "genereator"
def callback():
if callback.gen is None:
callback.gen = generator()
return next(callback.gen)
callback.gen = None
old_trace = sys.gettrace()
sys.settrace(noop_trace)
try:
# Install a trace function
threading.settrace(noop_trace)
# Create a generator in a C thread which exits after the call
_testcapi.call_in_temporary_c_thread(callback)
# Call the generator in a different Python thread, check that the
# generator didn't keep a reference to the destroyed thread state
for test in range(3):
# The trace function is still called here
callback()
finally:
sys.settrace(old_trace)
class ThreadJoinOnShutdown(BaseTestCase): class ThreadJoinOnShutdown(BaseTestCase):

View File

@ -10,6 +10,12 @@ Release date: 2014-01-05
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #14432: Remove the thread state field from the frame structure. Fix a
crash when a generator is created in a C thread that is destroyed while the
generator is still used. The issue was that a generator contains a frame, and
the frame kept a reference to the Python state of the destroyed C thread. The
crash occurs when a trace function is setup.
- Issue #19576: PyGILState_Ensure() now initializes threads. At startup, Python - Issue #19576: PyGILState_Ensure() now initializes threads. At startup, Python
has no concrete GIL. If PyGILState_Ensure() is called from a new thread for has no concrete GIL. If PyGILState_Ensure() is called from a new thread for
the first time and PyEval_InitThreads() was not called yet, a GIL needs to be the first time and PyEval_InitThreads() was not called yet, a GIL needs to be

View File

@ -2869,6 +2869,93 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines,
"This docstring has a valid signature and some extra newlines." "This docstring has a valid signature and some extra newlines."
); );
typedef struct {
PyThread_type_lock start_event;
PyThread_type_lock exit_event;
PyObject *callback;
} test_c_thread_t;
static void
temporary_c_thread(void *data)
{
test_c_thread_t *test_c_thread = data;
PyGILState_STATE state;
PyObject *res;
PyThread_release_lock(test_c_thread->start_event);
/* Allocate a Python thread state for this thread */
state = PyGILState_Ensure();
res = PyObject_CallFunction(test_c_thread->callback, "", NULL);
Py_CLEAR(test_c_thread->callback);
if (res == NULL) {
PyErr_Print();
}
else {
Py_DECREF(res);
}
/* Destroy the Python thread state for this thread */
PyGILState_Release(state);
PyThread_release_lock(test_c_thread->exit_event);
PyThread_exit_thread();
}
static PyObject *
call_in_temporary_c_thread(PyObject *self, PyObject *callback)
{
PyObject *res = NULL;
test_c_thread_t test_c_thread;
long thread;
PyEval_InitThreads();
test_c_thread.start_event = PyThread_allocate_lock();
test_c_thread.exit_event = PyThread_allocate_lock();
test_c_thread.callback = NULL;
if (!test_c_thread.start_event || !test_c_thread.exit_event) {
PyErr_SetString(PyExc_RuntimeError, "could not allocate lock");
goto exit;
}
Py_INCREF(callback);
test_c_thread.callback = callback;
PyThread_acquire_lock(test_c_thread.start_event, 1);
PyThread_acquire_lock(test_c_thread.exit_event, 1);
thread = PyThread_start_new_thread(temporary_c_thread, &test_c_thread);
if (thread == -1) {
PyErr_SetString(PyExc_RuntimeError, "unable to start the thread");
PyThread_release_lock(test_c_thread.start_event);
PyThread_release_lock(test_c_thread.exit_event);
goto exit;
}
PyThread_acquire_lock(test_c_thread.start_event, 1);
PyThread_release_lock(test_c_thread.start_event);
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(test_c_thread.exit_event, 1);
PyThread_release_lock(test_c_thread.exit_event);
Py_END_ALLOW_THREADS
Py_INCREF(Py_None);
res = Py_None;
exit:
Py_CLEAR(test_c_thread.callback);
if (test_c_thread.start_event)
PyThread_free_lock(test_c_thread.start_event);
if (test_c_thread.exit_event)
PyThread_free_lock(test_c_thread.exit_event);
return res;
}
static PyMethodDef TestMethods[] = { static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS}, {"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS}, {"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@ -2997,6 +3084,8 @@ static PyMethodDef TestMethods[] = {
{"docstring_with_signature_and_extra_newlines", {"docstring_with_signature_and_extra_newlines",
(PyCFunction)test_with_docstring, METH_NOARGS, (PyCFunction)test_with_docstring, METH_NOARGS,
docstring_with_signature_and_extra_newlines}, docstring_with_signature_and_extra_newlines},
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
PyDoc_STR("set_error_class(error_class) -> None")},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };

View File

@ -726,7 +726,6 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
Py_INCREF(locals); Py_INCREF(locals);
f->f_locals = locals; f->f_locals = locals;
} }
f->f_tstate = tstate;
f->f_lasti = -1; f->f_lasti = -1;
f->f_lineno = code->co_firstlineno; f->f_lineno = code->co_firstlineno;

View File

@ -123,13 +123,16 @@ static PyObject * load_args(PyObject ***, int);
static int lltrace; static int lltrace;
static int prtrace(PyObject *, char *); static int prtrace(PyObject *, char *);
#endif #endif
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *, static int call_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *,
int, PyObject *); int, PyObject *);
static int call_trace_protected(Py_tracefunc, PyObject *, static int call_trace_protected(Py_tracefunc, PyObject *,
PyFrameObject *, int, PyObject *); PyThreadState *, PyFrameObject *,
static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *); int, PyObject *);
static void call_exc_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *);
static int maybe_call_line_trace(Py_tracefunc, PyObject *, static int maybe_call_line_trace(Py_tracefunc, PyObject *,
PyFrameObject *, int *, int *, int *); PyThreadState *, PyFrameObject *, int *, int *, int *);
static PyObject * cmp_outcome(int, PyObject *, PyObject *); static PyObject * cmp_outcome(int, PyObject *, PyObject *);
static PyObject * import_from(PyObject *, PyObject *); static PyObject * import_from(PyObject *, PyObject *);
@ -1136,7 +1139,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
whenever an exception is detected. */ whenever an exception is detected. */
if (call_trace_protected(tstate->c_tracefunc, if (call_trace_protected(tstate->c_tracefunc,
tstate->c_traceobj, tstate->c_traceobj,
f, PyTrace_CALL, Py_None)) { tstate, f, PyTrace_CALL, Py_None)) {
/* Trace function raised an error */ /* Trace function raised an error */
goto exit_eval_frame; goto exit_eval_frame;
} }
@ -1146,7 +1149,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
return itself and isn't called for "line" events */ return itself and isn't called for "line" events */
if (call_trace_protected(tstate->c_profilefunc, if (call_trace_protected(tstate->c_profilefunc,
tstate->c_profileobj, tstate->c_profileobj,
f, PyTrace_CALL, Py_None)) { tstate, f, PyTrace_CALL, Py_None)) {
/* Profile function raised an error */ /* Profile function raised an error */
goto exit_eval_frame; goto exit_eval_frame;
} }
@ -1293,8 +1296,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
err = maybe_call_line_trace(tstate->c_tracefunc, err = maybe_call_line_trace(tstate->c_tracefunc,
tstate->c_traceobj, tstate->c_traceobj,
f, &instr_lb, &instr_ub, tstate, f,
&instr_prev); &instr_lb, &instr_ub, &instr_prev);
/* Reload possibly changed frame fields */ /* Reload possibly changed frame fields */
JUMPTO(f->f_lasti); JUMPTO(f->f_lasti);
if (f->f_stacktop != NULL) { if (f->f_stacktop != NULL) {
@ -1906,7 +1909,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
PyObject *val; PyObject *val;
if (tstate->c_tracefunc != NULL if (tstate->c_tracefunc != NULL
&& PyErr_ExceptionMatches(PyExc_StopIteration)) && PyErr_ExceptionMatches(PyExc_StopIteration))
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, f); call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
err = _PyGen_FetchStopIterationValue(&val); err = _PyGen_FetchStopIterationValue(&val);
if (err < 0) if (err < 0)
goto error; goto error;
@ -2658,7 +2661,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (!PyErr_ExceptionMatches(PyExc_StopIteration)) if (!PyErr_ExceptionMatches(PyExc_StopIteration))
goto error; goto error;
else if (tstate->c_tracefunc != NULL) else if (tstate->c_tracefunc != NULL)
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, f); call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
PyErr_Clear(); PyErr_Clear();
} }
/* iterator ended normally */ /* iterator ended normally */
@ -3054,7 +3057,8 @@ error:
PyTraceBack_Here(f); PyTraceBack_Here(f);
if (tstate->c_tracefunc != NULL) if (tstate->c_tracefunc != NULL)
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, f); call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj,
tstate, f);
fast_block_end: fast_block_end:
assert(why != WHY_NOT); assert(why != WHY_NOT);
@ -3182,8 +3186,8 @@ fast_yield:
if (tstate->use_tracing) { if (tstate->use_tracing) {
if (tstate->c_tracefunc) { if (tstate->c_tracefunc) {
if (why == WHY_RETURN || why == WHY_YIELD) { if (why == WHY_RETURN || why == WHY_YIELD) {
if (call_trace(tstate->c_tracefunc, if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
tstate->c_traceobj, f, tstate, f,
PyTrace_RETURN, retval)) { PyTrace_RETURN, retval)) {
Py_XDECREF(retval); Py_XDECREF(retval);
retval = NULL; retval = NULL;
@ -3191,18 +3195,19 @@ fast_yield:
} }
} }
else if (why == WHY_EXCEPTION) { else if (why == WHY_EXCEPTION) {
call_trace_protected(tstate->c_tracefunc, call_trace_protected(tstate->c_tracefunc, tstate->c_traceobj,
tstate->c_traceobj, f, tstate, f,
PyTrace_RETURN, NULL); PyTrace_RETURN, NULL);
} }
} }
if (tstate->c_profilefunc) { if (tstate->c_profilefunc) {
if (why == WHY_EXCEPTION) if (why == WHY_EXCEPTION)
call_trace_protected(tstate->c_profilefunc, call_trace_protected(tstate->c_profilefunc,
tstate->c_profileobj, f, tstate->c_profileobj,
tstate, f,
PyTrace_RETURN, NULL); PyTrace_RETURN, NULL);
else if (call_trace(tstate->c_profilefunc, else if (call_trace(tstate->c_profilefunc, tstate->c_profileobj,
tstate->c_profileobj, f, tstate, f,
PyTrace_RETURN, retval)) { PyTrace_RETURN, retval)) {
Py_XDECREF(retval); Py_XDECREF(retval);
retval = NULL; retval = NULL;
@ -3846,7 +3851,8 @@ prtrace(PyObject *v, char *str)
#endif #endif
static void static void
call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f) call_exc_trace(Py_tracefunc func, PyObject *self,
PyThreadState *tstate, PyFrameObject *f)
{ {
PyObject *type, *value, *traceback, *orig_traceback, *arg; PyObject *type, *value, *traceback, *orig_traceback, *arg;
int err; int err;
@ -3862,7 +3868,7 @@ call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f)
PyErr_Restore(type, value, orig_traceback); PyErr_Restore(type, value, orig_traceback);
return; return;
} }
err = call_trace(func, self, f, PyTrace_EXCEPTION, arg); err = call_trace(func, self, tstate, f, PyTrace_EXCEPTION, arg);
Py_DECREF(arg); Py_DECREF(arg);
if (err == 0) if (err == 0)
PyErr_Restore(type, value, orig_traceback); PyErr_Restore(type, value, orig_traceback);
@ -3874,13 +3880,14 @@ call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f)
} }
static int static int
call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame, call_trace_protected(Py_tracefunc func, PyObject *obj,
PyThreadState *tstate, PyFrameObject *frame,
int what, PyObject *arg) int what, PyObject *arg)
{ {
PyObject *type, *value, *traceback; PyObject *type, *value, *traceback;
int err; int err;
PyErr_Fetch(&type, &value, &traceback); PyErr_Fetch(&type, &value, &traceback);
err = call_trace(func, obj, frame, what, arg); err = call_trace(func, obj, tstate, frame, what, arg);
if (err == 0) if (err == 0)
{ {
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
@ -3895,10 +3902,10 @@ call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
} }
static int static int
call_trace(Py_tracefunc func, PyObject *obj, PyFrameObject *frame, call_trace(Py_tracefunc func, PyObject *obj,
PyThreadState *tstate, PyFrameObject *frame,
int what, PyObject *arg) int what, PyObject *arg)
{ {
PyThreadState *tstate = frame->f_tstate;
int result; int result;
if (tstate->tracing) if (tstate->tracing)
return 0; return 0;
@ -3914,8 +3921,7 @@ call_trace(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
PyObject * PyObject *
_PyEval_CallTracing(PyObject *func, PyObject *args) _PyEval_CallTracing(PyObject *func, PyObject *args)
{ {
PyFrameObject *frame = PyEval_GetFrame(); PyThreadState *tstate = PyThreadState_GET();
PyThreadState *tstate = frame->f_tstate;
int save_tracing = tstate->tracing; int save_tracing = tstate->tracing;
int save_use_tracing = tstate->use_tracing; int save_use_tracing = tstate->use_tracing;
PyObject *result; PyObject *result;
@ -3932,8 +3938,8 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
/* See Objects/lnotab_notes.txt for a description of how tracing works. */ /* See Objects/lnotab_notes.txt for a description of how tracing works. */
static int static int
maybe_call_line_trace(Py_tracefunc func, PyObject *obj, maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
PyFrameObject *frame, int *instr_lb, int *instr_ub, PyThreadState *tstate, PyFrameObject *frame,
int *instr_prev) int *instr_lb, int *instr_ub, int *instr_prev)
{ {
int result = 0; int result = 0;
int line = frame->f_lineno; int line = frame->f_lineno;
@ -3953,7 +3959,7 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
number and call the trace function. */ number and call the trace function. */
if (frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev) { if (frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev) {
frame->f_lineno = line; frame->f_lineno = line;
result = call_trace(func, obj, frame, PyTrace_LINE, Py_None); result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
} }
*instr_prev = frame->f_lasti; *instr_prev = frame->f_lasti;
return result; return result;
@ -4149,10 +4155,9 @@ err_args(PyObject *func, int flags, int nargs)
#define C_TRACE(x, call) \ #define C_TRACE(x, call) \
if (tstate->use_tracing && tstate->c_profilefunc) { \ if (tstate->use_tracing && tstate->c_profilefunc) { \
if (call_trace(tstate->c_profilefunc, \ if (call_trace(tstate->c_profilefunc, tstate->c_profileobj, \
tstate->c_profileobj, \ tstate, tstate->frame, \
tstate->frame, PyTrace_C_CALL, \ PyTrace_C_CALL, func)) { \
func)) { \
x = NULL; \ x = NULL; \
} \ } \
else { \ else { \
@ -4161,14 +4166,14 @@ if (tstate->use_tracing && tstate->c_profilefunc) { \
if (x == NULL) { \ if (x == NULL) { \
call_trace_protected(tstate->c_profilefunc, \ call_trace_protected(tstate->c_profilefunc, \
tstate->c_profileobj, \ tstate->c_profileobj, \
tstate->frame, PyTrace_C_EXCEPTION, \ tstate, tstate->frame, \
func); \ PyTrace_C_EXCEPTION, func); \
/* XXX should pass (type, value, tb) */ \ /* XXX should pass (type, value, tb) */ \
} else { \ } else { \
if (call_trace(tstate->c_profilefunc, \ if (call_trace(tstate->c_profilefunc, \
tstate->c_profileobj, \ tstate->c_profileobj, \
tstate->frame, PyTrace_C_RETURN, \ tstate, tstate->frame, \
func)) { \ PyTrace_C_RETURN, func)) { \
Py_DECREF(x); \ Py_DECREF(x); \
x = NULL; \ x = NULL; \
} \ } \

View File

@ -367,7 +367,7 @@ trace_init(void)
static PyObject * static PyObject *
call_trampoline(PyThreadState *tstate, PyObject* callback, call_trampoline(PyObject* callback,
PyFrameObject *frame, int what, PyObject *arg) PyFrameObject *frame, int what, PyObject *arg)
{ {
PyObject *args; PyObject *args;
@ -405,12 +405,11 @@ static int
profile_trampoline(PyObject *self, PyFrameObject *frame, profile_trampoline(PyObject *self, PyFrameObject *frame,
int what, PyObject *arg) int what, PyObject *arg)
{ {
PyThreadState *tstate = frame->f_tstate;
PyObject *result; PyObject *result;
if (arg == NULL) if (arg == NULL)
arg = Py_None; arg = Py_None;
result = call_trampoline(tstate, self, frame, what, arg); result = call_trampoline(self, frame, what, arg);
if (result == NULL) { if (result == NULL) {
PyEval_SetProfile(NULL, NULL); PyEval_SetProfile(NULL, NULL);
return -1; return -1;
@ -423,7 +422,6 @@ static int
trace_trampoline(PyObject *self, PyFrameObject *frame, trace_trampoline(PyObject *self, PyFrameObject *frame,
int what, PyObject *arg) int what, PyObject *arg)
{ {
PyThreadState *tstate = frame->f_tstate;
PyObject *callback; PyObject *callback;
PyObject *result; PyObject *result;
@ -433,7 +431,7 @@ trace_trampoline(PyObject *self, PyFrameObject *frame,
callback = frame->f_trace; callback = frame->f_trace;
if (callback == NULL) if (callback == NULL)
return 0; return 0;
result = call_trampoline(tstate, callback, frame, what, arg); result = call_trampoline(callback, frame, what, arg);
if (result == NULL) { if (result == NULL) {
PyEval_SetTrace(NULL, NULL); PyEval_SetTrace(NULL, NULL);
Py_XDECREF(frame->f_trace); Py_XDECREF(frame->f_trace);