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

Skip to content

Commit 85bcf37

Browse files
Issue #23996: Added _PyGen_SetStopIterationValue for safe raising
StopIteration with value. More safely handle non-normalized exceptions in -_PyGen_FetchStopIterationValue.
2 parents 692b97c + 60e49aa commit 85bcf37

7 files changed

Lines changed: 281 additions & 68 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
PyAPI_FUNC(PyObject *) _PyGen_Send(PyGenObject *, PyObject *);
4647
PyObject *_PyGen_yf(PyGenObject *);

Lib/test/test_asyncgen.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,48 @@ async def run():
450450

451451
self.loop.run_until_complete(run())
452452

453+
def test_async_gen_asyncio_anext_tuple(self):
454+
async def foo():
455+
try:
456+
yield (1,)
457+
except ZeroDivisionError:
458+
yield (2,)
459+
460+
async def run():
461+
it = foo().__aiter__()
462+
463+
self.assertEqual(await it.__anext__(), (1,))
464+
with self.assertRaises(StopIteration) as cm:
465+
it.__anext__().throw(ZeroDivisionError)
466+
self.assertEqual(cm.exception.args[0], (2,))
467+
with self.assertRaises(StopAsyncIteration):
468+
await it.__anext__()
469+
470+
self.loop.run_until_complete(run())
471+
472+
def test_async_gen_asyncio_anext_stopiteration(self):
473+
async def foo():
474+
try:
475+
yield StopIteration(1)
476+
except ZeroDivisionError:
477+
yield StopIteration(3)
478+
479+
async def run():
480+
it = foo().__aiter__()
481+
482+
v = await it.__anext__()
483+
self.assertIsInstance(v, StopIteration)
484+
self.assertEqual(v.value, 1)
485+
with self.assertRaises(StopIteration) as cm:
486+
it.__anext__().throw(ZeroDivisionError)
487+
v = cm.exception.args[0]
488+
self.assertIsInstance(v, StopIteration)
489+
self.assertEqual(v.value, 3)
490+
with self.assertRaises(StopAsyncIteration):
491+
await it.__anext__()
492+
493+
self.loop.run_until_complete(run())
494+
453495
def test_async_gen_asyncio_aclose_06(self):
454496
async def foo():
455497
try:
@@ -759,6 +801,43 @@ async def run():
759801
self.loop.run_until_complete(run())
760802
self.assertEqual(DONE, 1)
761803

804+
def test_async_gen_asyncio_athrow_tuple(self):
805+
async def gen():
806+
try:
807+
yield 1
808+
except ZeroDivisionError:
809+
yield (2,)
810+
811+
async def run():
812+
g = gen()
813+
v = await g.asend(None)
814+
self.assertEqual(v, 1)
815+
v = await g.athrow(ZeroDivisionError)
816+
self.assertEqual(v, (2,))
817+
with self.assertRaises(StopAsyncIteration):
818+
await g.asend(None)
819+
820+
self.loop.run_until_complete(run())
821+
822+
def test_async_gen_asyncio_athrow_stopiteration(self):
823+
async def gen():
824+
try:
825+
yield 1
826+
except ZeroDivisionError:
827+
yield StopIteration(2)
828+
829+
async def run():
830+
g = gen()
831+
v = await g.asend(None)
832+
self.assertEqual(v, 1)
833+
v = await g.athrow(ZeroDivisionError)
834+
self.assertIsInstance(v, StopIteration)
835+
self.assertEqual(v.value, 2)
836+
with self.assertRaises(StopAsyncIteration):
837+
await g.asend(None)
838+
839+
self.loop.run_until_complete(run())
840+
762841
def test_async_gen_asyncio_shutdown_01(self):
763842
finalized = 0
764843

Lib/test/test_coroutines.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,21 @@ async def coroutine():
838838
coro.close()
839839
self.assertEqual(CHK, 1)
840840

