Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 87ee678

Browse files
author
Anselm Kruis
committed
Merge branch master into master-slp.
The outcome of this merge does not compile. I'll fix it in the next commit.
2 parents f46b638 + ae3087c commit 87ee678

13 files changed

+188
-174
lines changed

Include/frameobject.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,6 @@ typedef struct _frame {
3838
char f_trace_lines; /* Emit per-line trace events? */
3939
char f_trace_opcodes; /* Emit per-opcode trace events? */
4040

41-
/* In a generator, we need to be able to swap between the exception
42-
state inside the generator and the exception state of the calling
43-
frame (which shouldn't be impacted when the generator "yields"
44-
from an except handler).
45-
These three fields exist exactly for that, and are unused for
46-
non-generator frames. See the save_exc_state and swap_exc_state
47-
functions in ceval.c for details of their use. */
48-
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
4941
/* Borrowed reference to a generator, or NULL */
5042
PyObject *f_gen;
5143

Include/genobject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ struct _frame; /* Avoid including frameobject.h */
2525
/* Name of the generator. */ \
2626
PyObject *prefix##_name; \
2727
/* Qualified name of the generator. */ \
28-
PyObject *prefix##_qualname;
28+
PyObject *prefix##_qualname; \
29+
_PyErr_StackItem prefix##_exc_state;
2930

3031
typedef struct {
3132
/* The gi_ prefix is intended to remind of generator-iterator. */

Include/pyerrors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ PyAPI_FUNC(void) PyErr_SetNone(PyObject *);
7878
PyAPI_FUNC(void) PyErr_SetObject(PyObject *, PyObject *);
7979
#ifndef Py_LIMITED_API
8080
PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *);
81+
_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate);
8182
#endif
8283
PyAPI_FUNC(void) PyErr_SetString(
8384
PyObject *exception,

Include/pystate.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *);
126126
#ifdef Py_LIMITED_API
127127
typedef struct _ts PyThreadState;
128128
#else
129+
130+
typedef struct _err_stackitem {
131+
/* This struct represents an entry on the exception stack, which is a
132+
* per-coroutine state. (Coroutine in the computer science sense,
133+
* including the thread and generators).
134+
* This ensures that the exception state is not impacted by "yields"
135+
* from an except handler.
136+
*/
137+
PyObject *exc_type, *exc_value, *exc_traceback;
138+
139+
struct _err_stackitem *previous_item;
140+
141+
} _PyErr_StackItem;
142+
143+
129144
typedef struct _ts {
130145
/* See Python/ceval.c for comments explaining most fields */
131146

@@ -150,13 +165,19 @@ typedef struct _ts {
150165
PyObject *c_profileobj;
151166
PyObject *c_traceobj;
152167

168+
/* The exception currently being raised */
153169
PyObject *curexc_type;
154170
PyObject *curexc_value;
155171
PyObject *curexc_traceback;
156172

157-
PyObject *exc_type;
158-
PyObject *exc_value;
159-
PyObject *exc_traceback;
173+
/* The exception currently being handled, if no coroutines/generators
174+
* are present. Always last element on the stack referred to be exc_info.
175+
*/
176+
_PyErr_StackItem exc_state;
177+
178+
/* Pointer to the top of the stack of the exceptions currently
179+
* being handled */
180+
_PyErr_StackItem *exc_info;
160181

161182
PyObject *dict; /* Stores per-thread state */
162183

Lib/test/test_exceptions.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,62 @@ def test_unhandled(self):
10971097
self.assertIn("test message", report)
10981098
self.assertTrue(report.endswith("\n"))
10991099

1100+
def test_yield_in_nested_try_excepts(self):
1101+
#Issue #25612
1102+
class MainError(Exception):
1103+
pass
1104+
1105+
class SubError(Exception):
1106+
pass
1107+
1108+
def main():
1109+
try:
1110+
raise MainError()
1111+
except MainError:
1112+
try:
1113+
yield
1114+
except SubError:
1115+
pass
1116+
raise
1117+
1118+
coro = main()
1119+
coro.send(None)
1120+
with self.assertRaises(MainError):
1121+
coro.throw(SubError())
1122+
1123+
def test_generator_doesnt_retain_old_exc2(self):
1124+
#Issue 28884#msg282532
1125+
def g():
1126+
try:
1127+
raise ValueError
1128+
except ValueError:
1129+
yield 1
1130+
self.assertEqual(sys.exc_info(), (None, None, None))
1131+
yield 2
1132+
1133+
gen = g()
1134+
1135+
try:
1136+
raise IndexError
1137+
except IndexError:
1138+
self.assertEqual(next(gen), 1)
1139+
self.assertEqual(next(gen), 2)
1140+
1141+
def test_raise_in_generator(self):
1142+
#Issue 25612#msg304117
1143+
def g():
1144+
yield 1
1145+
raise
1146+
yield 2
1147+
1148+
with self.assertRaises(ZeroDivisionError):
1149+
i = g()
1150+
try:
1151+
1/0
1152+
except:
1153+
next(i)
1154+
next(i)
1155+
11001156

11011157
class ImportErrorTests(unittest.TestCase):
11021158

Lib/test/test_sys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ class C(object): pass
973973
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
974974
ncells + nfrees - 1
975975
slextra = 'P' if test.support.stackless else ''
976-
check(x, vsize('8P2c4P3ic' + CO_MAXBLOCKS*'3i' + slextra + 'P' + extras*'P'))
976+
check(x, vsize('5P2c4P3ic' + CO_MAXBLOCKS*'3i' + slextra + 'P' + extras*'P'))
977977
# function
978978
def func(): pass
979979
check(func, size('12P'))
@@ -990,7 +990,7 @@ def bar(cls):
990990
check(bar, size('PP'))
991991
# generator
992992
def get_gen(): yield 1
993-
check(get_gen(), size('Pb2PPP'))
993+
check(get_gen(), size('Pb2PPP4P'))
994994
# iterator
995995
check(iter('abc'), size('lP'))
996996
# callable-iterator
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Move the current exception state from the frame object to the co-routine.
2+
This simplifies the interpreter and fixes a couple of obscure bugs caused by
3+
having swap exception state when entering or exiting a generator.

Objects/frameobject.c

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,7 @@ static PyGetSetDef frame_getsetlist[] = {
404404
405405
* ob_type, ob_size, f_code, f_valuestack;
406406
407-
* f_locals, f_trace,
408-
f_exc_type, f_exc_value, f_exc_traceback are NULL;
407+
* f_locals, f_trace are NULL;
409408
410409
* f_localsplus does not require re-allocation and
411410
the local variables in f_localsplus are NULL.
@@ -470,9 +469,6 @@ frame_dealloc(PyFrameObject *f)
470469
Py_DECREF(f->f_globals);
471470
Py_CLEAR(f->f_locals);
472471
Py_CLEAR(f->f_trace);
473-
Py_CLEAR(f->f_exc_type);
474-
Py_CLEAR(f->f_exc_value);
475-
Py_CLEAR(f->f_exc_traceback);
476472

477473
co = f->f_code;
478474
if (co->co_zombieframe == NULL)
@@ -501,9 +497,6 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
501497
Py_VISIT(f->f_globals);
502498
Py_VISIT(f->f_locals);
503499
Py_VISIT(f->f_trace);
504-
Py_VISIT(f->f_exc_type);
505-
Py_VISIT(f->f_exc_value);
506-
Py_VISIT(f->f_exc_traceback);
507500

508501
/* locals */
509502
slots = f->f_code->co_nlocals + PyTuple_GET_SIZE(f->f_code->co_cellvars) + PyTuple_GET_SIZE(f->f_code->co_freevars);
@@ -534,9 +527,6 @@ frame_tp_clear(PyFrameObject *f)
534527
f->f_stacktop = NULL;
535528
f->f_executing = 0;
536529

537-
Py_CLEAR(f->f_exc_type);
538-
Py_CLEAR(f->f_exc_value);
539-
Py_CLEAR(f->f_exc_traceback);
540530
Py_CLEAR(f->f_trace);
541531

542532
/* locals */
@@ -730,7 +720,6 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
730720
f->f_localsplus[i] = NULL;
731721
f->f_locals = NULL;
732722
f->f_trace = NULL;
733-
f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL;
734723
}
735724
f->f_stacktop = f->f_valuestack;
736725
f->f_builtins = builtins;

Objects/genobject.c

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@ static char *NON_INIT_CORO_MSG = "can't send non-None value to a "
1818
static char *ASYNC_GEN_IGNORED_EXIT_MSG =
1919
"async generator ignored GeneratorExit";
2020

21+
static inline int
22+
exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg)
23+
{
24+
Py_VISIT(exc_state->exc_type);
25+
Py_VISIT(exc_state->exc_value);
26+
Py_VISIT(exc_state->exc_traceback);
27+
return 0;
28+
}
29+
2130
static int
2231
gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
2332
{
2433
Py_VISIT((PyObject *)gen->gi_frame);
2534
Py_VISIT(gen->gi_code);
2635
Py_VISIT(gen->gi_name);
2736
Py_VISIT(gen->gi_qualname);
28-
return 0;
37+
return exc_state_traverse(&gen->gi_exc_state, visit, arg);
2938
}
3039

3140
void
@@ -89,6 +98,21 @@ _PyGen_Finalize(PyObject *self)
8998
PyErr_Restore(error_type, error_value, error_traceback);
9099
}
91100

