Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 24411f8

Browse files
Issue #23996: Added _PyGen_SetStopIterationValue for safe raising
StopIteration with value. More safely handle non-normalized exceptions in -_PyGen_FetchStopIterationValue.
1 parent 04b3d8b commit 24411f8

4 files changed

Lines changed: 133 additions & 10 deletions

File tree

Include/genobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
4141
PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(struct _frame *,
4242
PyObject *name, PyObject *qualname);
4343
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
44+
PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *);
4445
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
4546
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
4647
PyObject *_PyGen_yf(PyGenObject *);

Lib/test/test_coroutines.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,21 @@ async def coroutine():
710710
coro.close()
711711
self.assertEqual(CHK, 1)
712712

713+
def test_coro_wrapper_send_tuple(self):
714+
async def foo():
715+
return (10,)
716+
717+
result = run_async__await__(foo())
718+
self.assertEqual(result, ([], (10,)))
719+
720+
def test_coro_wrapper_send_stop_iterator(self):
721+
async def foo():
722+
return StopIteration(10)
723+
724+
result = run_async__await__(foo())
725+
self.assertIsInstance(result[1], StopIteration)
726+
self.assertEqual(result[1].value, 10)
727+
713728
def test_cr_await(self):
714729
@types.coroutine
715730
def a():
@@ -1537,6 +1552,52 @@ async def foo():
15371552
warnings.simplefilter("error")
15381553
run_async(foo())
15391554

1555+
def test_for_tuple(self):
1556+
class Done(Exception): pass
1557+
1558+
class AIter(tuple):
1559+
i = 0
1560+
def __aiter__(self):
1561+
return self
1562+
async def __anext__(self):
1563+
if self.i >= len(self):
1564+
raise StopAsyncIteration
1565+
self.i += 1
1566+
return self[self.i - 1]
1567+
1568+
result = []
1569+
async def foo():
1570+
async for i in AIter([42]):
1571+
result.append(i)
1572+
raise Done
1573+
1574+
with self.assertRaises(Done):
1575+
foo().send(None)
1576+
self.assertEqual(result, [42])
1577+
1578+
def test_for_stop_iteration(self):
1579+
class Done(Exception): pass
1580+
1581+
class AIter(StopIteration):
1582+
i = 0
1583+
def __aiter__(self):
1584+
return self
1585+
async def __anext__(self):
1586+
if self.i:
1587+
raise StopAsyncIteration
1588+
self.i += 1
1589+
return self.value
1590+
1591+
result = []
1592+
async def foo():
1593+
async for i in AIter(42):
1594+
result.append(i)
1595+
raise Done
1596+
1597+
with self.assertRaises(Done):
1598+
foo().send(None)
1599+
self.assertEqual(result, [42])
1600+
15401601
def test_copy(self):
15411602
async def func(): pass
15421603
coro = func()

Lib/test/test_generators.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,27 @@ def f():
277277
# hence no warning.
278278
next(g)
279279

280+
def test_return_tuple(self):
281+
def g():
282+
return (yield 1)
283+
284+
gen = g()
285+
self.assertEqual(next(gen), 1)
286+
with self.assertRaises(StopIteration) as cm:
287+
gen.send((2,))
288+
self.assertEqual(cm.exception.value, (2,))
289+
290+
def test_return_stopiteration(self):
291+
def g():
292+
return (yield 1)
293+
294+
gen = g()
295+
self.assertEqual(next(gen), 1)
296+
with self.assertRaises(StopIteration) as cm:
297+
gen.send(StopIteration(2))
298+
self.assertIsInstance(cm.exception.value, StopIteration)
299+
self.assertEqual(cm.exception.value.value, 2)
300+
280301

281302
class YieldFromTests(unittest.TestCase):
282303
def test_generator_gi_yieldfrom(self):

Objects/genobject.c

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
154154
/* Delay exception instantiation if we can */
155155
PyErr_SetNone(PyExc_StopIteration);
156156
} else {
157-
PyObject *e = PyObject_CallFunctionObjArgs(
158-
PyExc_StopIteration, result, NULL);
159-
if (e != NULL) {
160-
PyErr_SetObject(PyExc_StopIteration, e);
161-
Py_DECREF(e);
162-
}
157+
_PyGen_SetStopIterationValue(result);
163158
}
164159
Py_CLEAR(result);
165160
}
@@ -459,6 +454,43 @@ gen_iternext(PyGenObject *gen)
459454
return gen_send_ex(gen, NULL, 0, 0);
460455
}
461456

457+
/*
458+
* Set StopIteration with specified value. Value can be arbitrary object
459+
* or NULL.
460+
*
461+
* Returns 0 if StopIteration is set and -1 if any other exception is set.
462+
*/
463+
int
464+
_PyGen_SetStopIterationValue(PyObject *value)
465+
{
466+
PyObject *e;
467+
468+
if (value == NULL ||
469+
(!PyTuple_Check(value) &&
470+
!PyObject_TypeCheck(value, (PyTypeObject *) PyExc_StopIteration)))
471+
{
472+
/* Delay exception instantiation if we can */
473+
PyErr_SetObject(PyExc_StopIteration, value);
474+
return 0;
475+
}
476+
/* Construct an exception instance manually with
477+
* PyObject_CallFunctionObjArgs and pass it to PyErr_SetObject.
478+
*
479+
* We do this to handle a situation when "value" is a tuple, in which
480+
* case PyErr_SetObject would set the value of StopIteration to
481+
* the first element of the tuple.
482+
*
483+
* (See PyErr_SetObject/_PyErr_CreateException code for details.)
484+
*/
485+
e = PyObject_CallFunctionObjArgs(PyExc_StopIteration, value, NULL);
486+
if (e == NULL) {
487+
return -1;
488+
}
489+
PyErr_SetObject(PyExc_StopIteration, e);
490+
Py_DECREF(e);
491+
return 0;
492+
}
493+
462494
/*
463495
* If StopIteration exception is set, fetches its 'value'
464496
* attribute if any, otherwise sets pvalue to None.
@@ -469,7 +501,8 @@ gen_iternext(PyGenObject *gen)
469501
*/
470502

471503
int
472-
_PyGen_FetchStopIterationValue(PyObject **pvalue) {
504+
_PyGen_FetchStopIterationValue(PyObject **pvalue)
505+
{
473506
PyObject *et, *ev, *tb;
474507
PyObject *value = NULL;
475508

@@ -481,8 +514,15 @@ _PyGen_FetchStopIterationValue(PyObject **pvalue) {
481514
value = ((PyStopIterationObject *)ev)->value;
482515
Py_INCREF(value);
483516
Py_DECREF(ev);
484-
} else if (et == PyExc_StopIteration) {
485-
/* avoid normalisation and take ev as value */
517+
} else if (et == PyExc_StopIteration && !PyTuple_Check(ev)) {
518+
/* Avoid normalisation and take ev as value.
519+
*
520+
* Normalization is required if the value is a tuple, in
521+
* that case the value of StopIteration would be set to
522+
* the first element of the tuple.
523+
*
524+
* (See _PyErr_CreateException code for details.)
525+
*/
486526
value = ev;
487527
} else {
488528
/* normalisation required */
@@ -1012,7 +1052,7 @@ typedef struct {
10121052
static PyObject *
10131053
aiter_wrapper_iternext(PyAIterWrapper *aw)
10141054
{
1015-
PyErr_SetObject(PyExc_StopIteration, aw->aw_aiter);
1055+
_PyGen_SetStopIterationValue(aw->aw_aiter);
10161056
return NULL;
10171057
}
10181058

0 commit comments

Comments
 (0)