841+
def test_coro_wrapper_send_tuple(self):
842+
async def foo():
843+
return (10,)
844+
845+
result = run_async__await__(foo())
846+
self.assertEqual(result, ([], (10,)))
847+
848+
def test_coro_wrapper_send_stop_iterator(self):
849+
async def foo():
850+
return StopIteration(10)
851+
852+
result = run_async__await__(foo())
853+
self.assertIsInstance(result[1], StopIteration)
854+
self.assertEqual(result[1].value, 10)
855+
841856
def test_cr_await(self):
842857
@types.coroutine
843858
def a():
@@ -1665,6 +1680,52 @@ async def foo():
16651680
warnings.simplefilter("error")
16661681
run_async(foo())
16671682

1683+
def test_for_tuple(self):
1684+
class Done(Exception): pass
1685+
1686+
class AIter(tuple):
1687+
i = 0
1688+
def __aiter__(self):
1689+
return self
1690+
async def __anext__(self):
1691+
if self.i >= len(self):
1692+
raise StopAsyncIteration
1693+
self.i += 1
1694+
return self[self.i - 1]
1695+
1696+
result = []
1697+
async def foo():
1698+
async for i in AIter([42]):
1699+
result.append(i)
1700+
raise Done
1701+
1702+
with self.assertRaises(Done):
1703+
foo().send(None)
1704+
self.assertEqual(result, [42])
1705+
1706+
def test_for_stop_iteration(self):
1707+
class Done(Exception): pass
1708+
1709+
class AIter(StopIteration):
1710+
i = 0
1711+
def __aiter__(self):
1712+
return self
1713+
async def __anext__(self):
1714+
if self.i:
1715+
raise StopAsyncIteration
1716+
self.i += 1
1717+
return self.value
1718+
1719+
result = []
1720+
async def foo():
1721+
async for i in AIter(42):
1722+
result.append(i)
1723+
raise Done
1724+
1725+
with self.assertRaises(Done):
1726+
foo().send(None)
1727+
self.assertEqual(result, [42])
1728+
16681729
def test_comp_1(self):
16691730
async def f(i):
16701731
return i

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):

Lib/test/test_yield_from.py

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,10 @@ def g1():
384384
trace.append("Starting g1")
385385
yield "g1 ham"
386386
ret = yield from g2()
387-
trace.append("g2 returned %s" % (ret,))
388-
ret = yield from g2(42)
389-
trace.append("g2 returned %s" % (ret,))
387+
trace.append("g2 returned %r" % (ret,))
388+
for v in 1, (2,), StopIteration(3):
389+
ret = yield from g2(v)
390+
trace.append("g2 returned %r" % (ret,))
390391
yield "g1 eggs"
391392
trace.append("Finishing g1")
392393
def g2(v = None):
@@ -410,7 +411,17 @@ def g2(v = None):
410411
"Yielded g2 spam",
411412
"Yielded g2 more spam",
412413
"Finishing g2",
413-
"g2 returned 42",
414+
"g2 returned 1",
415+
"Starting g2",
416+
"Yielded g2 spam",
417+
"Yielded g2 more spam",
418+
"Finishing g2",
419+
"g2 returned (2,)",
420+
"Starting g2",
421+
"Yielded g2 spam",
422+
"Yielded g2 more spam",
423+
"Finishing g2",
424+
"g2 returned StopIteration(3,)",
414425
"Yielded g1 eggs",
415426
"Finishing g1",
416427
])
@@ -670,23 +681,33 @@ def f(r):
670681
next(gi)
671682
trace.append("f SHOULD NOT BE HERE")
672683
except StopIteration as e:
673-
trace.append("f caught %s" % (repr(e),))
684+
trace.append("f caught %r" % (e,))
674685
def g(r):
675686
trace.append("g starting")
676687
yield
677-
trace.append("g returning %s" % (r,))
688+
trace.append("g returning %r" % (r,))
678689
return r
679690
f(None)
680-
f(42)
691+
f(1)
692+
f((2,))
693+
f(StopIteration(3))
681694
self.assertEqual(trace,[
682695
"g starting",
683696
"f resuming g",
684697
"g returning None",
685698
"f caught StopIteration()",
686699
"g starting",
687700
"f resuming g",
688-
"g returning 42",
689-
"f caught StopIteration(42,)",
701+
"g returning 1",
702+
"f caught StopIteration(1,)",
703+
"g starting",
704+
"f resuming g",
705+
"g returning (2,)",
706+
"f caught StopIteration((2,),)",
707+
"g starting",
708+
"f resuming g",
709+
"g returning StopIteration(3,)",
710+
"f caught StopIteration(StopIteration(3,),)",
690711
])
691712

