From 14d28be344702b98f2694e245ed4a00c86f898c2 Mon Sep 17 00:00:00 2001
From: Paul Sokolovsky <pfalcon@users.sourceforge.net>
Date: Mon, 27 Jan 2014 01:01:37 +0200
Subject: [PATCH] gen.send(): Throw StopIteration. Also, explicitly shutdown
 finished gen.

Otherwise, some generator statements still may be spuriously executed on
subsequent calls to next()/send().
---
 py/objgenerator.c              | 25 +++++++++++++++++++++----
 tests/basics/generator_send.py | 22 ++++++++++++++++++++++
 2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/py/objgenerator.c b/py/objgenerator.c
index 0cac34b09..91bbbceb2 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -73,8 +73,11 @@ mp_obj_t gen_instance_getiter(mp_obj_t self_in) {
     return self_in;
 }
 
-static mp_obj_t gen_send(mp_obj_t self_in, mp_obj_t send_value) {
+static mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) {
     mp_obj_gen_instance_t *self = self_in;
+    if (self->ip == 0) {
+        return mp_const_stop_iteration;
+    }
     if (self->sp == self->state - 1) {
         if (send_value != mp_const_none) {
             nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "can't send non-None value to a just-started generator"));
@@ -86,6 +89,12 @@ static mp_obj_t gen_send(mp_obj_t self_in, mp_obj_t send_value) {
     if (yield) {
         return *self->sp;
     } else {
+        // Explicitly mark generator as completed. If we don't do this,
+        // subsequent next() may re-execute statements after last yield
+        // again and again, leading to side effects.
+        // TODO: check how return with value behaves under such conditions
+        // in CPython.
+        self->ip = 0;
         if (*self->sp == mp_const_none) {
             return mp_const_stop_iteration;
         } else {
@@ -94,14 +103,22 @@ static mp_obj_t gen_send(mp_obj_t self_in, mp_obj_t send_value) {
         }
     }
 }
-static MP_DEFINE_CONST_FUN_OBJ_2(gen_send_obj, gen_send);
 
 mp_obj_t gen_instance_iternext(mp_obj_t self_in) {
-    return gen_send(self_in, mp_const_none);
+    return gen_next_send(self_in, mp_const_none);
+}
+
+static mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
+    mp_obj_t ret = gen_next_send(self_in, send_value);
+    if (ret == mp_const_stop_iteration) {
+        nlr_jump(mp_obj_new_exception(MP_QSTR_StopIteration));
+    }
+    return ret;
 }
+static MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
 
 static const mp_method_t gen_type_methods[] = {
-    { "send", &gen_send_obj },
+    { "send", &gen_instance_send_obj },
     { NULL, NULL }, // end-of-list sentinel
 };
 
diff --git a/tests/basics/generator_send.py b/tests/basics/generator_send.py
index 4158478ca..c472c31ba 100644
--- a/tests/basics/generator_send.py
+++ b/tests/basics/generator_send.py
@@ -13,3 +13,25 @@ except TypeError:
 print(g.send(None))
 print(g.send(100))
 print(g.send(200))
+
+
+def f2():
+    print("entering")
+    for i in range(3):
+        print(i)
+        yield
+    print("returning 1")
+    print("returning 2")
+
+g = f2()
+g.send(None)
+g.send(1)
+g.send(1)
+try:
+    g.send(1)
+except StopIteration:
+    print("caught")
+try:
+    g.send(1)
+except StopIteration:
+    print("caught")
-- 
GitLab