summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorT. Wouters <thomas@python.org>2017-03-30 19:48:55 (GMT)
committerGitHub <noreply@github.com>2017-03-30 19:48:55 (GMT)
commit9273dfe1800fc7241d69f4d523d748ebd35b3801 (patch)
treef92bcc47277da8b1e41dc8b539cfc20cc9458cbc
parent8b8bde44f3912d8b2df5591ffc31d2d8c6dcca6c (diff)
downloadcpython-9273dfe1800fc7241d69f4d523d748ebd35b3801.zip
cpython-9273dfe1800fc7241d69f4d523d748ebd35b3801.tar.gz
cpython-9273dfe1800fc7241d69f4d523d748ebd35b3801.tar.bz2
bpo-29942: Fix the use of recursion in itertools.chain.from_iterable. (#912)
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)
-rw-r--r--Lib/test/test_itertools.py8
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/itertoolsmodule.c52
3 files changed, 39 insertions, 24 deletions
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index e054303..b8399fc 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -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...
diff --git a/Misc/NEWS b/Misc/NEWS
index e640622..0aefc75 100644
--- 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.
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index be0f498..584dc6e 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -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 *