692713
def test_send_and_return_with_value(self):
@@ -706,50 +727,64 @@ def f(r):
706727
def g(r):
707728
trace.append("g starting")
708729
x = yield
709-
trace.append("g received %s" % (x,))
710-
trace.append("g returning %s" % (r,))
730+
trace.append("g received %r" % (x,))
731+
trace.append("g returning %r" % (r,))
711732
return r
712733
f(None)
713-
f(42)
714-
self.assertEqual(trace,[
734+
f(1)
735+
f((2,))
736+
f(StopIteration(3))
737+
self.assertEqual(trace, [
715738
"g starting",
716739
"f sending spam to g",
717-
"g received spam",
740+
"g received 'spam'",
718741
"g returning None",
719742
"f caught StopIteration()",
720743
"g starting",
721744
"f sending spam to g",
722-
"g received spam",
723-
"g returning 42",
724-
"f caught StopIteration(42,)",
745+
"g received 'spam'",
746+
"g returning 1",
747+
'f caught StopIteration(1,)',
748+
'g starting',
749+
'f sending spam to g',
750+
"g received 'spam'",
751+
'g returning (2,)',
752+
'f caught StopIteration((2,),)',
753+
'g starting',
754+
'f sending spam to g',
755+
"g received 'spam'",
756+
'g returning StopIteration(3,)',
757+
'f caught StopIteration(StopIteration(3,),)'
725758
])
726759

727760
def test_catching_exception_from_subgen_and_returning(self):
728761
"""
729762
Test catching an exception thrown into a
730763
subgenerator and returning a value
731764
"""
732-
trace = []
733765
def inner():
734766
try:
735767
yield 1
736768
except ValueError:
737769
trace.append("inner caught ValueError")
738-
return 2
770+
return value
739771

740772
def outer():
741773
v = yield from inner()
742-
trace.append("inner returned %r to outer" % v)
774+
trace.append("inner returned %r to outer" % (v,))
743775
yield v
744-
g = outer()
745-
trace.append(next(g))
746-
trace.append(g.throw(ValueError))
747-
self.assertEqual(trace,[
748-
1,
749-
"inner caught ValueError",
750-
"inner returned 2 to outer",
751-
2,
752-
])
776+
777+
for value in 2, (2,), StopIteration(2):
778+
trace = []
779+
g = outer()
780+
trace.append(next(g))
781+
trace.append(repr(g.throw(ValueError)))
782+
self.assertEqual(trace, [
783+
1,
784+
"inner caught ValueError",
785+
"inner returned %r to outer" % (value,),
786+
repr(value),
787+
])
753788

754789
def test_throwing_GeneratorExit_into_subgen_that_returns(self):
755790
"""

Modules/_asynciomodule.c

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -997,26 +997,12 @@ FutureIter_iternext(futureiterobject *it)
997997

998998
res = _asyncio_Future_result_impl(fut);
999999
if (res != NULL) {
1000-
/* The result of the Future is not an exception.
1001-
1002-
We construct an exception instance manually with
1003-
PyObject_CallFunctionObjArgs and pass it to PyErr_SetObject
1004-
(similarly to what genobject.c does).
1005-
1006-
We do this to handle a situation when "res" is a tuple, in which
1007-
case PyErr_SetObject would set the value of StopIteration to
1008-
the first element of the tuple.
1009-
1010-
(See PyErr_SetObject/_PyErr_CreateException code for details.)
1011-
*/
1012-
PyObject *e = PyObject_CallFunctionObjArgs(
1013-
PyExc_StopIteration, res, NULL);
1014-
Py_DECREF(res);
1015-
if (e == NULL) {
1000+
/* The result of the Future is not an exception. */
1001+
if (_PyGen_SetStopIterationValue(res) < 0) {
1002+
Py_DECREF(res);
10161003
return NULL;
10171004
}
1018-
PyErr_SetObject(PyExc_StopIteration, e);
1019-
Py_DECREF(e);
1005+
Py_DECREF(res);
10201006
}
10211007

10221008
it->future = NULL;

0 commit comments

Comments
 (0)