@@ -91,6 +91,12 @@ def __call__(self):
91
91
raise IndexError # Emergency stop
92
92
return i
93
93
94
+ class EmptyIterClass :
95
+ def __len__ (self ):
96
+ return 0
97
+ def __getitem__ (self , i ):
98
+ raise StopIteration
99
+
94
100
# Main test suite
95
101
96
102
class TestCase (unittest .TestCase ):
@@ -238,6 +244,52 @@ def test_mutating_seq_class_exhausted_iter(self):
238
244
self .assertEqual (list (empit ), [5 , 6 ])
239
245
self .assertEqual (list (a ), [0 , 1 , 2 , 3 , 4 , 5 , 6 ])
240
246
247
+ def test_reduce_mutating_builtins_iter (self ):
248
+ # This is a reproducer of issue #101765
249
+ # where iter `__reduce__` calls could lead to a segfault or SystemError
250
+ # depending on the order of C argument evaluation, which is undefined
251
+
252
+ # Backup builtins.iter
253
+ builtins = __builtins__ .__dict__ if hasattr (__builtins__ , "__dict__" ) else __builtins__
254
+ orig_iter = builtins ["iter" ]
255
+
256
+ def run (item ):
257
+ (fn , * flag ), * initializer = item
258
+ it = iter (fn (* initializer ), * flag )
259
+
260
+ class CustomStr :
261
+ def __hash__ (self ):
262
+ return hash ("iter" )
263
+ def __eq__ (self , other ):
264
+ # Here we exhaust `it`, possibly changing our `it_seq` pointer to NULL
265
+ # The `__reduce__` call should correctly get the pointers after this call
266
+ list (it )
267
+ return other == "iter"
268
+
269
+ _iter = builtins ["iter" ]
270
+ del builtins ["iter" ]
271
+ builtins [CustomStr ()] = _iter
272
+
273
+ return it .__reduce__ ()
274
+
275
+ types = [
276
+ ([EmptyIterClass ],),
277
+ ([bytes ], 8 ),
278
+ ([bytearray ], 8 ),
279
+ ([tuple ], range (8 )),
280
+ ([lambda : (lambda : 0 ), 0 ],)
281
+ ]
282
+
283
+ try :
284
+ self .assertEqual (run (([str ], "xyz" )), (orig_iter , ("xyz" ,), 0 ))
285
+ self .assertEqual (run (([list ], range (8 ))), (orig_iter , ([],)))
286
+ for case in types :
287
+ self .assertEqual (run (case ), (orig_iter , ((),)))
288
+ finally :
289
+ # Restore original iter
290
+ del builtins ["iter" ]
291
+ builtins ["iter" ] = orig_iter
292
+
241
293
# Test a new_style class with __iter__ but no next() method
242
294
def test_new_style_iter_class (self ):
243
295
class IterClass (object ):
0 commit comments