Skip to content

Commit 2a44069

Browse files
committed
Fix NULL deref in async yield from cleanup
Treat a deleted `StopAsyncIteration.value` as `None` when handling delegated iterator exhaustion. Add a regression test covering immediate exhaustion and exhaustion after yielding an item.
1 parent 1037bed commit 2a44069

4 files changed

Lines changed: 44 additions & 4 deletions

File tree

‎Lib/test/test_yield_from_async.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,32 @@ class TestPEP828Extras(unittest.TestCase):
16931693
parity-checked against ``test_yield_from``.
16941694
"""
16951695

1696+
@_async_test
1697+
async def test_custom_iterator_deleted_return_value(self):
1698+
class Iterator:
1699+
def __init__(self, remaining):
1700+
self.remaining = remaining
1701+
1702+
def __aiter__(self):
1703+
return self
1704+
1705+
async def __anext__(self):
1706+
if self.remaining:
1707+
self.remaining -= 1
1708+
return 42
1709+
exc = StopAsyncIteration(99)
1710+
del exc.value
1711+
raise exc
1712+
1713+
async def gen(count):
1714+
result = yield from Iterator(count)
1715+
self.assertIsNone(result)
1716+
1717+
for count in (0, 1):
1718+
with self.subTest(count=count):
1719+
self.assertEqual([item async for item in gen(count)],
1720+
[42] * count)
1721+
16961722
@_async_test
16971723
async def test_delegate_exception(self):
16981724
yielded_first = sentinel("yielded_first")

‎Modules/_testinternalcapi/test_cases.c.h‎

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

‎Python/bytecodes.c‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "pycore_abstract.h" // _PyIndex_Check()
1111
#include "pycore_audit.h" // _PySys_Audit()
1212
#include "pycore_backoff.h"
13-
#include "pycore_call.h" // _Py_METH_CALL_FLAGS
1413
#include "pycore_cell.h" // PyCell_GetRef()
1514
#include "pycore_ceval.h" // _PyEval_LazyImportName(), _PyEval_LazyImportFrom()
1615
#include "pycore_code.h"
@@ -1987,7 +1986,12 @@ dummy_func(
19871986

19881987
int matches = PyErr_GivenExceptionMatches(exc_value, PyExc_StopAsyncIteration);
19891988
if (matches) {
1990-
value = PyStackRef_FromPyObjectNew(((PyStopAsyncIterationObject *)exc_value)->value);
1989+
PyObject *ret = ((PyStopAsyncIterationObject *)exc_value)->value;
1990+
if (ret == NULL) {
1991+
value = PyStackRef_None;
1992+
} else {
1993+
value = PyStackRef_FromPyObjectNew(ret);
1994+
}
19911995
DECREF_INPUTS();
19921996
}
19931997
else {

‎Python/generated_cases.c.h‎

Lines changed: 6 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)