101+
static inline void
102+
exc_state_clear(_PyErr_StackItem *exc_state)
103+
{
104+
PyObject *t, *v, *tb;
105+
t = exc_state->exc_type;
106+
v = exc_state->exc_value;
107+
tb = exc_state->exc_traceback;
108+
exc_state->exc_type = NULL;
109+
exc_state->exc_value = NULL;
110+
exc_state->exc_traceback = NULL;
111+
Py_XDECREF(t);
112+
Py_XDECREF(v);
113+
Py_XDECREF(tb);
114+
}
115+
92116
static void
93117
gen_dealloc(PyGenObject *gen)
94118
{
@@ -118,6 +142,7 @@ gen_dealloc(PyGenObject *gen)
118142
Py_CLEAR(gen->gi_code);
119143
Py_CLEAR(gen->gi_name);
120144
Py_CLEAR(gen->gi_qualname);
145+
exc_state_clear(&gen->gi_exc_state);
121146
PyObject_GC_Del(gen);
122147
}
123148

@@ -231,7 +256,11 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
231256
f->f_back = tstate->frame;
232257

233258
gen->gi_running = 1;
259+
gen->gi_exc_state.previous_item = tstate->exc_info;
260+
tstate->exc_info = &gen->gi_exc_state;
234261
result = PyEval_EvalFrameEx(f, exc);
262+
tstate->exc_info = gen->gi_exc_state.previous_item;
263+
gen->gi_exc_state.previous_item = NULL;
235264
gen->gi_running = 0;
236265

237266
/* Don't keep the reference to f_back any longer than necessary. It
@@ -388,16 +417,7 @@ gen_iternext_callback(PyFrameObject *f, int exc, PyObject *result)
388417
if (!result || f->f_stacktop == NULL) {
389418
/* generator can't be rerun, so release the frame */
390419
/* first clean reference cycle through stored exception traceback */
391-
PyObject *t, *v, *tb;
392-
t = f->f_exc_type;
393-
v = f->f_exc_value;
394-
tb = f->f_exc_traceback;
395-
f->f_exc_type = NULL;
396-
f->f_exc_value = NULL;
397-
f->f_exc_traceback = NULL;
398-
Py_XDECREF(t);
399-
Py_XDECREF(v);
400-
Py_XDECREF(tb);
420+
exc_state_clear(&gen->gi_exc_state);
401421
gen->gi_frame->f_gen = NULL;
402422
assert(f == gen->gi_frame);
403423
gen->gi_frame = NULL;
@@ -923,6 +943,10 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
923943
gen->gi_code = (PyObject *)(f->f_code);
924944
gen->gi_running = 0;
925945
gen->gi_weakreflist = NULL;
946+
gen->gi_exc_state.exc_type = NULL;
947+
gen->gi_exc_state.exc_value = NULL;
948+
gen->gi_exc_state.exc_traceback = NULL;
949+
gen->gi_exc_state.previous_item = NULL;
926950
if (name != NULL)
927951
gen->gi_name = name;
928952
else

0 commit comments

Comments
 (0)