Skip to content

Commit a0d1a3d

Browse files
slidefilmor
andauthored
Call PyErr_NormalizeException for exceptions (#1265)
* Call PyErr_NormalizeException for exceptions See https://docs.python.org/3/c-api/exceptions.html#c.PyErr_NormalizeException This fixes cases where some exceptions are "unnormalized" (see https://docs.python.org/3/c-api/exceptions.html#c.PyErr_NormalizeException). Calling PyErr_NormalizeException will normalize the exception so that any calls to the traceback module functions correctly. Fixes #1190 Co-authored-by: Benedikt Reinartz <filmor@gmail.com>
1 parent d9e15a7 commit a0d1a3d

File tree

4 files changed

+60
-8
lines changed

4 files changed

+60
-8
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ details about the cause of the failure
4040
- Indexers can now be used with interface objects
4141
- Fixed a bug where indexers could not be used if they were inherited
4242
- Made it possible to use `__len__` also on `ICollection<>` interface objects
43+
- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions
4344
- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects
4445
- Fixed objects returned by enumerating `PyObject` being disposed too soon
4546
- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException

src/embed_tests/TestPythonException.cs

+46-1
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,54 @@ public void TestPythonExceptionFormatNoTraceback()
8686
}
8787
catch (PythonException ex)
8888
{
89-
// ImportError/ModuleNotFoundError do not have a traceback when not running in a script
89+
// ImportError/ModuleNotFoundError do not have a traceback when not running in a script
9090
Assert.AreEqual(ex.StackTrace, ex.Format());
9191
}
9292
}
93+
94+
[Test]
95+
public void TestPythonExceptionFormatNormalized()
96+
{
97+
try
98+
{
99+
PythonEngine.Exec("a=b\n");
100+
}
101+
catch (PythonException ex)
102+
{
103+
Assert.AreEqual("Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\nNameError: name 'b' is not defined\n", ex.Format());
104+
}
105+
}
106+
107+
[Test]
108+
public void TestPythonException_PyErr_NormalizeException()
109+
{
110+
using (var scope = Py.CreateScope())
111+
{
112+
scope.Exec(@"
113+
class TestException(NameError):
114+
def __init__(self, val):
115+
super().__init__(val)
116+
x = int(val)");
117+
Assert.IsTrue(scope.TryGet("TestException", out PyObject type));
118+
119+
PyObject str = "dummy string".ToPython();
120+
IntPtr typePtr = type.Handle;
121+
IntPtr strPtr = str.Handle;
122+
IntPtr tbPtr = Runtime.Runtime.None.Handle;
123+
Runtime.Runtime.XIncref(typePtr);
124+
Runtime.Runtime.XIncref(strPtr);
125+
Runtime.Runtime.XIncref(tbPtr);
126+
Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr);
127+
128+
using (PyObject typeObj = new PyObject(typePtr), strObj = new PyObject(strPtr), tbObj = new PyObject(tbPtr))
129+
{
130+
// the type returned from PyErr_NormalizeException should not be the same type since a new
131+
// exception was raised by initializing the exception
132+
Assert.AreNotEqual(type.Handle, typePtr);
133+
// the message should now be the string from the throw exception during normalization
134+
Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString());
135+
}
136+
}
137+
}
93138
}
94139
}

src/runtime/pythonexception.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,18 @@ public string Format()
160160
{
161161
if (_pyTB != IntPtr.Zero && _pyType != IntPtr.Zero && _pyValue != IntPtr.Zero)
162162
{
163-
Runtime.XIncref(_pyType);
164-
Runtime.XIncref(_pyValue);
165-
Runtime.XIncref(_pyTB);
166-
using (PyObject pyType = new PyObject(_pyType))
167-
using (PyObject pyValue = new PyObject(_pyValue))
168-
using (PyObject pyTB = new PyObject(_pyTB))
163+
IntPtr tb = _pyTB;
164+
IntPtr type = _pyType;
165+
IntPtr value = _pyValue;
166+
167+
Runtime.XIncref(type);
168+
Runtime.XIncref(value);
169+
Runtime.XIncref(tb);
170+
Runtime.PyErr_NormalizeException(ref type, ref value, ref tb);
171+
172+
using (PyObject pyType = new PyObject(type))
173+
using (PyObject pyValue = new PyObject(value))
174+
using (PyObject pyTB = new PyObject(tb))
169175
using (PyObject tb_mod = PythonEngine.ImportModule("traceback"))
170176
{
171177
var buffer = new StringBuilder();

src/runtime/runtime.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2035,7 +2035,7 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size)
20352035
internal static extern int PyErr_GivenExceptionMatches(IntPtr ob, IntPtr val);
20362036

20372037
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
2038-
internal static extern void PyErr_NormalizeException(IntPtr ob, IntPtr val, IntPtr tb);
2038+
internal static extern void PyErr_NormalizeException(ref IntPtr ob, ref IntPtr val, ref IntPtr tb);
20392039

20402040
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
20412041
internal static extern IntPtr PyErr_Occurred();

0 commit comments

Comments
 (0)