From 84197d487839781c83139ed4b5347e5d317ae8fe Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 15 Oct 2020 15:14:13 -0700 Subject: [PATCH 1/8] Call PyErr_NormalizeException for exceptions See https://docs.python.org/3/c-api/exceptions.html#c.PyErr_NormalizeException --- src/runtime/pythonexception.cs | 1 + src/runtime/runtime.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 893bd9491..162ea6876 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -23,6 +23,7 @@ public PythonException() { IntPtr gs = PythonEngine.AcquireLock(); Runtime.PyErr_Fetch(out _pyType, out _pyValue, out _pyTB); + Runtime.PyErr_NormalizeException(ref _pyType, ref _pyValue, ref _pyTB); if (_pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) { string type; diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 915e1db00..0cb8bda12 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -2041,7 +2041,7 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) internal static extern int PyErr_GivenExceptionMatches(IntPtr ob, IntPtr val); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern void PyErr_NormalizeException(IntPtr ob, IntPtr val, IntPtr tb); + internal static extern void PyErr_NormalizeException(ref IntPtr ob, ref IntPtr val, ref IntPtr tb); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyErr_Occurred(); From eddd65788ae624ead5b40204bd4e722f76c14d6f Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 15 Oct 2020 15:19:10 -0700 Subject: [PATCH 2/8] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d27c136a3..4b32728ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ details about the cause of the failure - Indexers can now be used with interface objects - Fixed a bug where indexers could not be used if they were inherited - Made it possible to use `__len__` also on `ICollection<>` interface objects +- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions ## [2.5.0][] - 2020-06-14 From a923e998b5626607217a0ba67d6a73c13512289c Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 15 Oct 2020 15:27:31 -0700 Subject: [PATCH 3/8] Add test --- src/embed_tests/TestPythonException.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 000c32ca3..566501f4a 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -90,5 +90,18 @@ public void TestPythonExceptionFormatNoTraceback() Assert.AreEqual(ex.StackTrace, ex.Format()); } } + + [Test] + public void TestPythonExceptionFormatNormalized() + { + try + { + PythonEngine.Exec("a=b\n"); + } + catch (PythonException ex) + { + Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 1, in \nNameError: name 'b' is not defined\n", ex.Format()); + } + } } } From 9b3d14b9996d72717e11db1d960c4fc3b51a5445 Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 15 Oct 2020 16:02:25 -0700 Subject: [PATCH 4/8] Localize call to PyErr_NormalizeException to the Format method. --- src/runtime/pythonexception.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 162ea6876..fe65efcf8 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -23,7 +23,6 @@ public PythonException() { IntPtr gs = PythonEngine.AcquireLock(); Runtime.PyErr_Fetch(out _pyType, out _pyValue, out _pyTB); - Runtime.PyErr_NormalizeException(ref _pyType, ref _pyValue, ref _pyTB); if (_pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) { string type; @@ -159,12 +158,17 @@ public string Format() { if (_pyTB != IntPtr.Zero && _pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) { - Runtime.XIncref(_pyType); - Runtime.XIncref(_pyValue); - Runtime.XIncref(_pyTB); - using (PyObject pyType = new PyObject(_pyType)) - using (PyObject pyValue = new PyObject(_pyValue)) - using (PyObject pyTB = new PyObject(_pyTB)) + IntPtr tb = _pyTB; + IntPtr type = _pyType; + IntPtr value = _pyValue; + Runtime.PyErr_NormalizeException(ref type, ref value, ref tb); + + Runtime.XIncref(type); + Runtime.XIncref(value); + Runtime.XIncref(tb); + using (PyObject pyType = new PyObject(type)) + using (PyObject pyValue = new PyObject(value)) + using (PyObject pyTB = new PyObject(tb)) using (PyObject tb_mod = PythonEngine.ImportModule("traceback")) { var buffer = new StringBuilder(); From 092e961bc49fd0ce7a1f5f4394fca70374d9beef Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 12 Nov 2020 10:07:16 -0700 Subject: [PATCH 5/8] incref before the call to PyErr_NormalizeException --- src/runtime/pythonexception.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index df594d523..74601dfe6 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -161,11 +161,12 @@ public string Format() IntPtr tb = _pyTB; IntPtr type = _pyType; IntPtr value = _pyValue; - Runtime.PyErr_NormalizeException(ref type, ref value, ref tb); Runtime.XIncref(type); Runtime.XIncref(value); Runtime.XIncref(tb); + Runtime.PyErr_NormalizeException(ref type, ref value, ref tb); + using (PyObject pyType = new PyObject(type)) using (PyObject pyValue = new PyObject(value)) using (PyObject pyTB = new PyObject(tb)) From caa09225c1bf029db2d66922ced2b23a0576bbbb Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Thu, 10 Dec 2020 17:58:52 -0700 Subject: [PATCH 6/8] Add test for __init__ that causes exception --- src/embed_tests/TestPythonException.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 566501f4a..19d944367 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -86,7 +86,7 @@ public void TestPythonExceptionFormatNoTraceback() } catch (PythonException ex) { - // ImportError/ModuleNotFoundError do not have a traceback when not running in a script + // ImportError/ModuleNotFoundError do not have a traceback when not running in a script Assert.AreEqual(ex.StackTrace, ex.Format()); } } @@ -103,5 +103,25 @@ public void TestPythonExceptionFormatNormalized() Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 1, in \nNameError: name 'b' is not defined\n", ex.Format()); } } + + [Test] + public void TestPythonExceptionFormatNormalizedWithExceptionIn__init__() + { + try + { + PythonEngine.Exec(@" +class TestException(NameError): + def __init__(self, val): + super().__init__(val) + x = int(val) + +raise TestException('foo') +"); + } + catch(PythonException ex) + { + Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 7, in \n File \"\", line 5, in __init__\nValueError: invalid literal for int() with base 10: 'foo'\n", ex.Format()); + } + } } } From c28951f086dbc845a4b1b498b4bae52aa2ceef90 Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Mon, 14 Dec 2020 16:32:53 -0700 Subject: [PATCH 7/8] Fix test for exception during normalization --- src/embed_tests/TestPythonException.cs | 38 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 19d944367..e74b5a9e1 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -105,22 +105,40 @@ public void TestPythonExceptionFormatNormalized() } [Test] - public void TestPythonExceptionFormatNormalizedWithExceptionIn__init__() + public void TestPythonException_PyErr_NormalizeException() { - try + using (Py.GIL()) { - PythonEngine.Exec(@" + using (PythonEngine engine = new PythonEngine()) + { + var scope = Py.CreateScope(); + scope.Exec(@" class TestException(NameError): def __init__(self, val): super().__init__(val) - x = int(val) + x = int(val)"); -raise TestException('foo') -"); - } - catch(PythonException ex) - { - Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 7, in \n File \"\", line 5, in __init__\nValueError: invalid literal for int() with base 10: 'foo'\n", ex.Format()); + if (scope.TryGet("TestException", out PyObject type)) + { + PyObject str = "dummy string".ToPython(); + IntPtr typePtr = type.Handle; + IntPtr strPtr = str.Handle; + IntPtr tbPtr = Runtime.Runtime.None.Handle; + Runtime.Runtime.XIncref(typePtr); + Runtime.Runtime.XIncref(strPtr); + Runtime.Runtime.XIncref(tbPtr); + Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr); + + using(PyObject typeObj = new PyObject(typePtr), strObj = new PyObject(strPtr), tbObj = new PyObject(tbPtr)) + { + // the type returned from PyErr_NormalizeException should not be the same type since a new + // exception was raised by initializing the exception + Assert.AreNotEqual(type.Handle, typePtr); + // the message should now be the string from the throw exception during normalization + Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); + } + } + } } } } From 4ac9c48d3442b8fe63ed885340c79c2d05811e9e Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Sat, 19 Dec 2020 07:49:20 -0700 Subject: [PATCH 8/8] Fixup test --- src/embed_tests/TestPythonException.cs | 42 +++++++++++--------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index e74b5a9e1..3ab0f8106 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -107,37 +107,31 @@ public void TestPythonExceptionFormatNormalized() [Test] public void TestPythonException_PyErr_NormalizeException() { - using (Py.GIL()) + using (var scope = Py.CreateScope()) { - using (PythonEngine engine = new PythonEngine()) - { - var scope = Py.CreateScope(); - scope.Exec(@" + scope.Exec(@" class TestException(NameError): def __init__(self, val): super().__init__(val) x = int(val)"); + Assert.IsTrue(scope.TryGet("TestException", out PyObject type)); - if (scope.TryGet("TestException", out PyObject type)) - { - PyObject str = "dummy string".ToPython(); - IntPtr typePtr = type.Handle; - IntPtr strPtr = str.Handle; - IntPtr tbPtr = Runtime.Runtime.None.Handle; - Runtime.Runtime.XIncref(typePtr); - Runtime.Runtime.XIncref(strPtr); - Runtime.Runtime.XIncref(tbPtr); - Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr); + PyObject str = "dummy string".ToPython(); + IntPtr typePtr = type.Handle; + IntPtr strPtr = str.Handle; + IntPtr tbPtr = Runtime.Runtime.None.Handle; + Runtime.Runtime.XIncref(typePtr); + Runtime.Runtime.XIncref(strPtr); + Runtime.Runtime.XIncref(tbPtr); + Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr); - using(PyObject typeObj = new PyObject(typePtr), strObj = new PyObject(strPtr), tbObj = new PyObject(tbPtr)) - { - // the type returned from PyErr_NormalizeException should not be the same type since a new - // exception was raised by initializing the exception - Assert.AreNotEqual(type.Handle, typePtr); - // the message should now be the string from the throw exception during normalization - Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); - } - } + using (PyObject typeObj = new PyObject(typePtr), strObj = new PyObject(strPtr), tbObj = new PyObject(tbPtr)) + { + // the type returned from PyErr_NormalizeException should not be the same type since a new + // exception was raised by initializing the exception + Assert.AreNotEqual(type.Handle, typePtr); + // the message should now be the string from the throw exception during normalization + Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); } } }