Skip to content

Commit fbb1c5e

Browse files
Issue python#26494: Fixed crash on iterating exhausting iterators.
Affected classes are generic sequence iterators, iterators of str, bytes, bytearray, list, tuple, set, frozenset, dict, OrderedDict, corresponding views and os.scandir() iterator.
1 parent 13b3acd commit fbb1c5e

19 files changed

Lines changed: 92 additions & 22 deletions

Lib/test/seq_tests.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import unittest
66
import sys
77
import pickle
8+
from test import support
89

910
# Various iterables
1011
# This is used for checking the constructor (here and in test_deque.py)
@@ -408,3 +409,7 @@ def test_pickle(self):
408409
lst2 = pickle.loads(pickle.dumps(lst, proto))
409410
self.assertEqual(lst2, lst)
410411
self.assertNotEqual(id(lst2), id(lst))
412+
413+
def test_free_after_iterating(self):
414+
support.check_free_after_iterating(self, iter, self.type2test)
415+
support.check_free_after_iterating(self, reversed, self.type2test)

Lib/test/support/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,3 +2366,22 @@ def run_in_subinterp(code):
23662366
"memory allocations")
23672367
import _testcapi
23682368
return _testcapi.run_in_subinterp(code)
2369+
2370+
2371+
def check_free_after_iterating(test, iter, cls, args=()):
2372+
class A(cls):
2373+
def __del__(self):
2374+
nonlocal done
2375+
done = True
2376+
try:
2377+
next(it)
2378+
except StopIteration:
2379+
pass
2380+
2381+
done = False
2382+
it = iter(A(*args))
2383+
# Issue 26494: Shouldn't crash
2384+
test.assertRaises(StopIteration, next, it)
2385+
# The sequence should be deallocated just after the end of iterating
2386+
gc_collect()
2387+
test.assertTrue(done)

Lib/test/test_bytes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,10 @@ def test_find_etc_raise_correct_error_messages(self):
747747
self.assertRaisesRegex(TypeError, r'\bendswith\b', b.endswith,
748748
x, None, None, None)
749749

750+
def test_free_after_iterating(self):
751+
test.support.check_free_after_iterating(self, iter, self.type2test)
752+
test.support.check_free_after_iterating(self, reversed, self.type2test)
753+
750754

751755
class BytesTest(BaseBytesTest, unittest.TestCase):
752756
type2test = bytes

Lib/test/test_deque.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ def test_subscript(self):
905905
# For now, bypass tests that require slicing
906906
pass
907907

908+
def test_free_after_iterating(self):
909+
# For now, bypass tests that require slicing
910+
self.skipTest("Exhausted deque iterator doesn't free a deque")
911+
908912
#==============================================================================
909913

910914
libreftest = """

Lib/test/test_dict.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,12 @@ def __eq__(self, o):
952952
d = {X(): 0, 1: 1}
953953
self.assertRaises(RuntimeError, d.update, other)
954954

955+
def test_free_after_iterating(self):
956+
support.check_free_after_iterating(self, iter, dict)
957+
support.check_free_after_iterating(self, lambda d: iter(d.keys()), dict)
958+
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
959+
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
960+
955961
from test import mapping_tests
956962

957963
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):

Lib/test/test_iter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
import unittest
55
from test.support import run_unittest, TESTFN, unlink, cpython_only
6+
from test.support import check_free_after_iterating
67
import pickle
78
import collections.abc
89

@@ -980,6 +981,9 @@ def test_iter_neg_setstate(self):
980981
self.assertEqual(next(it), 0)
981982
self.assertEqual(next(it), 1)
982983

984+
def test_free_after_iterating(self):
985+
check_free_after_iterating(self, iter, SequenceClass, (0,))
986+
983987

984988
def test_main():
985989
run_unittest(TestCase)

Lib/test/test_ordered_dict.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,12 @@ class A:
598598
gc.collect()
599599
self.assertIsNone(r())
600600

601+
def test_free_after_iterating(self):
602+
support.check_free_after_iterating(self, iter, self.OrderedDict)
603+
support.check_free_after_iterating(self, lambda d: iter(d.keys()), self.OrderedDict)
604+
support.check_free_after_iterating(self, lambda d: iter(d.values()), self.OrderedDict)
605+
support.check_free_after_iterating(self, lambda d: iter(d.items()), self.OrderedDict)
606+
601607

602608
class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
603609

Lib/test/test_set.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ class C(object):
362362
gc.collect()
363363
self.assertTrue(ref() is None, "Cycle was not collected")
364364

365+
def test_free_after_iterating(self):
366+
support.check_free_after_iterating(self, iter, self.thetype)
367+
365368
class TestSet(TestJointOps, unittest.TestCase):
366369
thetype = set
367370
basetype = set

Lib/test/test_unicode.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,6 +2729,10 @@ def test_pep393_utf8_caching_bug(self):
27292729
# Check that the second call returns the same result
27302730
self.assertEqual(getargs_s_hash(s), chr(k).encode() * (i + 1))
27312731

2732+
def test_free_after_iterating(self):
2733+
support.check_free_after_iterating(self, iter, str)
2734+
support.check_free_after_iterating(self, reversed, str)
2735+
27322736

27332737
class StringModuleTest(unittest.TestCase):
27342738
def test_formatter_parser(self):

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Release date: tba
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #26494: Fixed crash on iterating exhausting iterators.
14+
Affected classes are generic sequence iterators, iterators of str, bytes,
15+
bytearray, list, tuple, set, frozenset, dict, OrderedDict, corresponding
16+
views and os.scandir() iterator.
17+
1318
- Issue #26581: If coding cookie is specified multiple times on a line in
1419
Python source code file, only the first one is taken to account.
1520

0 commit comments

Comments
 (0)