Skip to content

Commit 99c7232

Browse files
authored
[3.10] bpo-46009: Do not exhaust generator when send() method raises (GH-29986). (GH-29988)
* [3.10] bpo-46009: Do not exhaust generator when send() method raises (GH-29986). (cherry picked from commit 69806b9) Co-authored-by: Mark Shannon <[email protected]> * Rename variable after cherry-pick. * Add NULL check.
1 parent cca3004 commit 99c7232

File tree

5 files changed

+30
-21
lines changed

5 files changed

+30
-21
lines changed

Doc/library/dis.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,9 +1252,8 @@ All of the following opcodes use their arguments.
12521252

12531253
.. opcode:: GEN_START (kind)
12541254

1255-
Pops TOS. If TOS was not ``None``, raises an exception. The ``kind``
1256-
operand corresponds to the type of generator or coroutine and determines
1257-
the error message. The legal kinds are 0 for generator, 1 for coroutine,
1255+
Pops TOS. The ``kind`` operand corresponds to the type of generator or
1256+
coroutine. The legal kinds are 0 for generator, 1 for coroutine,
12581257
and 2 for async generator.
12591258

12601259
.. versionadded:: 3.10

Lib/test/test_generators.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ def f():
161161
with self.assertRaises((TypeError, pickle.PicklingError)):
162162
pickle.dumps(g, proto)
163163

164+
def test_send_non_none_to_new_gen(self):
165+
def f():
166+
yield 1
167+
g = f()
168+
with self.assertRaises(TypeError):
169+
g.send(0)
170+
self.assertEqual(next(g), 1)
171+
164172

165173
class ExceptionTest(unittest.TestCase):
166174
# Tests for the issue #23353: check that the currently handled exception
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Restore behavior from 3.9 and earlier when sending non-None to newly started
2+
generator. In 3.9 this did not affect the state of the generator. In 3.10.0
3+
and 3.10.1 ``gen_func().send(0)`` is equivalent to
4+
``gen_func().throw(TypeError(...)`` which exhausts the generator. In 3.10.2
5+
onward, the behavior has been reverted to that of 3.9.

Objects/genobject.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
145145
PyObject *result;
146146

147147
*presult = NULL;
148+
if (f != NULL && f->f_lasti < 0 && arg && arg != Py_None) {
149+
const char *msg = "can't send non-None value to a "
150+
"just-started generator";
151+
if (PyCoro_CheckExact(gen)) {
152+
msg = NON_INIT_CORO_MSG;
153+
}
154+
else if (PyAsyncGen_CheckExact(gen)) {
155+
msg = "can't send non-None value to a "
156+
"just-started async generator";
157+
}
158+
PyErr_SetString(PyExc_TypeError, msg);
159+
return PYGEN_ERROR;
160+
}
148161
if (f != NULL && _PyFrame_IsExecuting(f)) {
149162
const char *msg = "generator already executing";
150163
if (PyCoro_CheckExact(gen)) {

Python/ceval.c

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2649,25 +2649,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
26492649

26502650
case TARGET(GEN_START): {
26512651
PyObject *none = POP();
2652+
assert(none == Py_None);
2653+
assert(oparg < 3);
26522654
Py_DECREF(none);
2653-
if (!Py_IsNone(none)) {
2654-
if (oparg > 2) {
2655-
_PyErr_SetString(tstate, PyExc_SystemError,
2656-
"Illegal kind for GEN_START");
2657-
}
2658-
else {
2659-
static const char *gen_kind[3] = {
2660-
"generator",
2661-
"coroutine",
2662-
"async generator"
2663-
};
2664-
_PyErr_Format(tstate, PyExc_TypeError,
2665-
"can't send non-None value to a "
2666-
"just-started %s",
2667-
gen_kind[oparg]);
2668-
}
2669-
goto error;
2670-
}
26712655
DISPATCH();
26722656
}
26732657

0 commit comments

Comments
 (0)