From 31bd3319e2d91509cf310a1533048fddd1cd8784 Mon Sep 17 00:00:00 2001 From: Shoryamishra61 Date: Sat, 10 Oct 2026 14:39:47 +0530 Subject: [PATCH] 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. --- Lib/test/test_yield_from.py | 84 +++++++++++++++++++ ...-10-10-14-36-00.gh-issue-159090.vL9b7q.rst | 3 + Modules/_testinternalcapi/test_cases.c.h | 3 +- Objects/genobject.c | 2 +- Python/bytecodes.c | 3 +- Python/generated_cases.c.h | 3 +- 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-10-14-36-00.gh-issue-159090.vL9b7q.rst diff --git a/Lib/test/test_yield_from.py b/Lib/test/test_yield_from.py index 74c9fa169876384..18d4cab10bcb0be 100644 --- a/Lib/test/test_yield_from.py +++ b/Lib/test/test_yield_from.py @@ -1595,6 +1595,90 @@ def my_generator(): with self.assertRaisesRegex(RuntimeError, "nobody expects the spanish inquisition"): next(iter(my_generator())) + def test_yield_from_stop_iteration_deleted_value(self): + # gh-159090: Avoid NULL dereference when StopIteration.value is deleted + class SillyIter: + def __iter__(self): + return self + def __next__(self): + exc = StopIteration(42) + del exc.value + raise exc + + def my_gen(): + res = yield from SillyIter() + return res + + g = my_gen() + with self.assertRaises(StopIteration) as cm: + next(g) + self.assertIsNone(cm.exception.value) + + class CustomStopIteration(StopIteration): + pass + + class CustomIter: + def __iter__(self): + return self + def __next__(self): + exc = CustomStopIteration(42) + del exc.value + raise exc + + def my_gen_custom(): + res = yield from CustomIter() + return res + + g = my_gen_custom() + with self.assertRaises(StopIteration) as cm: + next(g) + self.assertIsNone(cm.exception.value) + + def test_cleanup_throw_stop_iteration_deleted_value(self): + # gh-159090: CLEANUP_THROW instruction with deleted StopIteration.value + class ThrowIter: + def __iter__(self): + return self + def __next__(self): + return 1 + def throw(self, *args): + exc = StopIteration(42) + del exc.value + raise exc + + def my_gen(): + res = yield from ThrowIter() + return res + + g = my_gen() + self.assertEqual(next(g), 1) + with self.assertRaises(StopIteration) as cm: + g.throw(ValueError) + self.assertIsNone(cm.exception.value) + + class CustomStopIteration(StopIteration): + pass + + class CustomThrowIter: + def __iter__(self): + return self + def __next__(self): + return 1 + def throw(self, *args): + exc = CustomStopIteration(42) + del exc.value + raise exc + + def my_gen_custom(): + res = yield from CustomThrowIter() + return res + + g = my_gen_custom() + self.assertEqual(next(g), 1) + with self.assertRaises(StopIteration) as cm: + g.throw(ValueError) + self.assertIsNone(cm.exception.value) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-10-14-36-00.gh-issue-159090.vL9b7q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-10-14-36-00.gh-issue-159090.vL9b7q.rst new file mode 100644 index 000000000000000..7843c3f50d0c6eb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-10-14-36-00.gh-issue-159090.vL9b7q.rst @@ -0,0 +1,3 @@ +Fix a segmentation fault when a generator or ``yield from`` expression +receives a :exc:`StopIteration` instance whose ``value`` attribute has been +deleted. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 3bdc16437e2bc61..541ce294a507fe6 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -5190,7 +5190,8 @@ int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration); _PyFrame_StackPointerInvalidate(frame); if (matches) { - value = PyStackRef_FromPyObjectNew(((PyStopIterationObject *)exc_value)->value); + PyObject *val = ((PyStopIterationObject *)exc_value)->value; + value = val ? PyStackRef_FromPyObjectNew(val) : PyStackRef_None; assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); _PyStackRef tmp = sub_iter; diff --git a/Objects/genobject.c b/Objects/genobject.c index c313002c723e317..6cde6cb9d5954f6 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -815,7 +815,7 @@ _PyGen_FetchStopIterationValue(PyObject **pvalue) PyObject *value = NULL; if (PyErr_ExceptionMatches(PyExc_StopIteration)) { PyObject *exc = PyErr_GetRaisedException(); - value = Py_NewRef(((PyStopIterationObject *)exc)->value); + value = Py_XNewRef(((PyStopIterationObject *)exc)->value); Py_DECREF(exc); } else if (PyErr_Occurred()) { return -1; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 31eaeab0d67841f..d7d54c20dc3dc10 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1968,7 +1968,8 @@ dummy_func( assert(exc_value && PyExceptionInstance_Check(exc_value)); int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration); if (matches) { - value = PyStackRef_FromPyObjectNew(((PyStopIterationObject *)exc_value)->value); + PyObject *val = ((PyStopIterationObject *)exc_value)->value; + value = val ? PyStackRef_FromPyObjectNew(val) : PyStackRef_None; DECREF_INPUTS(); null_out = null_in; none = PyStackRef_None; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index dd0ce41e4b06b4a..41fe166d559aabe 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -5190,7 +5190,8 @@ int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration); _PyFrame_StackPointerInvalidate(frame); if (matches) { - value = PyStackRef_FromPyObjectNew(((PyStopIterationObject *)exc_value)->value); + PyObject *val = ((PyStopIterationObject *)exc_value)->value; + value = val ? PyStackRef_FromPyObjectNew(val) : PyStackRef_None; assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); _PyStackRef tmp = sub_iter;