Issue #26494: Fixed crash on iterating exhausting iterators. · pythoncapi/cpython@ab479c4 · GitHub
Skip to content

Commit ab479c4

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.
2 parents fe4c012 + fbb1c5e commit ab479c4

19 files changed

Lines changed: 92 additions & 22 deletions

Lib/test/seq_tests.py

Lines changed: 5 additions & 0 deletions

Lib/test/support/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,3 +2432,22 @@ def run_in_subinterp(code):
24322432
"memory allocations")
24332433
import _testcapi
24342434
return _testcapi.run_in_subinterp(code)
2435+
2436+
2437+
def check_free_after_iterating(test, iter, cls, args=()):
2438+
class A(cls):
2439+
def __del__(self):
2440+
nonlocal done
2441+
done = True
2442+
try:
2443+
next(it)
2444+
except StopIteration:
2445+
pass
2446+
2447+
done = False
2448+
it = iter(A(*args))
2449+
# Issue 26494: Shouldn't crash
2450+
test.assertRaises(StopIteration, next, it)
2451+
# The sequence should be deallocated just after the end of iterating
2452+
gc_collect()
2453+
test.assertTrue(done)

Lib/test/test_bytes.py

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

764+
def test_free_after_iterating(self):
765+
test.support.check_free_after_iterating(self, iter, self.type2test)
766+
test.support.check_free_after_iterating(self, reversed, self.type2test)
767+
764768

765769
class BytesTest(BaseBytesTest, unittest.TestCase):
766770
type2test = bytes

Lib/test/test_deque.py

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

921+
def test_free_after_iterating(self):
922+
# For now, bypass tests that require slicing
923+
self.skipTest("Exhausted deque iterator doesn't free a deque")
924+
921925
#==============================================================================
922926

923927
libreftest = """

Lib/test/test_dict.py

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

957+
def test_free_after_iterating(self):
958+
support.check_free_after_iterating(self, iter, dict)
959+
support.check_free_after_iterating(self, lambda d: iter(d.keys()), dict)
960+
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
961+
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
962+
957963
from test import mapping_tests
958964

959965
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
@@ -608,6 +608,12 @@ class A:
608608
gc.collect()
609609
self.assertIsNone(r())
610610

611+
def test_free_after_iterating(self):
612+
support.check_free_after_iterating(self, iter, self.OrderedDict)
613+
support.check_free_after_iterating(self, lambda d: iter(d.keys()), self.OrderedDict)
614+
support.check_free_after_iterating(self, lambda d: iter(d.values()), self.OrderedDict)
615+
support.check_free_after_iterating(self, lambda d: iter(d.items()), self.OrderedDict)
616+
611617

612618
class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
613619

Lib/test/test_set.py

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

367+
def test_free_after_iterating(self):
368+
support.check_free_after_iterating(self, iter, self.thetype)
369+
367370
class TestSet(TestJointOps, unittest.TestCase):
368371
thetype = set
369372
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

0 commit comments

Comments
 (0)