Skip to content

Commit 31bd331

Browse files
gh-159090: Handle deleted StopIteration values in generators
Safely handle cases where StopIteration.value is deleted or NULL in _PyGen_FetchStopIterationValue and the CLEANUP_THROW bytecode instruction.
1 parent 7d5dce9 commit 31bd331

6 files changed

Lines changed: 94 additions & 4 deletions

File tree

‎Lib/test/test_yield_from.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,90 @@ def my_generator():
15951595
with self.assertRaisesRegex(RuntimeError, "nobody expects the spanish inquisition"):
15961596
next(iter(my_generator()))
15971597

1598+
def test_yield_from_stop_iteration_deleted_value(self):
1599+
# gh-159090: Avoid NULL dereference when StopIteration.value is deleted
1600+
class SillyIter:
1601+
def __iter__(self):
1602+
return self
1603+
def __next__(self):
1604+
exc = StopIteration(42)
1605+
del exc.value
1606+
raise exc
1607+
1608+
def my_gen():
1609+
res = yield from SillyIter()
1610+
return res
1611+
1612+
g = my_gen()
1613+
with self.assertRaises(StopIteration) as cm:
1614+
next(g)
1615+
self.assertIsNone(cm.exception.value)
1616+
1617+
class CustomStopIteration(StopIteration):
1618+
pass
1619+
1620+
class CustomIter:
1621+
def __iter__(self):
1622+
return self
1623+
def __next__(self):
1624+
exc = CustomStopIteration(42)
1625+
del exc.value
1626+
raise exc
1627+
1628+
def my_gen_custom():
1629+
res = yield from CustomIter()
1630+
return res
1631+
1632+
g = my_gen_custom()
1633+
with self.assertRaises(StopIteration) as cm:
1634+
next(g)
1635+
self.assertIsNone(cm.exception.value)
1636+
1637+
def test_cleanup_throw_stop_iteration_deleted_value(self):
1638+
# gh-159090: CLEANUP_THROW instruction with deleted StopIteration.value
1639+
class ThrowIter:
1640+
def __iter__(self):
1641+
return self
1642+
def __next__(self):
1643+
return 1
1644+
def throw(self, *args):
1645+
exc = StopIteration(42)
1646+
del exc.value
1647+
raise exc
1648+
1649+
def my_gen():
1650+
res = yield from ThrowIter()
1651+
return res
1652+
1653+
g = my_gen()
1654+
self.assertEqual(next(g), 1)
1655+
with self.assertRaises(StopIteration) as cm:
1656+
g.throw(ValueError)
1657+
self.assertIsNone(cm.exception.value)
1658+
1659+
class CustomStopIteration(StopIteration):
1660+
pass
1661+
1662+
class CustomThrowIter:
1663+
def __iter__(self):
1664+
return self
1665+
def __next__(self):
1666+
return 1
1667+
def throw(self, *args):
1668+
exc = CustomStopIteration(42)
1669+
del exc.value
1670+
raise exc
1671+
1672+
def my_gen_custom():
1673+
res = yield from CustomThrowIter()
1674+
return res
1675+
1676+
g = my_gen_custom()
1677+
self.assertEqual(next(g), 1)
1678+
with self.assertRaises(StopIteration) as cm:
1679+
g.throw(ValueError)
1680+
self.assertIsNone(cm.exception.value)
1681+
15981682

15991683
if __name__ == '__main__':
16001684
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a segmentation fault when a generator or ``yield from`` expression
2+
receives a :exc:`StopIteration` instance whose ``value`` attribute has been
3+
deleted.

‎Modules/_testinternalcapi/test_cases.c.h‎

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/genobject.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ _PyGen_FetchStopIterationValue(PyObject **pvalue)
815815
PyObject *value = NULL;
816816
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
817817
PyObject *exc = PyErr_GetRaisedException();
818-
value = Py_NewRef(((PyStopIterationObject *)exc)->value);
818+
value = Py_XNewRef(((PyStopIterationObject *)exc)->value);
819819
Py_DECREF(exc);
820820
} else if (PyErr_Occurred()) {
821821
return -1;

‎Python/bytecodes.c‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1968,7 +1968,8 @@ dummy_func(
19681968
assert(exc_value && PyExceptionInstance_Check(exc_value));
19691969
int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration);
19701970
if (matches) {
1971-
value = PyStackRef_FromPyObjectNew(((PyStopIterationObject *)exc_value)->value);
1971+
PyObject *val = ((PyStopIterationObject *)exc_value)->value;
1972+
value = val ? PyStackRef_FromPyObjectNew(val) : PyStackRef_None;
19721973
DECREF_INPUTS();
19731974
null_out = null_in;
19741975
none = PyStackRef_None;

‎Python/generated_cases.c.h‎

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)