Commit d694a062 authored by T. Wouters's avatar T. Wouters Committed by GitHub

bpo-29942: Fix the use of recursion in itertools.chain.from_iterable. (#913)

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 5466d4af)
parent 079f21f8
...@@ -1465,6 +1465,14 @@ class RegressionTests(unittest.TestCase): ...@@ -1465,6 +1465,14 @@ class RegressionTests(unittest.TestCase):
self.assertRaises(AssertionError, list, cycle(gen1())) self.assertRaises(AssertionError, list, cycle(gen1()))
self.assertEqual(hist, [0,1]) 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 xrange(10000000))
with self.assertRaises(StopIteration):
next(it)
class SubclassWithKwargsTest(unittest.TestCase): class SubclassWithKwargsTest(unittest.TestCase):
def test_keywords_in_subclass(self): def test_keywords_in_subclass(self):
# count is not subclassable... # count is not subclassable...
......
...@@ -42,6 +42,9 @@ Extension Modules ...@@ -42,6 +42,9 @@ Extension Modules
Library Library
------- -------
- bpo-29942: Fix a crash in itertools.chain.from_iterable when encountering
long runs of empty iterables.
- bpo-29861: Release references to tasks, their arguments and their results - bpo-29861: Release references to tasks, their arguments and their results
as soon as they are finished in multiprocessing.Pool. as soon as they are finished in multiprocessing.Pool.
......
...@@ -1708,33 +1708,37 @@ chain_next(chainobject *lz) ...@@ -1708,33 +1708,37 @@ chain_next(chainobject *lz)
{ {
PyObject *item; PyObject *item;
if (lz->source == NULL) /* lz->source is the iterator of iterables. If it's NULL, we've already
return NULL; /* already stopped */ * consumed them all. lz->active is the current iterator. If it's NULL,
* we should grab a new one from lz->source. */
if (lz->active == NULL) { while (lz->source != 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);
if (lz->active == NULL) { if (lz->active == NULL) {
Py_CLEAR(lz->source); PyObject *iterable = PyIter_Next(lz->source);
return NULL; /* input not iterable */ 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 = 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 */
}
/* lz->active is consumed, try with the next iterable. */
Py_CLEAR(lz->active);
} }
item = PyIter_Next(lz->active); /* Everything had been consumed already. */
if (item != NULL) return 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 */
} }
PyDoc_STRVAR(chain_doc, PyDoc_STRVAR(chain_doc,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment