Skip to content

Commit 26fd46e

Browse files
committed
Fix infinite generator hanging on extended slice assignment (gh-146268)
1 parent 83360b5 commit 26fd46e

File tree

3 files changed

+134
-6
lines changed

3 files changed

+134
-6
lines changed

Lib/test/list_tests.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,55 @@ def test_slice_assign_iterator(self):
202202
x[:] = reversed(range(3))
203203
self.assertEqual(x, self.type2test([2, 1, 0]))
204204

205+
def test_extended_slice_assign_infinite_iterator(self):
206+
# gh-146268: assigning an infinite iterator to an extended slice
207+
# (step != 1) should raise ValueError, not hang indefinitely.
208+
def infinite_gen():
209+
while True:
210+
yield "foo"
211+
212+
a = self.type2test(range(4))
213+
with self.assertRaises(ValueError) as cm:
214+
a[::2] = infinite_gen()
215+
self.assertIn("yielded more items than extended slice of size 2",
216+
str(cm.exception))
217+
# list should be unchanged after the failed assignment
218+
self.assertEqual(a, self.type2test(range(4)))
219+
220+
a = self.type2test(range(10))
221+
with self.assertRaises(ValueError):
222+
a[::3] = infinite_gen()
223+
self.assertEqual(a, self.type2test(range(10)))
224+
225+
# Negative step with infinite iterator
226+
a = self.type2test(range(6))
227+
with self.assertRaises(ValueError):
228+
a[::-2] = infinite_gen()
229+
self.assertEqual(a, self.type2test(range(6)))
230+
231+
def test_extended_slice_assign_iterator(self):
232+
# Assigning a finite iterator with the correct length to an
233+
# extended slice should work.
234+
a = self.type2test(range(10))
235+
a[::2] = iter(range(5))
236+
self.assertEqual(a, self.type2test([0, 1, 1, 3, 2, 5, 3, 7, 4, 9]))
237+
238+
# Too few items from an iterator
239+
a = self.type2test(range(10))
240+
with self.assertRaises(ValueError) as cm:
241+
a[::2] = iter(range(3))
242+
self.assertIn("sequence of size 3 to extended slice of size 5",
243+
str(cm.exception))
244+
self.assertEqual(a, self.type2test(range(10)))
245+
246+
# Too many items from an iterator
247+
a = self.type2test(range(10))
248+
with self.assertRaises(ValueError) as cm:
249+
a[::2] = iter(range(10))
250+
self.assertIn("yielded more items than extended slice of size 5",
251+
str(cm.exception))
252+
self.assertEqual(a, self.type2test(range(10)))
253+
205254
def test_delslice(self):
206255
a = self.type2test([0, 1])
207256
del a[1:2]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed extended slice assignment (e.g. ``l[::2] = x``) hanging when the
2+
right-hand side is an infinite iterator. A bounded iteration is now used so
3+
that a :exc:`ValueError` is raised promptly. Patch by Charles Machalow.

Objects/listobject.c

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3821,12 +3821,70 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value)
38213821
PyObject **garbage, **seqitems, **selfitems;
38223822
Py_ssize_t i;
38233823
size_t cur;
3824+
int bounded_iter = 0;
38243825

38253826
/* protect against a[::-1] = a */
38263827
if (self == (PyListObject*)value) {
38273828
seq = list_slice_lock_held((PyListObject *)value, 0,
38283829
Py_SIZE(value));
38293830
}
3831+
else if (step != 1 &&
3832+
!PyList_CheckExact(value) &&
3833+
!PyTuple_CheckExact(value))
3834+
{
3835+
/* For extended slices (step != 1) with arbitrary iterables,
3836+
use bounded iteration to avoid hanging on infinite
3837+
iterators (gh-146268). We compute a preliminary slice
3838+
length to cap the number of items we collect. The real
3839+
slice length is recomputed afterwards because the
3840+
iterable's __next__ may mutate the list. */
3841+
Py_ssize_t tmp_start = start, tmp_stop = stop;
3842+
Py_ssize_t slicelength_bound = adjust_slice_indexes(
3843+
self, &tmp_start, &tmp_stop, step);
3844+
3845+
PyObject *it = PyObject_GetIter(value);
3846+
if (it == NULL) {
3847+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
3848+
PyErr_SetString(PyExc_TypeError,
3849+
"must assign iterable "
3850+
"to extended slice");
3851+
}
3852+
return -1;
3853+
}
3854+
Py_ssize_t alloc = slicelength_bound + 1;
3855+
if (alloc <= 0) {
3856+
/* Overflow or zero-length slice; still collect at
3857+
least 1 item so the size check can detect a
3858+
non-empty iterable. */
3859+
alloc = 1;
3860+
}
3861+
seq = PyList_New(alloc);
3862+
if (seq == NULL) {
3863+
Py_DECREF(it);
3864+
return -1;
3865+
}
3866+
Py_ssize_t j;
3867+
for (j = 0; j < alloc; j++) {
3868+
PyObject *v = PyIter_Next(it);
3869+
if (v == NULL) {
3870+
if (PyErr_Occurred()) {
3871+
/* Discard unfilled slots before decref */
3872+
Py_SET_SIZE(seq, j);
3873+
Py_DECREF(seq);
3874+
Py_DECREF(it);
3875+
return -1;
3876+
}
3877+
break;
3878+
}
3879+
PyList_SET_ITEM(seq, j, v);
3880+
}
3881+
Py_DECREF(it);
3882+
/* Shrink to the number of items actually collected */
3883+
if (j < alloc) {
3884+
Py_SET_SIZE(seq, j);
3885+
}
3886+
bounded_iter = 1;
3887+
}
38303888
else {
38313889
seq = PySequence_Fast(value,
38323890
"must assign iterable "
@@ -3845,12 +3903,30 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value)
38453903
}
38463904

38473905
if (PySequence_Fast_GET_SIZE(seq) != slicelength) {
3848-
PyErr_Format(PyExc_ValueError,
3849-
"attempt to assign sequence of "
3850-
"size %zd to extended slice of "
3851-
"size %zd",
3852-
PySequence_Fast_GET_SIZE(seq),
3853-
slicelength);
3906+
if (bounded_iter &&
3907+
PySequence_Fast_GET_SIZE(seq) > slicelength) {
3908+
PyErr_Format(PyExc_ValueError,
3909+
"attempt to assign iterable that yielded "
3910+
"more items than extended slice of "
3911+
"size %zd",
3912+
slicelength);
3913+
}
3914+
else if (bounded_iter) {
3915+
PyErr_Format(PyExc_ValueError,
3916+
"attempt to assign sequence of "
3917+
"size %zd to extended slice of "
3918+
"size %zd",
3919+
PySequence_Fast_GET_SIZE(seq),
3920+
slicelength);
3921+
}
3922+
else {
3923+
PyErr_Format(PyExc_ValueError,
3924+
"attempt to assign sequence of "
3925+
"size %zd to extended slice of "
3926+
"size %zd",
3927+
PySequence_Fast_GET_SIZE(seq),
3928+
slicelength);
3929+
}
38543930
Py_DECREF(seq);
38553931
return -1;
38563932
}

0 commit comments

Comments
 (0)