Skip to content

Commit 82fdc39

Browse files
committed
Fix crashes when trying to pickle CLR exceptions.
The "args" slot of BaseException was not filled, instead we provided the args through our __getattr__ implementation. This fails in BaseException_reduce which depends on "args" being not NULL. We fix this by explicitly setting the "args" slot on all CLR exception objects on creation. This also makes tp_repr obsolete.
1 parent 2b167af commit 82fdc39

File tree

3 files changed

+43
-29
lines changed

3 files changed

+43
-29
lines changed

src/runtime/clrobject.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ internal CLRObject(Object ob, IntPtr tp) : base()
3030
this.pyHandle = py;
3131
this.gcHandle = gc;
3232
inst = ob;
33+
34+
// Fix the BaseException args slot if wrapping a CLR exception
35+
Exceptions.SetArgs(py);
3336
}
3437

3538

src/runtime/exceptions.cs

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,6 @@ internal static Exception ToException(IntPtr ob)
6262
return Runtime.PyUnicode_FromString(message);
6363
}
6464

65-
//====================================================================
66-
// Exception __repr__ implementation.
67-
//====================================================================
68-
69-
public static IntPtr tp_repr(IntPtr ob)
70-
{
71-
Exception e = ToException(ob);
72-
if (e == null)
73-
{
74-
return Exceptions.RaiseTypeError("invalid object");
75-
}
76-
string name = e.GetType().Name;
77-
string message;
78-
if (e.Message != String.Empty)
79-
{
80-
message = String.Format("{0}('{1}',)", name, e.Message);
81-
}
82-
else
83-
{
84-
message = String.Format("{0}()", name);
85-
}
86-
return Runtime.PyUnicode_FromString(message);
87-
}
88-
8965
//====================================================================
9066
// Exceptions __getattribute__ implementation.
9167
// handles Python's args and message attributes
@@ -198,6 +174,36 @@ internal static void Shutdown()
198174
}
199175
}
200176

177+
/// <summary>
178+
/// Set the 'args' slot on a python exception object that wraps
179+
/// a CLR exception. This is needed for pickling CLR exceptions as
180+
/// BaseException_reduce will only check the slots, bypassing the
181+
/// __getattr__ implementation, and thus dereferencing a NULL
182+
/// pointer.
183+
/// </summary>
184+
/// <param name="e">A CLR exception</param>
185+
/// <param name="ob">The python object wrapping </param>
186+
internal static void SetArgs(IntPtr ob)
187+
{
188+
var e = ExceptionClassObject.ToException(ob);
189+
if (e == null)
190+
return;
191+
192+
IntPtr args;
193+
if (e.Message != String.Empty)
194+
{
195+
args = Runtime.PyTuple_New(1);
196+
IntPtr msg = Runtime.PyUnicode_FromString(e.Message);
197+
Runtime.PyTuple_SetItem(args, 0, msg);
198+
}
199+
else
200+
{
201+
args = Runtime.PyTuple_New(0);
202+
}
203+
204+
Marshal.WriteIntPtr(ob, ExceptionOffset.args, args);
205+
}
206+
201207
/// <summary>
202208
/// Shortcut for (pointer == NULL) -> throw PythonException
203209
/// </summary>

src/tests/test_exceptions.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
unicode = str
77

88

9-
# Note: all of these tests are known to fail because Python currently
10-
# doesn't allow new-style classes to be used as exceptions. I'm leaving
11-
# the tests in place in to document 'how it ought to work' in the hopes
12-
# that they'll all pass one day...
13-
149
class ExceptionTests(unittest.TestCase):
1510
"""Test exception support."""
1611

@@ -336,6 +331,16 @@ def testExceptionIsInstanceOfSystemObject(self):
336331
else:
337332
self.assertFalse(isinstance(o, Object))
338333

334+
def testPicklingExceptions(self):
335+
from System import Exception
336+
import pickle
337+
338+
exc = Exception("test")
339+
dumped = pickle.dumps(exc)
340+
loaded = pickle.loads(dumped)
341+
342+
self.assertEqual(repr(exc), repr(loaded))
343+
339344

340345
def test_suite():
341346
return unittest.makeSuite(ExceptionTests)

0 commit comments

Comments
 (0)