From d0f34e928d482badd8f9df9d1061c17d253071fe Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Wed, 9 Aug 2017 11:45:07 -0400 Subject: [PATCH 1/6] crashing exception test (cherry picked from commit 31a4482) --- src/testing/exceptiontest.cs | 9 +++++++++ src/tests/test_exceptions.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/testing/exceptiontest.cs b/src/testing/exceptiontest.cs index e4f683721..6ae41c4e1 100644 --- a/src/testing/exceptiontest.cs +++ b/src/testing/exceptiontest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; namespace Python.Test { @@ -54,6 +56,13 @@ public static bool ThrowException() throw new OverflowException("error"); } + public static IEnumerable ThrowExceptionInIterator() + { + yield return 1; + yield return 2; + throw new OverflowException("error"); + } + public static void ThrowChainedExceptions() { try diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index f47209f6d..c816701c4 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -345,3 +345,17 @@ def test_chained_exceptions(): assert exc.Message == msg assert exc.__cause__ == exc.InnerException exc = exc.__cause__ + +def test_iteration_exception(): + from Python.Test import ExceptionTest + from System import OverflowException + + val = ExceptionTest.ThrowExceptionInIterator().__iter__() + assert next(val) == 1 + assert next(val) == 2 + with pytest.raises(OverflowException) as cm: + next(val) + + exc = cm.value + + assert isinstance(exc, OverflowException) From 6b03c63323ca608664862cfc3b6761667ed251ac Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Wed, 9 Aug 2017 16:03:32 -0400 Subject: [PATCH 2/6] Catch exceptions from iterator.MoveNext() (cherry picked from commit 8531755) --- src/runtime/iterator.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/runtime/iterator.cs b/src/runtime/iterator.cs index c7c60ab19..f9cf10178 100644 --- a/src/runtime/iterator.cs +++ b/src/runtime/iterator.cs @@ -23,9 +23,21 @@ public Iterator(IEnumerator e) public static IntPtr tp_iternext(IntPtr ob) { var self = GetManagedObject(ob) as Iterator; - if (!self.iter.MoveNext()) + try { - Exceptions.SetError(Exceptions.StopIteration, Runtime.PyNone); + if (!self.iter.MoveNext()) + { + Exceptions.SetError(Exceptions.StopIteration, Runtime.PyNone); + return IntPtr.Zero; + } + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); return IntPtr.Zero; } object item = self.iter.Current; From 9aac6098f0119f2019778b935013997901186a1c Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Wed, 9 Aug 2017 16:57:50 -0400 Subject: [PATCH 3/6] Updated changelog (cherry picked from commit 9e08150) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c30b4c393..57aee39c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added `clr.GetClrType` (#432, #433) - Allowed passing `None` for nullable args (#460) - Added keyword arguments based on C# syntax for calling CPython methods (#461) +- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python (#475) - Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger (#443) ### Changed @@ -29,7 +30,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed conversion of 'float' and 'double' values (#486) - Fixed 'clrmethod' for python 2 (#492) - Fixed double calling of constructor when deriving from .NET class (#495) -- Fixed `clr.GetClrType` when iterating over `System` members (#607) +- Fixed `clr.GetClrType` when iterating over `System` members (#607) - Fixed `LockRecursionException` when loading assemblies (#627) - Fixed errors breaking .NET Remoting on method invoke (#276) - Fixed PyObject.GetHashCode (#676) From d119c6c2c278eae26f8ebf13cf082524510c48c3 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Wed, 9 Aug 2017 17:41:28 -0400 Subject: [PATCH 4/6] Add test throwing exception with inner exception in iterator (cherry picked from commit 4956b3e) --- src/tests/test_exceptions.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index c816701c4..74ed96f0d 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -350,12 +350,30 @@ def test_iteration_exception(): from Python.Test import ExceptionTest from System import OverflowException - val = ExceptionTest.ThrowExceptionInIterator().__iter__() + exception = OverflowException("error") + + val = ExceptionTest.ThrowExceptionInIterator(exception).__iter__() + assert next(val) == 1 + assert next(val) == 2 + with pytest.raises(OverflowException) as cm: + next(val) + + exc = cm.value + + assert exc == exception + +def test_iteration_innerexception(): + from Python.Test import ExceptionTest + from System import OverflowException + + exception = System.Exception("message", OverflowException("error")) + + val = ExceptionTest.ThrowExceptionInIterator(exception).__iter__() assert next(val) == 1 assert next(val) == 2 with pytest.raises(OverflowException) as cm: next(val) - exc = cm.value + exc = cm.value - assert isinstance(exc, OverflowException) + assert exc == exception.InnerException From a1bb354d984bfb14ddf1e1e4b9f0aec1a9392fad Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Wed, 9 Aug 2017 17:45:35 -0400 Subject: [PATCH 5/6] Add exception argument to test method (cherry picked from commit 470773f) --- src/testing/exceptiontest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/testing/exceptiontest.cs b/src/testing/exceptiontest.cs index 6ae41c4e1..45acaa2cc 100644 --- a/src/testing/exceptiontest.cs +++ b/src/testing/exceptiontest.cs @@ -56,11 +56,11 @@ public static bool ThrowException() throw new OverflowException("error"); } - public static IEnumerable ThrowExceptionInIterator() + public static IEnumerable ThrowExceptionInIterator(Exception e) { yield return 1; yield return 2; - throw new OverflowException("error"); + throw e; } public static void ThrowChainedExceptions() From 2131292d3ab277e89a3065ee9d488d7dd8f7364a Mon Sep 17 00:00:00 2001 From: abessen Date: Mon, 25 Jun 2018 16:32:22 -0400 Subject: [PATCH 6/6] Test that after exception is thrown iterator is no longer valid --- src/tests/test_exceptions.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index 74ed96f0d..c697290ee 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -362,6 +362,11 @@ def test_iteration_exception(): assert exc == exception + # after exception is thrown iterator is no longer valid + with pytest.raises(StopIteration): + next(val) + + def test_iteration_innerexception(): from Python.Test import ExceptionTest from System import OverflowException @@ -377,3 +382,7 @@ def test_iteration_innerexception(): exc = cm.value assert exc == exception.InnerException + + # after exception is thrown iterator is no longer valid + with pytest.raises(StopIteration): + next(val)