]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-29942: Fix the use of recursion in itertools.chain.from_iterable. (#912)
authorT. Wouters <thomas@python.org>
Thu, 30 Mar 2017 19:48:55 +0000 (12:48 -0700)
committerGitHub <noreply@github.com>
Thu, 30 Mar 2017 19:48:55 +0000 (12:48 -0700)
Fix the use of recursion in itertools.chain.from_iterable. Using recursion
is unnecessary, and can easily cause stack overflows, especially when
building in low optimization modes or with Py_DEBUG enabled.
(cherry picked from commit 5466d4af5fe76ec0a5fbc8a05675287d9e8e9d14)

Lib/test/test_itertools.py
Misc/NEWS
Modules/itertoolsmodule.c

index e054303dedbe389aec214314e701dfe92e68e5e0..b8399fc1220cb63be0fc35d041cf45232923794d 100644 (file)
@@ -1943,6 +1943,14 @@ class RegressionTests(unittest.TestCase):
         self.assertRaises(AssertionError, list, cycle(gen1()))
         self.assertEqual(hist, [0,1])
 
+    def test_long_chain_of_empty_iterables(self):
+        # Make sure itertools.chain doesn't run into recursion limits when
+        # dealing with long chains of empty iterables. Even with a high
+        # number this would probably only fail in Py_DEBUG mode.
+        it = chain.from_iterable(() for unused in range(10000000))
+        with self.assertRaises(StopIteration):
+            next(it)
+
 class SubclassWithKwargsTest(unittest.TestCase):
     def test_keywords_in_subclass(self):
         # count is not subclassable...
index e640622f0d86a1a86e6c2b8282d22d4165064dbd..0aefc7583bcb22225fb241d58966591e28c690d7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -49,6 +49,9 @@ Extension Modules
 Library
 -------
 
+- bpo-29942: Fix a crash in itertools.chain.from_iterable when encountering
+  long runs of empty iterables.
+
 - bpo-27863: Fixed multiple crashes in ElementTree caused by race conditions
   and wrong types.
 
index be0f4982526f62a836abc1d52c9b3d19897762dc..584dc6e79243414a39c30c2a40e2c8b58cd6ecf6 100644 (file)
@@ -1854,33 +1854,37 @@ chain_next(chainobject *lz)
 {
     PyObject *item;
 
-    if (lz->source == NULL)
-        return NULL;                                    /* already stopped */
-
-    if (lz->active == NULL) {
-        PyObject *iterable = PyIter_Next(lz->source);
-        if (iterable == NULL) {
-            Py_CLEAR(lz->source);
-            return NULL;                                /* no more input sources */
-        }
-        lz->active = PyObject_GetIter(iterable);
-        Py_DECREF(iterable);
+    /* lz->source is the iterator of iterables. If it's NULL, we've already
+     * consumed them all. lz->active is the current iterator. If it's NULL,
+     * we should grab a new one from lz->source. */
+    while (lz->source != NULL) {
         if (lz->active == NULL) {
-            Py_CLEAR(lz->source);
-            return NULL;                                /* input not iterable */
+            PyObject *iterable = PyIter_Next(lz->source);
+            if (iterable == NULL) {
+                Py_CLEAR(lz->source);
+                return NULL;            /* no more input sources */
+            }
+            lz->active = PyObject_GetIter(iterable);
+            Py_DECREF(iterable);
+            if (lz->active == NULL) {
+                Py_CLEAR(lz->source);
+                return NULL;            /* input not iterable */
+            }
         }
+        item = (*Py_TYPE(lz->active)->tp_iternext)(lz->active);
+        if (item != NULL)
+            return item;
+        if (PyErr_Occurred()) {
+            if (PyErr_ExceptionMatches(PyExc_StopIteration))
+                PyErr_Clear();
+            else
+                return NULL;            /* input raised an exception */
+        }
+        /* lz->active is consumed, try with the next iterable. */
+        Py_CLEAR(lz->active);
     }
-    item = PyIter_Next(lz->active);
-    if (item != NULL)
-        return item;
-    if (PyErr_Occurred()) {
-        if (PyErr_ExceptionMatches(PyExc_StopIteration))
-            PyErr_Clear();
-        else
-            return NULL;                                /* input raised an exception */
-    }
-    Py_CLEAR(lz->active);
-    return chain_next(lz);                      /* recurse and use next active */
+    /* Everything had been consumed already. */
+    return NULL;
 }
 
 static PyObject *