From adde4984035eeb4d039b7f58c47b3f442c27bc99 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 23 May 2017 14:39:21 -0700 Subject: [PATCH 01/10] Move exception state information from frame objects to coroutine (generator/thread) object where it belongs. --- Include/frameobject.h | 8 --- Include/genobject.h | 3 +- Include/pyerrors.h | 1 + Include/pystate.h | 20 ++++++- Lib/test/test_sys.py | 4 +- Objects/frameobject.c | 13 +---- Objects/genobject.c | 26 +++++++-- Python/ceval.c | 125 +++++++----------------------------------- Python/errors.c | 30 ++++++---- Python/pystate.c | 18 ++++-- Python/sysmodule.c | 11 ++-- 11 files changed, 100 insertions(+), 159 deletions(-) diff --git a/Include/frameobject.h b/Include/frameobject.h index dbe0a840df964a..a95baf8867a360 100644 --- a/Include/frameobject.h +++ b/Include/frameobject.h @@ -30,14 +30,6 @@ typedef struct _frame { char f_trace_lines; /* Emit per-line trace events? */ char f_trace_opcodes; /* Emit per-opcode trace events? */ - /* In a generator, we need to be able to swap between the exception - state inside the generator and the exception state of the calling - frame (which shouldn't be impacted when the generator "yields" - from an except handler). - These three fields exist exactly for that, and are unused for - non-generator frames. See the save_exc_state and swap_exc_state - functions in ceval.c for details of their use. */ - PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; /* Borrowed reference to a generator, or NULL */ PyObject *f_gen; diff --git a/Include/genobject.h b/Include/genobject.h index b9db9f9c1c4b83..ea491b71889174 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -25,7 +25,8 @@ struct _frame; /* Avoid including frameobject.h */ /* Name of the generator. */ \ PyObject *prefix##_name; \ /* Qualified name of the generator. */ \ - PyObject *prefix##_qualname; + PyObject *prefix##_qualname; \ + PyExcState prefix##_exc_state; typedef struct { /* The gi_ prefix is intended to remind of generator-iterator. */ diff --git a/Include/pyerrors.h b/Include/pyerrors.h index fcaba85488e535..3a8384a18cc0d8 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -78,6 +78,7 @@ PyAPI_FUNC(void) PyErr_SetNone(PyObject *); PyAPI_FUNC(void) PyErr_SetObject(PyObject *, PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *); +PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate); #endif PyAPI_FUNC(void) PyErr_SetString( PyObject *exception, diff --git a/Include/pystate.h b/Include/pystate.h index 507a598297e1f7..b1451f83842e23 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -123,6 +123,20 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *); #ifdef Py_LIMITED_API typedef struct _ts PyThreadState; #else + +typedef struct _excstate { + /* In a generator, we need to be able to swap between the exception + state inside the generator and the exception state of the calling + frame (which shouldn't be impacted when the generator "yields" + from an except handler). + These three fields exist exactly for that. */ + PyObject *exc_type, *exc_value, *exc_traceback; + + struct _excstate *exc_previous; + +} PyExcState; + + typedef struct _ts { /* See Python/ceval.c for comments explaining most fields */ @@ -151,9 +165,7 @@ typedef struct _ts { PyObject *curexc_value; PyObject *curexc_traceback; - PyObject *exc_type; - PyObject *exc_value; - PyObject *exc_traceback; + PyExcState exc_state; PyObject *dict; /* Stores per-thread state */ @@ -197,6 +209,8 @@ typedef struct _ts { PyObject *async_gen_firstiter; PyObject *async_gen_finalizer; + PyExcState *exc_info; + /* XXX signal handlers should also be here */ } PyThreadState; diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index fd45abee67e5a5..68726296128ae2 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -971,7 +971,7 @@ class C(object): pass nfrees = len(x.f_code.co_freevars) extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ ncells + nfrees - 1 - check(x, vsize('8P2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) + check(x, vsize('5P2c4P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass check(func, size('12P')) @@ -988,7 +988,7 @@ def bar(cls): check(bar, size('PP')) # generator def get_gen(): yield 1 - check(get_gen(), size('Pb2PPP')) + check(get_gen(), size('Pb2PPP4P')) # iterator check(iter('abc'), size('lP')) # callable-iterator diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 8739596bfc36ff..6ab3a22950ade6 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -379,8 +379,7 @@ static PyGetSetDef frame_getsetlist[] = { * ob_type, ob_size, f_code, f_valuestack; - * f_locals, f_trace, - f_exc_type, f_exc_value, f_exc_traceback are NULL; + * f_locals, f_trace are NULL; * f_localsplus does not require re-allocation and the local variables in f_localsplus are NULL. @@ -438,9 +437,6 @@ frame_dealloc(PyFrameObject *f) Py_DECREF(f->f_globals); Py_CLEAR(f->f_locals); Py_CLEAR(f->f_trace); - Py_CLEAR(f->f_exc_type); - Py_CLEAR(f->f_exc_value); - Py_CLEAR(f->f_exc_traceback); co = f->f_code; if (co->co_zombieframe == NULL) @@ -469,9 +465,6 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg) Py_VISIT(f->f_globals); Py_VISIT(f->f_locals); Py_VISIT(f->f_trace); - Py_VISIT(f->f_exc_type); - Py_VISIT(f->f_exc_value); - Py_VISIT(f->f_exc_traceback); /* locals */ slots = f->f_code->co_nlocals + PyTuple_GET_SIZE(f->f_code->co_cellvars) + PyTuple_GET_SIZE(f->f_code->co_freevars); @@ -502,9 +495,6 @@ frame_tp_clear(PyFrameObject *f) f->f_stacktop = NULL; f->f_executing = 0; - Py_CLEAR(f->f_exc_type); - Py_CLEAR(f->f_exc_value); - Py_CLEAR(f->f_exc_traceback); Py_CLEAR(f->f_trace); /* locals */ @@ -698,7 +688,6 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code, f->f_localsplus[i] = NULL; f->f_locals = NULL; f->f_trace = NULL; - f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL; } f->f_stacktop = f->f_valuestack; f->f_builtins = builtins; diff --git a/Objects/genobject.c b/Objects/genobject.c index 5d5798c2f48dfe..bcb4c03d0cedfd 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -23,6 +23,9 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg) Py_VISIT(gen->gi_code); Py_VISIT(gen->gi_name); Py_VISIT(gen->gi_qualname); + Py_VISIT(gen->gi_exc_state.exc_type); + Py_VISIT(gen->gi_exc_state.exc_value); + Py_VISIT(gen->gi_exc_state.exc_traceback); return 0; } @@ -116,6 +119,9 @@ gen_dealloc(PyGenObject *gen) Py_CLEAR(gen->gi_code); Py_CLEAR(gen->gi_name); Py_CLEAR(gen->gi_qualname); + Py_CLEAR(gen->gi_exc_state.exc_type); + Py_CLEAR(gen->gi_exc_state.exc_value); + Py_CLEAR(gen->gi_exc_state.exc_traceback); PyObject_GC_Del(gen); } @@ -187,7 +193,11 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) f->f_back = tstate->frame; gen->gi_running = 1; + gen->gi_exc_state.exc_previous = tstate->exc_info; + tstate->exc_info = &gen->gi_exc_state; result = PyEval_EvalFrameEx(f, exc); + tstate->exc_info = gen->gi_exc_state.exc_previous; + gen->gi_exc_state.exc_previous = NULL; gen->gi_running = 0; /* Don't keep the reference to f_back any longer than necessary. It @@ -282,12 +292,12 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) /* generator can't be rerun, so release the frame */ /* first clean reference cycle through stored exception traceback */ PyObject *t, *v, *tb; - t = f->f_exc_type; - v = f->f_exc_value; - tb = f->f_exc_traceback; - f->f_exc_type = NULL; - f->f_exc_value = NULL; - f->f_exc_traceback = NULL; + t = gen->gi_exc_state.exc_type; + v = gen->gi_exc_state.exc_value; + tb = gen->gi_exc_state.exc_traceback; + gen->gi_exc_state.exc_type = NULL; + gen->gi_exc_state.exc_value = NULL; + gen->gi_exc_state.exc_traceback = NULL; Py_XDECREF(t); Py_XDECREF(v); Py_XDECREF(tb); @@ -810,6 +820,10 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f, gen->gi_code = (PyObject *)(f->f_code); gen->gi_running = 0; gen->gi_weakreflist = NULL; + gen->gi_exc_state.exc_type = NULL; + gen->gi_exc_state.exc_value = NULL; + gen->gi_exc_state.exc_traceback = NULL; + gen->gi_exc_state.exc_previous = NULL; if (name != NULL) gen->gi_name = name; else diff --git a/Python/ceval.c b/Python/ceval.c index 0f7a40c45ce2c7..c74d685007744c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -511,9 +511,6 @@ enum why_code { WHY_SILENCED = 0x0080 /* Exception silenced by 'with' */ }; -static void save_exc_state(PyThreadState *, PyFrameObject *); -static void swap_exc_state(PyThreadState *, PyFrameObject *); -static void restore_and_clear_exc_state(PyThreadState *, PyFrameObject *); static int do_raise(PyObject *, PyObject *); static int unpack_iterable(PyObject *, int, int, PyObject **); @@ -813,17 +810,19 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) #define UNWIND_EXCEPT_HANDLER(b) \ do { \ PyObject *type, *value, *traceback; \ + PyExcState *exc_info; \ assert(STACK_LEVEL() >= (b)->b_level + 3); \ while (STACK_LEVEL() > (b)->b_level + 3) { \ value = POP(); \ Py_XDECREF(value); \ } \ - type = tstate->exc_type; \ - value = tstate->exc_value; \ - traceback = tstate->exc_traceback; \ - tstate->exc_type = POP(); \ - tstate->exc_value = POP(); \ - tstate->exc_traceback = POP(); \ + exc_info = tstate->exc_info; \ + type = exc_info->exc_type; \ + value = exc_info->exc_value; \ + traceback = exc_info->exc_traceback; \ + exc_info->exc_type = POP(); \ + exc_info->exc_value = POP(); \ + exc_info->exc_traceback = POP(); \ Py_XDECREF(type); \ Py_XDECREF(value); \ Py_XDECREF(traceback); \ @@ -910,16 +909,6 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ f->f_executing = 1; - if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) { - if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) { - /* We were in an except handler when we left, - restore the exception state which was put aside - (see YIELD_VALUE). */ - swap_exc_state(tstate, f); - } - else - save_exc_state(tstate, f); - } #ifdef LLTRACE lltrace = _PyDict_GetItemId(f->f_globals, &PyId___ltrace__) != NULL; @@ -3447,12 +3436,13 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) || b->b_type == SETUP_FINALLY)) { PyObject *exc, *val, *tb; int handler = b->b_handler; + PyExcState *exc_info = tstate->exc_info; /* Beware, this invalidates all b->b_* fields */ PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL()); - PUSH(tstate->exc_traceback); - PUSH(tstate->exc_value); - if (tstate->exc_type != NULL) { - PUSH(tstate->exc_type); + PUSH(exc_info->exc_traceback); + PUSH(exc_info->exc_value); + if (exc_info->exc_type != NULL) { + PUSH(exc_info->exc_type); } else { Py_INCREF(Py_None); @@ -3470,10 +3460,10 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) else PyException_SetTraceback(val, Py_None); Py_INCREF(exc); - tstate->exc_type = exc; + exc_info->exc_type = exc; Py_INCREF(val); - tstate->exc_value = val; - tstate->exc_traceback = tb; + exc_info->exc_value = val; + exc_info->exc_traceback = tb; if (tb == NULL) tb = Py_None; Py_INCREF(tb); @@ -3516,28 +3506,6 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) assert((retval != NULL) ^ (PyErr_Occurred() != NULL)); fast_yield: - if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) { - - /* The purpose of this block is to put aside the generator's exception - state and restore that of the calling frame. If the current - exception state is from the caller, we clear the exception values - on the generator frame, so they are not swapped back in latter. The - origin of the current exception state is determined by checking for - except handler blocks, which we must be in iff a new exception - state came into existence in this frame. (An uncaught exception - would have why == WHY_EXCEPTION, and we wouldn't be here). */ - int i; - for (i = 0; i < f->f_iblock; i++) { - if (f->f_blockstack[i].b_type == EXCEPT_HANDLER) { - break; - } - } - if (i == f->f_iblock) - /* We did not create this exception. */ - restore_and_clear_exc_state(tstate, f); - else - swap_exc_state(tstate, f); - } if (tstate->use_tracing) { if (tstate->c_tracefunc) { @@ -4057,60 +4025,6 @@ special_lookup(PyObject *o, _Py_Identifier *id) } -/* These 3 functions deal with the exception state of generators. */ - -static void -save_exc_state(PyThreadState *tstate, PyFrameObject *f) -{ - PyObject *type, *value, *traceback; - Py_XINCREF(tstate->exc_type); - Py_XINCREF(tstate->exc_value); - Py_XINCREF(tstate->exc_traceback); - type = f->f_exc_type; - value = f->f_exc_value; - traceback = f->f_exc_traceback; - f->f_exc_type = tstate->exc_type; - f->f_exc_value = tstate->exc_value; - f->f_exc_traceback = tstate->exc_traceback; - Py_XDECREF(type); - Py_XDECREF(value); - Py_XDECREF(traceback); -} - -static void -swap_exc_state(PyThreadState *tstate, PyFrameObject *f) -{ - PyObject *tmp; - tmp = tstate->exc_type; - tstate->exc_type = f->f_exc_type; - f->f_exc_type = tmp; - tmp = tstate->exc_value; - tstate->exc_value = f->f_exc_value; - f->f_exc_value = tmp; - tmp = tstate->exc_traceback; - tstate->exc_traceback = f->f_exc_traceback; - f->f_exc_traceback = tmp; -} - -static void -restore_and_clear_exc_state(PyThreadState *tstate, PyFrameObject *f) -{ - PyObject *type, *value, *tb; - type = tstate->exc_type; - value = tstate->exc_value; - tb = tstate->exc_traceback; - tstate->exc_type = f->f_exc_type; - tstate->exc_value = f->f_exc_value; - tstate->exc_traceback = f->f_exc_traceback; - f->f_exc_type = NULL; - f->f_exc_value = NULL; - f->f_exc_traceback = NULL; - Py_XDECREF(type); - Py_XDECREF(value); - Py_XDECREF(tb); -} - - /* Logic for the raise statement (too complicated for inlining). This *consumes* a reference count to each of its arguments. */ static int @@ -4121,10 +4035,11 @@ do_raise(PyObject *exc, PyObject *cause) if (exc == NULL) { /* Reraise */ PyThreadState *tstate = PyThreadState_GET(); + PyExcState *exc_info = tstate->exc_info; PyObject *tb; - type = tstate->exc_type; - value = tstate->exc_value; - tb = tstate->exc_traceback; + type = exc_info->exc_type; + value = exc_info->exc_value; + tb = exc_info->exc_traceback; if (type == Py_None || type == NULL) { PyErr_SetString(PyExc_RuntimeError, "No active exception to reraise"); diff --git a/Python/errors.c b/Python/errors.c index 5709fddb584115..714772260a207a 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -53,6 +53,14 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) Py_XDECREF(oldtraceback); } +PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { + PyExcState *exc_info = tstate->exc_info; + while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && + exc_info->exc_previous != NULL && exc_info != &tstate->exc_state) + exc_info = exc_info->exc_previous; + return exc_info; +} + static PyObject* _PyErr_CreateException(PyObject *exception, PyObject *value) { @@ -83,7 +91,7 @@ PyErr_SetObject(PyObject *exception, PyObject *value) } Py_XINCREF(value); - exc_value = tstate->exc_value; + exc_value = _PyErr_GetExcInfo(tstate)->exc_value; if (exc_value != NULL && exc_value != Py_None) { /* Implicit exception chaining */ Py_INCREF(exc_value); @@ -335,9 +343,11 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { PyThreadState *tstate = PyThreadState_GET(); - *p_type = tstate->exc_type; - *p_value = tstate->exc_value; - *p_traceback = tstate->exc_traceback; + PyExcState *exc_info = _PyErr_GetExcInfo(tstate); + *p_type = exc_info->exc_type; + *p_value = exc_info->exc_value; + *p_traceback = exc_info->exc_traceback; + Py_XINCREF(*p_type); Py_XINCREF(*p_value); @@ -350,13 +360,13 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) PyObject *oldtype, *oldvalue, *oldtraceback; PyThreadState *tstate = PyThreadState_GET(); - oldtype = tstate->exc_type; - oldvalue = tstate->exc_value; - oldtraceback = tstate->exc_traceback; + oldtype = tstate->exc_info->exc_type; + oldvalue = tstate->exc_info->exc_value; + oldtraceback = tstate->exc_info->exc_traceback; - tstate->exc_type = p_type; - tstate->exc_value = p_value; - tstate->exc_traceback = p_traceback; + tstate->exc_info->exc_type = p_type; + tstate->exc_info->exc_value = p_value; + tstate->exc_info->exc_traceback = p_traceback; Py_XDECREF(oldtype); Py_XDECREF(oldvalue); diff --git a/Python/pystate.c b/Python/pystate.c index 3feae346d44cac..8d86e6a521d590 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -257,9 +257,10 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->curexc_value = NULL; tstate->curexc_traceback = NULL; - tstate->exc_type = NULL; - tstate->exc_value = NULL; - tstate->exc_traceback = NULL; + tstate->exc_state.exc_type = NULL; + tstate->exc_state.exc_value = NULL; + tstate->exc_state.exc_traceback = NULL; + tstate->exc_info = &tstate->exc_state; tstate->c_profilefunc = NULL; tstate->c_tracefunc = NULL; @@ -444,9 +445,14 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->curexc_value); Py_CLEAR(tstate->curexc_traceback); - Py_CLEAR(tstate->exc_type); - Py_CLEAR(tstate->exc_value); - Py_CLEAR(tstate->exc_traceback); + Py_CLEAR(tstate->exc_state.exc_type); + Py_CLEAR(tstate->exc_state.exc_value); + Py_CLEAR(tstate->exc_state.exc_traceback); + assert(tstate->exc_info.exc_previous == NULL); + if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) + fprintf(stderr, + "PyThreadState_Clear: warning: thread still has a generator\n"); + tstate->c_profilefunc = NULL; tstate->c_tracefunc = NULL; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e38a200c005052..9fe08aa077eee4 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -311,14 +311,13 @@ PyDoc_STRVAR(excepthook_doc, static PyObject * sys_exc_info(PyObject *self, PyObject *noargs) { - PyThreadState *tstate; - tstate = PyThreadState_GET(); + PyExcState *exc_info = _PyErr_GetExcInfo(PyThreadState_GET()); return Py_BuildValue( "(OOO)", - tstate->exc_type != NULL ? tstate->exc_type : Py_None, - tstate->exc_value != NULL ? tstate->exc_value : Py_None, - tstate->exc_traceback != NULL ? - tstate->exc_traceback : Py_None); + exc_info->exc_type != NULL ? exc_info->exc_type : Py_None, + exc_info->exc_value != NULL ? exc_info->exc_value : Py_None, + exc_info->exc_traceback != NULL ? + exc_info->exc_traceback : Py_None); } PyDoc_STRVAR(exc_info_doc, From 207ff6ce61614bb7cd15eb09b85a8689c223a8ca Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 23 May 2017 14:56:44 -0700 Subject: [PATCH 02/10] Add tests to verify fix of bpo-25612 and case B6 of bpo-28884 --- Lib/test/test_exceptions.py | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index a25f3bf03a54a2..03c648573de74d 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1097,6 +1097,46 @@ def test_unhandled(self): self.assertIn("test message", report) self.assertTrue(report.endswith("\n")) + def test_yield_in_nested_try_excepts(self): + #Issue #25612 + class MainError(Exception): + pass + + class SubError(Exception): + pass + + def main(): + try: + raise MainError() + except MainError: + try: + yield + except SubError: + pass + raise + + coro = main() + coro.send(None) + with self.assertRaises(MainError): + coro.throw(SubError()) + + def test_generator_doesnt_retain_old_exc2(self): + #Issue 28884#msg282532 + def g(): + try: + raise ValueError + except ValueError: + yield 1 + self.assertEqual(sys.exc_info(), (None, None, None)) + yield 2 + + gen = g() + + try: + raise IndexError + except IndexError: + self.assertEqual(next(gen), 1) + self.assertEqual(next(gen), 2) class ImportErrorTests(unittest.TestCase): From 52acc3c93aa1c1d9a45a974232ef7b5f82902e12 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 23 May 2017 17:20:22 -0700 Subject: [PATCH 03/10] Factor out clear/traverse for exc_state struct. --- Include/pystate.h | 8 ++++++-- Objects/genobject.c | 40 +++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Include/pystate.h b/Include/pystate.h index b1451f83842e23..faafcae76bccf6 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -161,12 +161,18 @@ typedef struct _ts { PyObject *c_profileobj; PyObject *c_traceobj; + /* The exception currently being raised */ PyObject *curexc_type; PyObject *curexc_value; PyObject *curexc_traceback; + /* The exception currently being handled, if no coroutines/generators are present. + Always last element on the stack referred to be exc_info. */ PyExcState exc_state; + /* Pointer to the top of the stack of the exceptions currently being handled */ + PyExcState *exc_info; + PyObject *dict; /* Stores per-thread state */ int gilstate_counter; @@ -209,8 +215,6 @@ typedef struct _ts { PyObject *async_gen_firstiter; PyObject *async_gen_finalizer; - PyExcState *exc_info; - /* XXX signal handlers should also be here */ } PyThreadState; diff --git a/Objects/genobject.c b/Objects/genobject.c index bcb4c03d0cedfd..f1e3037843faa5 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -16,6 +16,13 @@ static char *NON_INIT_CORO_MSG = "can't send non-None value to a " static char *ASYNC_GEN_IGNORED_EXIT_MSG = "async generator ignored GeneratorExit"; +static inline int PyExcState_traverse(PyExcState *exc_state, visitproc visit, void *arg) { + Py_VISIT(exc_state->exc_type); + Py_VISIT(exc_state->exc_value); + Py_VISIT(exc_state->exc_traceback); + return 0; +} + static int gen_traverse(PyGenObject *gen, visitproc visit, void *arg) { @@ -23,10 +30,7 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg) Py_VISIT(gen->gi_code); Py_VISIT(gen->gi_name); Py_VISIT(gen->gi_qualname); - Py_VISIT(gen->gi_exc_state.exc_type); - Py_VISIT(gen->gi_exc_state.exc_value); - Py_VISIT(gen->gi_exc_state.exc_traceback); - return 0; + return PyExcState_traverse(&gen->gi_exc_state, visit, arg); } void @@ -90,6 +94,19 @@ _PyGen_Finalize(PyObject *self) PyErr_Restore(error_type, error_value, error_traceback); } +static inline void PyExcState_clear(PyExcState *exc_state) { + PyObject *t, *v, *tb; + t = exc_state->exc_type; + v = exc_state->exc_value; + tb = exc_state->exc_traceback; + exc_state->exc_type = NULL; + exc_state->exc_value = NULL; + exc_state->exc_traceback = NULL; + Py_XDECREF(t); + Py_XDECREF(v); + Py_XDECREF(tb); +} + static void gen_dealloc(PyGenObject *gen) { @@ -119,9 +136,7 @@ gen_dealloc(PyGenObject *gen) Py_CLEAR(gen->gi_code); Py_CLEAR(gen->gi_name); Py_CLEAR(gen->gi_qualname); - Py_CLEAR(gen->gi_exc_state.exc_type); - Py_CLEAR(gen->gi_exc_state.exc_value); - Py_CLEAR(gen->gi_exc_state.exc_traceback); + PyExcState_clear(&gen->gi_exc_state); PyObject_GC_Del(gen); } @@ -291,16 +306,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) if (!result || f->f_stacktop == NULL) { /* generator can't be rerun, so release the frame */ /* first clean reference cycle through stored exception traceback */ - PyObject *t, *v, *tb; - t = gen->gi_exc_state.exc_type; - v = gen->gi_exc_state.exc_value; - tb = gen->gi_exc_state.exc_traceback; - gen->gi_exc_state.exc_type = NULL; - gen->gi_exc_state.exc_value = NULL; - gen->gi_exc_state.exc_traceback = NULL; - Py_XDECREF(t); - Py_XDECREF(v); - Py_XDECREF(tb); + PyExcState_clear(&gen->gi_exc_state); gen->gi_frame->f_gen = NULL; gen->gi_frame = NULL; Py_DECREF(f); From f2ff96e35bc3e1d93a83018e4c154cf0b4f4dc16 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 24 May 2017 14:13:14 -0700 Subject: [PATCH 04/10] Make sure that previous exc-state in thread is initialized properly. --- Python/errors.c | 2 +- Python/pystate.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/errors.c b/Python/errors.c index 714772260a207a..834fb1ad7d7259 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -56,7 +56,7 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { PyExcState *exc_info = tstate->exc_info; while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && - exc_info->exc_previous != NULL && exc_info != &tstate->exc_state) + exc_info->exc_previous != NULL) exc_info = exc_info->exc_previous; return exc_info; } diff --git a/Python/pystate.c b/Python/pystate.c index 8d86e6a521d590..50d5a1434de92d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -260,6 +260,7 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->exc_state.exc_type = NULL; tstate->exc_state.exc_value = NULL; tstate->exc_state.exc_traceback = NULL; + tstate->exc_state.exc_previous = NULL; tstate->exc_info = &tstate->exc_state; tstate->c_profilefunc = NULL; @@ -448,7 +449,7 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->exc_state.exc_type); Py_CLEAR(tstate->exc_state.exc_value); Py_CLEAR(tstate->exc_state.exc_traceback); - assert(tstate->exc_info.exc_previous == NULL); + assert(tstate->exc_info->exc_previous == NULL); if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) fprintf(stderr, "PyThreadState_Clear: warning: thread still has a generator\n"); From 963aea3d675fb4a2bc2844e47a51dd77cc47d918 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sat, 27 May 2017 16:46:38 -0700 Subject: [PATCH 05/10] Make code conform to PEP-7 --- Include/pystate.h | 8 +++++--- Objects/genobject.c | 9 +++++---- Python/errors.c | 3 ++- Python/pystate.c | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Include/pystate.h b/Include/pystate.h index faafcae76bccf6..6a26780323b55d 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -166,11 +166,13 @@ typedef struct _ts { PyObject *curexc_value; PyObject *curexc_traceback; - /* The exception currently being handled, if no coroutines/generators are present. - Always last element on the stack referred to be exc_info. */ + /* The exception currently being handled, if no coroutines/generators + * are present. Always last element on the stack referred to be exc_info. + */ PyExcState exc_state; - /* Pointer to the top of the stack of the exceptions currently being handled */ + /* Pointer to the top of the stack of the exceptions currently + * being handled */ PyExcState *exc_info; PyObject *dict; /* Stores per-thread state */ diff --git a/Objects/genobject.c b/Objects/genobject.c index f1e3037843faa5..09d4e3759e1338 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -16,7 +16,8 @@ static char *NON_INIT_CORO_MSG = "can't send non-None value to a " static char *ASYNC_GEN_IGNORED_EXIT_MSG = "async generator ignored GeneratorExit"; -static inline int PyExcState_traverse(PyExcState *exc_state, visitproc visit, void *arg) { +static inline int +PyExcState_traverse(PyExcState *exc_state, visitproc visit, void *arg) { Py_VISIT(exc_state->exc_type); Py_VISIT(exc_state->exc_value); Py_VISIT(exc_state->exc_traceback); @@ -94,7 +95,7 @@ _PyGen_Finalize(PyObject *self) PyErr_Restore(error_type, error_value, error_traceback); } -static inline void PyExcState_clear(PyExcState *exc_state) { +static inline void PyExcState_Clear(PyExcState *exc_state) { PyObject *t, *v, *tb; t = exc_state->exc_type; v = exc_state->exc_value; @@ -136,7 +137,7 @@ gen_dealloc(PyGenObject *gen) Py_CLEAR(gen->gi_code); Py_CLEAR(gen->gi_name); Py_CLEAR(gen->gi_qualname); - PyExcState_clear(&gen->gi_exc_state); + PyExcState_Clear(&gen->gi_exc_state); PyObject_GC_Del(gen); } @@ -306,7 +307,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) if (!result || f->f_stacktop == NULL) { /* generator can't be rerun, so release the frame */ /* first clean reference cycle through stored exception traceback */ - PyExcState_clear(&gen->gi_exc_state); + PyExcState_Clear(&gen->gi_exc_state); gen->gi_frame->f_gen = NULL; gen->gi_frame = NULL; Py_DECREF(f); diff --git a/Python/errors.c b/Python/errors.c index 834fb1ad7d7259..69497a038dd731 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -56,8 +56,9 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { PyExcState *exc_info = tstate->exc_info; while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && - exc_info->exc_previous != NULL) + exc_info->exc_previous != NULL) { exc_info = exc_info->exc_previous; + } return exc_info; } diff --git a/Python/pystate.c b/Python/pystate.c index 50d5a1434de92d..8b38acc478e1d4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -450,10 +450,10 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->exc_state.exc_value); Py_CLEAR(tstate->exc_state.exc_traceback); assert(tstate->exc_info->exc_previous == NULL); - if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) + if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) { fprintf(stderr, "PyThreadState_Clear: warning: thread still has a generator\n"); - + } tstate->c_profilefunc = NULL; tstate->c_tracefunc = NULL; From 22b1e82c930437dcd967f9654415c7d485eb2e67 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 22 Oct 2017 13:03:31 +0100 Subject: [PATCH 06/10] Change names and formatting to conform to PEP-7 and make explicit that exception 'state' forms a stack. --- Include/genobject.h | 2 +- Include/pyerrors.h | 2 +- Include/pystate.h | 21 +++++++++++---------- Objects/genobject.c | 12 +++++++----- Python/ceval.c | 6 +++--- Python/errors.c | 12 +++++++----- Python/pystate.c | 2 ++ Python/sysmodule.c | 10 +++++----- 8 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Include/genobject.h b/Include/genobject.h index ea491b71889174..a718820070d720 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -26,7 +26,7 @@ struct _frame; /* Avoid including frameobject.h */ PyObject *prefix##_name; \ /* Qualified name of the generator. */ \ PyObject *prefix##_qualname; \ - PyExcState prefix##_exc_state; + _PyErr_StackItem prefix##_exc_state; typedef struct { /* The gi_ prefix is intended to remind of generator-iterator. */ diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 3a8384a18cc0d8..94182012246347 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -78,7 +78,7 @@ PyAPI_FUNC(void) PyErr_SetNone(PyObject *); PyAPI_FUNC(void) PyErr_SetObject(PyObject *, PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *); -PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate); +_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate); #endif PyAPI_FUNC(void) PyErr_SetString( PyObject *exception, diff --git a/Include/pystate.h b/Include/pystate.h index 6a26780323b55d..72299de7bb7213 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -124,17 +124,18 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *); typedef struct _ts PyThreadState; #else -typedef struct _excstate { - /* In a generator, we need to be able to swap between the exception - state inside the generator and the exception state of the calling - frame (which shouldn't be impacted when the generator "yields" - from an except handler). - These three fields exist exactly for that. */ +typedef struct _exc_stackitem { + /* This struct represents an entry on the exception stack, which is a + * per-coroutine state. (Coroutine in the computer science sense, + * including the thread and generators). + * This ensures that the exception state is not impacted by "yields" + * from an except handler. + */ PyObject *exc_type, *exc_value, *exc_traceback; - struct _excstate *exc_previous; + struct _exc_stackitem *exc_previous; -} PyExcState; +} _PyErr_StackItem; typedef struct _ts { @@ -169,11 +170,11 @@ typedef struct _ts { /* The exception currently being handled, if no coroutines/generators * are present. Always last element on the stack referred to be exc_info. */ - PyExcState exc_state; + _PyErr_StackItem exc_state; /* Pointer to the top of the stack of the exceptions currently * being handled */ - PyExcState *exc_info; + _PyErr_StackItem *exc_info; PyObject *dict; /* Stores per-thread state */ diff --git a/Objects/genobject.c b/Objects/genobject.c index 09d4e3759e1338..249ef317616c52 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -17,7 +17,8 @@ static char *ASYNC_GEN_IGNORED_EXIT_MSG = "async generator ignored GeneratorExit"; static inline int -PyExcState_traverse(PyExcState *exc_state, visitproc visit, void *arg) { +exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg) +{ Py_VISIT(exc_state->exc_type); Py_VISIT(exc_state->exc_value); Py_VISIT(exc_state->exc_traceback); @@ -31,7 +32,7 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg) Py_VISIT(gen->gi_code); Py_VISIT(gen->gi_name); Py_VISIT(gen->gi_qualname); - return PyExcState_traverse(&gen->gi_exc_state, visit, arg); + return exc_state_traverse(&gen->gi_exc_state, visit, arg); } void @@ -95,7 +96,8 @@ _PyGen_Finalize(PyObject *self) PyErr_Restore(error_type, error_value, error_traceback); } -static inline void PyExcState_Clear(PyExcState *exc_state) { +static inline void exc_state_clear(_PyErr_StackItem *exc_state) +{ PyObject *t, *v, *tb; t = exc_state->exc_type; v = exc_state->exc_value; @@ -137,7 +139,7 @@ gen_dealloc(PyGenObject *gen) Py_CLEAR(gen->gi_code); Py_CLEAR(gen->gi_name); Py_CLEAR(gen->gi_qualname); - PyExcState_Clear(&gen->gi_exc_state); + exc_state_clear(&gen->gi_exc_state); PyObject_GC_Del(gen); } @@ -307,7 +309,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) if (!result || f->f_stacktop == NULL) { /* generator can't be rerun, so release the frame */ /* first clean reference cycle through stored exception traceback */ - PyExcState_Clear(&gen->gi_exc_state); + exc_state_clear(&gen->gi_exc_state); gen->gi_frame->f_gen = NULL; gen->gi_frame = NULL; Py_DECREF(f); diff --git a/Python/ceval.c b/Python/ceval.c index c74d685007744c..78d8fae27fa29d 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -810,7 +810,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) #define UNWIND_EXCEPT_HANDLER(b) \ do { \ PyObject *type, *value, *traceback; \ - PyExcState *exc_info; \ + _PyErr_StackItem *exc_info; \ assert(STACK_LEVEL() >= (b)->b_level + 3); \ while (STACK_LEVEL() > (b)->b_level + 3) { \ value = POP(); \ @@ -3436,7 +3436,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) || b->b_type == SETUP_FINALLY)) { PyObject *exc, *val, *tb; int handler = b->b_handler; - PyExcState *exc_info = tstate->exc_info; + _PyErr_StackItem *exc_info = tstate->exc_info; /* Beware, this invalidates all b->b_* fields */ PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL()); PUSH(exc_info->exc_traceback); @@ -4035,7 +4035,7 @@ do_raise(PyObject *exc, PyObject *cause) if (exc == NULL) { /* Reraise */ PyThreadState *tstate = PyThreadState_GET(); - PyExcState *exc_info = tstate->exc_info; + _PyErr_StackItem *exc_info = tstate->exc_info; PyObject *tb; type = exc_info->exc_type; value = exc_info->exc_value; diff --git a/Python/errors.c b/Python/errors.c index 69497a038dd731..5afbd83c318638 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -53,10 +53,12 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) Py_XDECREF(oldtraceback); } -PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { - PyExcState *exc_info = tstate->exc_info; +_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate) +{ + _PyErr_StackItem *exc_info = tstate->exc_info; while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && - exc_info->exc_previous != NULL) { + exc_info->exc_previous != NULL) + { exc_info = exc_info->exc_previous; } return exc_info; @@ -92,7 +94,7 @@ PyErr_SetObject(PyObject *exception, PyObject *value) } Py_XINCREF(value); - exc_value = _PyErr_GetExcInfo(tstate)->exc_value; + exc_value = _PyErr_GetTopmostException(tstate)->exc_value; if (exc_value != NULL && exc_value != Py_None) { /* Implicit exception chaining */ Py_INCREF(exc_value); @@ -344,7 +346,7 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { PyThreadState *tstate = PyThreadState_GET(); - PyExcState *exc_info = _PyErr_GetExcInfo(tstate); + _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); *p_type = exc_info->exc_type; *p_value = exc_info->exc_value; *p_traceback = exc_info->exc_traceback; diff --git a/Python/pystate.c b/Python/pystate.c index 8b38acc478e1d4..44ee0e5da01270 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -449,6 +449,8 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->exc_state.exc_type); Py_CLEAR(tstate->exc_state.exc_value); Py_CLEAR(tstate->exc_state.exc_traceback); + + /* The stack of exception states should contain just this thread. */ assert(tstate->exc_info->exc_previous == NULL); if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) { fprintf(stderr, diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 9fe08aa077eee4..6dc8e08be7d997 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -311,13 +311,13 @@ PyDoc_STRVAR(excepthook_doc, static PyObject * sys_exc_info(PyObject *self, PyObject *noargs) { - PyExcState *exc_info = _PyErr_GetExcInfo(PyThreadState_GET()); + _PyErr_StackItem *err_info = _PyErr_GetTopmostException(PyThreadState_GET()); return Py_BuildValue( "(OOO)", - exc_info->exc_type != NULL ? exc_info->exc_type : Py_None, - exc_info->exc_value != NULL ? exc_info->exc_value : Py_None, - exc_info->exc_traceback != NULL ? - exc_info->exc_traceback : Py_None); + err_info->exc_type != NULL ? err_info->exc_type : Py_None, + err_info->exc_value != NULL ? err_info->exc_value : Py_None, + err_info->exc_traceback != NULL ? + err_info->exc_traceback : Py_None); } PyDoc_STRVAR(exc_info_doc, From e5ffc67bfc292260dc5c410e78eed83792479cfe Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 22 Oct 2017 13:13:16 +0100 Subject: [PATCH 07/10] Add blurb entry for new exception state handling. --- .../NEWS.d/next/C API/2017-10-22-13-12-28.bpo-25612.1jnWKT.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2017-10-22-13-12-28.bpo-25612.1jnWKT.rst diff --git a/Misc/NEWS.d/next/C API/2017-10-22-13-12-28.bpo-25612.1jnWKT.rst b/Misc/NEWS.d/next/C API/2017-10-22-13-12-28.bpo-25612.1jnWKT.rst new file mode 100644 index 00000000000000..102c2e32551055 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2017-10-22-13-12-28.bpo-25612.1jnWKT.rst @@ -0,0 +1,3 @@ +Move the current exception state from the frame object to the co-routine. +This simplifies the interpreter and fixes a couple of obscure bugs caused by +having swap exception state when entering or exiting a generator. From d3b7efb293014fb7633e38f4d708fc890ecb0a19 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sun, 22 Oct 2017 15:09:40 +0200 Subject: [PATCH 08/10] Right-justify C macro --- Include/genobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/genobject.h b/Include/genobject.h index a718820070d720..87fbe17d4abc03 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -25,7 +25,7 @@ struct _frame; /* Avoid including frameobject.h */ /* Name of the generator. */ \ PyObject *prefix##_name; \ /* Qualified name of the generator. */ \ - PyObject *prefix##_qualname; \ + PyObject *prefix##_qualname; \ _PyErr_StackItem prefix##_exc_state; typedef struct { From 36ea52f35542e6716d6404dff602a92673b45742 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 22 Oct 2017 15:09:40 +0200 Subject: [PATCH 09/10] More renaming and PEP-7 compliance --- Include/pystate.h | 4 ++-- Objects/genobject.c | 11 ++++++----- Python/errors.c | 7 ++++--- Python/pystate.c | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Include/pystate.h b/Include/pystate.h index 72299de7bb7213..238008fce47b39 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -124,7 +124,7 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *); typedef struct _ts PyThreadState; #else -typedef struct _exc_stackitem { +typedef struct _err_stackitem { /* This struct represents an entry on the exception stack, which is a * per-coroutine state. (Coroutine in the computer science sense, * including the thread and generators). @@ -133,7 +133,7 @@ typedef struct _exc_stackitem { */ PyObject *exc_type, *exc_value, *exc_traceback; - struct _exc_stackitem *exc_previous; + struct _err_stackitem *previous_item; } _PyErr_StackItem; diff --git a/Objects/genobject.c b/Objects/genobject.c index 249ef317616c52..7793a54fb3d682 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -96,7 +96,8 @@ _PyGen_Finalize(PyObject *self) PyErr_Restore(error_type, error_value, error_traceback); } -static inline void exc_state_clear(_PyErr_StackItem *exc_state) +static inline void +exc_state_clear(_PyErr_StackItem *exc_state) { PyObject *t, *v, *tb; t = exc_state->exc_type; @@ -211,11 +212,11 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) f->f_back = tstate->frame; gen->gi_running = 1; - gen->gi_exc_state.exc_previous = tstate->exc_info; + gen->gi_exc_state.previous_item = tstate->exc_info; tstate->exc_info = &gen->gi_exc_state; result = PyEval_EvalFrameEx(f, exc); - tstate->exc_info = gen->gi_exc_state.exc_previous; - gen->gi_exc_state.exc_previous = NULL; + tstate->exc_info = gen->gi_exc_state.previous_item; + gen->gi_exc_state.previous_item = NULL; gen->gi_running = 0; /* Don't keep the reference to f_back any longer than necessary. It @@ -832,7 +833,7 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f, gen->gi_exc_state.exc_type = NULL; gen->gi_exc_state.exc_value = NULL; gen->gi_exc_state.exc_traceback = NULL; - gen->gi_exc_state.exc_previous = NULL; + gen->gi_exc_state.previous_item = NULL; if (name != NULL) gen->gi_name = name; else diff --git a/Python/errors.c b/Python/errors.c index 5afbd83c318638..51fc791f45b122 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -53,13 +53,14 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) Py_XDECREF(oldtraceback); } -_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate) +_PyErr_StackItem * +_PyErr_GetTopmostException(PyThreadState *tstate) { _PyErr_StackItem *exc_info = tstate->exc_info; while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && - exc_info->exc_previous != NULL) + exc_info->previous_item != NULL) { - exc_info = exc_info->exc_previous; + exc_info = exc_info->previous_item; } return exc_info; } diff --git a/Python/pystate.c b/Python/pystate.c index 44ee0e5da01270..d85d604de5e6b0 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -260,7 +260,7 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->exc_state.exc_type = NULL; tstate->exc_state.exc_value = NULL; tstate->exc_state.exc_traceback = NULL; - tstate->exc_state.exc_previous = NULL; + tstate->exc_state.previous_item = NULL; tstate->exc_info = &tstate->exc_state; tstate->c_profilefunc = NULL; @@ -451,7 +451,7 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->exc_state.exc_traceback); /* The stack of exception states should contain just this thread. */ - assert(tstate->exc_info->exc_previous == NULL); + assert(tstate->exc_info->previous_item == NULL); if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) { fprintf(stderr, "PyThreadState_Clear: warning: thread still has a generator\n"); From cba130efd6905f6db1c2d8b17223cfe2c60f1902 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 22 Oct 2017 21:19:35 +0100 Subject: [PATCH 10/10] Update implementation of 'raise' to scan exception stack. --- Lib/test/test_exceptions.py | 16 ++++++++++++++++ Python/ceval.c | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 03c648573de74d..ad4bc093841d49 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1138,6 +1138,22 @@ def g(): self.assertEqual(next(gen), 1) self.assertEqual(next(gen), 2) + def test_raise_in_generator(self): + #Issue 25612#msg304117 + def g(): + yield 1 + raise + yield 2 + + with self.assertRaises(ZeroDivisionError): + i = g() + try: + 1/0 + except: + next(i) + next(i) + + class ImportErrorTests(unittest.TestCase): def test_attributes(self): diff --git a/Python/ceval.c b/Python/ceval.c index 78d8fae27fa29d..f9a798c8565692 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4035,7 +4035,7 @@ do_raise(PyObject *exc, PyObject *cause) if (exc == NULL) { /* Reraise */ PyThreadState *tstate = PyThreadState_GET(); - _PyErr_StackItem *exc_info = tstate->exc_info; + _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); PyObject *tb; type = exc_info->exc_type; value = exc_info->exc_value;