diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38782dfb4..b5a0080a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,9 @@ jobs: run: | python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Embedding tests + run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ + - name: Python Tests (Mono) if: ${{ matrix.os != 'windows' }} run: pytest --runtime mono @@ -67,9 +70,6 @@ jobs: if: ${{ matrix.os == 'windows' }} run: pytest --runtime netfx - - name: Embedding tests - run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ - - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b565fbbdf..5871e7ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Add GetPythonThreadID and Interrupt methods in PythonEngine - Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) - `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` +- Improved exception handling: + - exceptions can now be converted with codecs + - `InnerException` and `__cause__` are propagated properly ### Changed - Drop support for Python 2, 3.4, and 3.5 @@ -44,7 +47,9 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru - Sign Runtime DLL with a strong name - Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core +- .NET and Python exceptions are preserved when crossing Python/.NET boundary - BREAKING: custom encoders are no longer called for instances of `System.Type` +- `PythonException.Restore` no longer clears `PythonException` instance. ### Fixed @@ -70,6 +75,7 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru ### Removed - implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) +- messages in `PythonException` no longer start with exception type - support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 (see [the matrix](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support)) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index de5882b32..f0c00a6d8 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -322,6 +322,65 @@ def CanEncode(self, clr_type): PythonEngine.Exec(PyCode); } + + const string TestExceptionMessage = "Hello World!"; + [Test] + public void ExceptionEncoded() + { + PyObjectConversions.RegisterEncoder(new ValueErrorCodec()); + void CallMe() => throw new ValueErrorWrapper(TestExceptionMessage); + var callMeAction = new Action(CallMe); + using var _ = Py.GIL(); + using var scope = Py.CreateScope(); + scope.Exec(@" +def call(func): + try: + func() + except ValueError as e: + return str(e) +"); + var callFunc = scope.Get("call"); + string message = callFunc.Invoke(callMeAction.ToPython()).As(); + Assert.AreEqual(TestExceptionMessage, message); + } + + [Test] + public void ExceptionDecoded() + { + PyObjectConversions.RegisterDecoder(new ValueErrorCodec()); + using var _ = Py.GIL(); + using var scope = Py.CreateScope(); + var error = Assert.Throws(() + => PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')")); + Assert.AreEqual(TestExceptionMessage, error.Message); + } + + class ValueErrorWrapper : Exception + { + public ValueErrorWrapper(string message) : base(message) { } + } + + class ValueErrorCodec : IPyObjectEncoder, IPyObjectDecoder + { + public bool CanDecode(PyObject objectType, Type targetType) + => this.CanEncode(targetType) && objectType.Equals(PythonEngine.Eval("ValueError")); + + public bool CanEncode(Type type) => type == typeof(ValueErrorWrapper) + || typeof(ValueErrorWrapper).IsSubclassOf(type); + + public bool TryDecode(PyObject pyObj, out T value) + { + var message = pyObj.GetAttr("args")[0].As(); + value = (T)(object)new ValueErrorWrapper(message); + return true; + } + + public PyObject TryEncode(object value) + { + var error = (ValueErrorWrapper)value; + return PythonEngine.Eval("ValueError").Invoke(error.Message.ToPython()); + } + } } /// diff --git a/src/embed_tests/TestCallbacks.cs b/src/embed_tests/TestCallbacks.cs index 454c97578..6875fde01 100644 --- a/src/embed_tests/TestCallbacks.cs +++ b/src/embed_tests/TestCallbacks.cs @@ -24,7 +24,7 @@ public void TestNoOverloadException() { using (Py.GIL()) { dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])"); var error = Assert.Throws(() => callWith42(aFunctionThatCallsIntoPython.ToPython())); - Assert.AreEqual("TypeError", error.PythonTypeName); + Assert.AreEqual("TypeError", error.Type.Name); string expectedArgTypes = "()"; StringAssert.EndsWith(expectedArgTypes, error.Message); } diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 94e7026c7..906c8cb0d 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -95,7 +95,7 @@ public void StringBadCtor() var ex = Assert.Throws(() => a = new PyFloat(i)); - StringAssert.StartsWith("ValueError : could not convert string to float", ex.Message); + StringAssert.StartsWith("could not convert string to float", ex.Message); Assert.IsNull(a); } @@ -132,7 +132,7 @@ public void AsFloatBad() PyFloat a = null; var ex = Assert.Throws(() => a = PyFloat.AsFloat(s)); - StringAssert.StartsWith("ValueError : could not convert string to float", ex.Message); + StringAssert.StartsWith("could not convert string to float", ex.Message); Assert.IsNull(a); } } diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 005ab466d..bd6cf23a1 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -128,7 +128,7 @@ public void TestCtorBadString() var ex = Assert.Throws(() => a = new PyInt(i)); - StringAssert.StartsWith("ValueError : invalid literal for int", ex.Message); + StringAssert.StartsWith("invalid literal for int", ex.Message); Assert.IsNull(a); } @@ -161,7 +161,7 @@ public void TestAsIntBad() PyInt a = null; var ex = Assert.Throws(() => a = PyInt.AsInt(s)); - StringAssert.StartsWith("ValueError : invalid literal for int", ex.Message); + StringAssert.StartsWith("invalid literal for int", ex.Message); Assert.IsNull(a); } diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index e9acfbb45..eee129f2d 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -41,7 +41,7 @@ public void TestStringAsListType() var ex = Assert.Throws(() => t = PyList.AsList(i)); - Assert.AreEqual("TypeError : 'int' object is not iterable", ex.Message); + Assert.AreEqual("'int' object is not iterable", ex.Message); Assert.IsNull(t); } diff --git a/src/embed_tests/TestPyLong.cs b/src/embed_tests/TestPyLong.cs index 3c155f315..6d587d064 100644 --- a/src/embed_tests/TestPyLong.cs +++ b/src/embed_tests/TestPyLong.cs @@ -144,7 +144,7 @@ public void TestCtorBadString() var ex = Assert.Throws(() => a = new PyLong(i)); - StringAssert.StartsWith("ValueError : invalid literal", ex.Message); + StringAssert.StartsWith("invalid literal", ex.Message); Assert.IsNull(a); } @@ -177,7 +177,7 @@ public void TestAsLongBad() PyLong a = null; var ex = Assert.Throws(() => a = PyLong.AsLong(s)); - StringAssert.StartsWith("ValueError : invalid literal", ex.Message); + StringAssert.StartsWith("invalid literal", ex.Message); Assert.IsNull(a); } diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 362251049..5d76116aa 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -104,7 +104,7 @@ public void TestPyTupleInvalidAppend() var ex = Assert.Throws(() => t.Concat(s)); - StringAssert.StartsWith("TypeError : can only concatenate tuple", ex.Message); + StringAssert.StartsWith("can only concatenate tuple", ex.Message); Assert.AreEqual(0, t.Length()); Assert.IsEmpty(t); } @@ -164,7 +164,7 @@ public void TestInvalidAsTuple() var ex = Assert.Throws(() => t = PyTuple.AsTuple(i)); - Assert.AreEqual("TypeError : 'int' object is not iterable", ex.Message); + Assert.AreEqual("'int' object is not iterable", ex.Message); Assert.IsNull(t); } } diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index f70a54c99..a28fe00da 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -40,6 +40,7 @@ public void CanCreateHeapType() using var type = new PyType(spec); Assert.AreEqual(name, type.GetAttr("__name__").As()); + Assert.AreEqual(name, type.Name); Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); } } diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs index dcd539504..c6228f1b9 100644 --- a/src/embed_tests/TestPyWith.cs +++ b/src/embed_tests/TestPyWith.cs @@ -51,7 +51,7 @@ def fail(self): catch (PythonException e) { TestContext.Out.WriteLine(e.Message); - Assert.IsTrue(e.Message.Contains("ZeroDivisionError")); + Assert.IsTrue(e.Type.Name == "ZeroDivisionError"); } } diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a74fc3a8b..0763bfb34 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -30,31 +30,61 @@ public void TestMessage() var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("IndexError : list index out of range", ex.Message); + Assert.AreEqual("list index out of range", ex.Message); + Assert.IsNull(foo); + } + + [Test] + public void TestType() + { + var list = new PyList(); + PyObject foo = null; + + var ex = Assert.Throws(() => foo = list[0]); + + Assert.AreEqual("IndexError", ex.Type.Name); Assert.IsNull(foo); } [Test] public void TestNoError() { - var e = new PythonException(); // There is no PyErr to fetch - Assert.AreEqual("", e.Message); + // There is no PyErr to fetch + Assert.Throws(() => PythonException.FetchCurrentRaw()); + var currentError = PythonException.FetchCurrentOrNullRaw(); + Assert.IsNull(currentError); } [Test] - public void TestPythonErrorTypeName() + public void TestNestedExceptions() { try { - var module = PyModule.Import("really____unknown___module"); - Assert.Fail("Unknown module should not be loaded"); + PythonEngine.Exec(@" +try: + raise Exception('inner') +except Exception as ex: + raise Exception('outer') from ex +"); } catch (PythonException ex) { - Assert.That(ex.PythonTypeName, Is.EqualTo("ModuleNotFoundError").Or.EqualTo("ImportError")); + Assert.That(ex.InnerException, Is.InstanceOf()); + Assert.That(ex.InnerException.Message, Is.EqualTo("inner")); } } + [Test] + public void InnerIsEmptyWithNoCause() + { + var list = new PyList(); + PyObject foo = null; + + var ex = Assert.Throws(() => foo = list[0]); + + Assert.IsNull(ex.InnerException); + } + [Test] public void TestPythonExceptionFormat() { @@ -83,13 +113,6 @@ public void TestPythonExceptionFormat() } } - [Test] - public void TestPythonExceptionFormatNoError() - { - var ex = new PythonException(); - Assert.AreEqual(ex.StackTrace, ex.Format()); - } - [Test] public void TestPythonExceptionFormatNoTraceback() { @@ -132,22 +155,19 @@ def __init__(self, val): Assert.IsTrue(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); + var typePtr = new NewReference(type.Reference); + var strPtr = new NewReference(str.Reference); + var tbPtr = new NewReference(Runtime.Runtime.None.Reference); 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 var typeObj = typePtr.MoveToPyObject(); + using var strObj = strPtr.MoveToPyObject(); + using var tbObj = tbPtr.MoveToPyObject(); + // 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, typeObj.Handle); + // 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()); } } @@ -155,7 +175,7 @@ def __init__(self, val): public void TestPythonException_Normalize_ThrowsWhenErrorSet() { Exceptions.SetError(Exceptions.TypeError, "Error!"); - var pythonException = new PythonException(); + var pythonException = PythonException.FetchCurrentRaw(); Exceptions.SetError(Exceptions.TypeError, "Another error"); Assert.Throws(() => pythonException.Normalize()); } diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 4e05850c1..9ca6cf139 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using NUnit.Framework; using Python.Runtime; -using Python.Runtime.Platform; namespace Python.EmbeddingTest { @@ -102,7 +101,7 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() Exceptions.ErrorCheck(threadingDict); var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); if (lockType.IsNull) - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); using var args = NewReference.DangerousFromPointer(Runtime.Runtime.PyTuple_New(0)); using var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, args); diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index f1f667961..e98461cbb 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -102,8 +102,7 @@ import clr clr.AddReference('{path}') "; - var error = Assert.Throws(() => PythonEngine.Exec(code)); - Assert.AreEqual(nameof(FileLoadException), error.PythonTypeName); + Assert.Throws(() => PythonEngine.Exec(code)); } } } diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index 66a9a3f7c..1622f46d3 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -158,9 +158,10 @@ public static void TestRunExitFuncs() catch (PythonException e) { string msg = e.ToString(); + bool isImportError = e.Is(Exceptions.ImportError); Runtime.Runtime.Shutdown(); - if (e.IsMatches(Exceptions.ImportError)) + if (isImportError) { Assert.Ignore("no atexit module"); } diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs index d49f52fe9..bf8a91d3e 100644 --- a/src/runtime/BorrowedReference.cs +++ b/src/runtime/BorrowedReference.cs @@ -16,6 +16,8 @@ public IntPtr DangerousGetAddress() /// Gets a raw pointer to the Python object public IntPtr DangerousGetAddressOrNull() => this.pointer; + public static BorrowedReference Null => new BorrowedReference(); + /// /// Creates new instance of from raw pointer. Unsafe. /// diff --git a/src/runtime/ManagedTypes.cd b/src/runtime/ManagedTypes.cd new file mode 100644 index 000000000..385ae7117 --- /dev/null +++ b/src/runtime/ManagedTypes.cd @@ -0,0 +1,196 @@ + + + + + + FAAAAgAIAAAEDAAAAAAAAEACIACJAAIAAAAAAAIAAAQ= + classbase.cs + + + + + + AAAAAABIAAABDAAAIAIAAAAAAAAAAAAAAAAACAiAAAQ= + classderived.cs + + + + + + AAAAAAAAABAAAAAAAAAAACAAIAAJAAAAIAAAAACAAAI= + arrayobject.cs + + + + + + AAABAAAIAAAAAAAAIAAAAAAAAAAAAIAAACAAAACAAAA= + classobject.cs + + + + + + AAAACAAAAAAABABAAAAACAAAABAJAEAAAAAAAAIAAAA= + constructorbinding.cs + + + + + + EAAAAAAAAAAAAAAAAAACAAACBIAAAAJAAAAAAAAAAAA= + clrobject.cs + + + + + + AAAAAEAgIAQABAAAAABAAAAAIAIAAAAAAhAQAAAAKBA= + moduleobject.cs + + + + + + AAAACAAAAAAABAAAAAAACAAAABAJAEAAAAAAAAIAEAA= + constructorbinding.cs + + + + + + AAABAAAAAAAAAABAAAAAAEAAIAACAAAAAAAAAACAAAA= + delegateobject.cs + + + + + + + + + + + + + + AAAAAAAAAAAADAAAIAAAEABAAAAAAAACAAAAAAIAAAQ= + eventbinding.cs + + + + + + AAACAAAAAAAAAAAAAAIAAIAAAAAEAAAAQABAAAIBEAQ= + eventobject.cs + + + + + + AAAAAgAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAIAAAA= + exceptions.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAACAAAAAEEBAAAAAAABAAQ= + extensiontype.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAIBEAA= + fieldobject.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAggAAAAAAAEAACAACAAAA= + interfaceobject.cs + + + + + + UCBBgoBAIUgAAAEAACAAsAACAgAIABIAQYAAACIYIBA= + managedtype.cs + + + + + + AQAAAAAICBAAAQBAAAABAAIAAgABAAABAAAAUBCAAAQ= + metatype.cs + + + + + + + + + + + + + + EAAAAAAAAIAADABAIAAAAAAAAAgBAAAAUgAAAAIAAAQ= + methodbinding.cs + + + + + + FIADAAAAAAAIBAAAIAAIAAAIAAgFAAAAUAAgAAIAEAQ= + methodobject.cs + + + + + + AAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAAA= + modulefunctionobject.cs + + + + + + ECCCCkAAAAAABAAAAAABAAACAAAIAIIAEAAAAAIACAQ= + moduleobject.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + modulepropertyobject.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAQAAAAAIBEAg= + propertyobject.cs + + + + + + + + + + + + + + AAAAAAAAAAAAAAAAIAAAAAAAAAABAAAAAgAAAAIAAAQ= + overload.cs + + + + \ No newline at end of file diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index f19dfd04c..c037f988f 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -2,6 +2,7 @@ namespace Python.Runtime { using System; using System.Diagnostics.Contracts; + using System.Runtime.CompilerServices; /// /// Represents a reference to a Python object, that is tracked by Python's reference counting. @@ -56,6 +57,34 @@ public IntPtr DangerousMoveToPointerOrNull() return result; } + /// + /// Returns wrapper around this reference, which now owns + /// the pointer. Sets the original reference to null, as it no longer owns it. + /// + public PyObject MoveToPyObjectOrNull() => this.IsNull() ? null : this.MoveToPyObject(); + /// + /// Call this method to move ownership of this reference to a Python C API function, + /// that steals reference passed to it. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StolenReference StealNullable() + { + IntPtr rawPointer = this.pointer; + this.pointer = IntPtr.Zero; + return new StolenReference(rawPointer); + } + + /// + /// Call this method to move ownership of this reference to a Python C API function, + /// that steals reference passed to it. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StolenReference Steal() + { + if (this.IsNull()) throw new NullReferenceException(); + + return this.StealNullable(); + } /// /// Removes this reference to a Python object, and sets it to null. /// diff --git a/src/runtime/StolenReference.cs b/src/runtime/StolenReference.cs new file mode 100644 index 000000000..1130cff06 --- /dev/null +++ b/src/runtime/StolenReference.cs @@ -0,0 +1,46 @@ +namespace Python.Runtime +{ + using System; + using System.Diagnostics.Contracts; + + /// + /// Should only be used for the arguments of Python C API functions, that steal references, + /// and internal constructors. + /// + [NonCopyable] + readonly ref struct StolenReference + { + internal readonly IntPtr Pointer; + + internal StolenReference(IntPtr pointer) + { + Pointer = pointer; + } + + [Pure] + public static bool operator ==(in StolenReference reference, NullOnly @null) + => reference.Pointer == IntPtr.Zero; + [Pure] + public static bool operator !=(in StolenReference reference, NullOnly @null) + => reference.Pointer != IntPtr.Zero; + + [Pure] + public override bool Equals(object obj) + { + if (obj is IntPtr ptr) + return ptr == Pointer; + + return false; + } + + [Pure] + public override int GetHashCode() => Pointer.GetHashCode(); + } + + static class StolenReferenceExtensions + { + [Pure] + public static IntPtr DangerousGetAddressOrNull(this in StolenReference reference) + => reference.Pointer; + } +} diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index e3c5b2a3b..811b802c9 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -131,7 +131,7 @@ internal static void SaveRuntimeData(RuntimeDataStorage storage) } else if (Exceptions.ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } // We modified the Type object, notify it we did. diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 142fade25..02f7baf65 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -23,7 +23,7 @@ internal CLRObject(object ob, IntPtr tp) // Fix the BaseException args (and __cause__ in case of Python 3) // slot if wrapping a CLR exception - if (ob is Exception e) Exceptions.SetArgsAndCause(e, py); + if (ob is Exception e) Exceptions.SetArgsAndCause(ObjectReference, e); } internal CLRObject(object ob, BorrowedReference tp) : this(ob, tp.DangerousGetAddress()) { } diff --git a/src/runtime/constructorbinding.cs b/src/runtime/constructorbinding.cs index 6706c2b48..9ac1adc0f 100644 --- a/src/runtime/constructorbinding.cs +++ b/src/runtime/constructorbinding.cs @@ -149,21 +149,10 @@ public static IntPtr tp_repr(IntPtr ob) return self.repr; } - /// - /// ConstructorBinding dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) - { - var self = (ConstructorBinding)GetManagedObject(ob); - Runtime.XDecref(self.repr); - self.Dealloc(); - } - - public static int tp_clear(IntPtr ob) + protected override void Clear() { - var self = (ConstructorBinding)GetManagedObject(ob); - Runtime.Py_CLEAR(ref self.repr); - return 0; + Runtime.Py_CLEAR(ref this.repr); + base.Clear(); } public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg) @@ -252,21 +241,10 @@ public static IntPtr tp_repr(IntPtr ob) return self.repr; } - /// - /// ConstructorBinding dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) - { - var self = (BoundContructor)GetManagedObject(ob); - Runtime.XDecref(self.repr); - self.Dealloc(); - } - - public static int tp_clear(IntPtr ob) + protected override void Clear() { - var self = (BoundContructor)GetManagedObject(ob); - Runtime.Py_CLEAR(ref self.repr); - return 0; + Runtime.Py_CLEAR(ref this.repr); + base.Clear(); } public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg) diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 70b3d9eaa..47263e8c4 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -110,6 +110,9 @@ internal static IntPtr ToPython(T value) return ToPython(value, typeof(T)); } + internal static NewReference ToPythonReference(T value) + => NewReference.DangerousFromPointer(ToPython(value, typeof(T))); + private static readonly Func IsTransparentProxy = GetIsTransparentProxy(); private static bool Never(object _) => false; @@ -530,7 +533,7 @@ internal static int ToInt32(BorrowedReference value) nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return checked((int)num); } @@ -831,7 +834,7 @@ private static void SetConversionError(IntPtr value, Type target) string src = Runtime.GetManagedString(ob); Runtime.XDecref(ob); - Runtime.PyErr_Restore(causeType, causeVal, causeTrace); + Runtime.PyErr_Restore(causeType.StealNullable(), causeVal.StealNullable(), causeTrace.StealNullable()); Exceptions.RaiseTypeError($"Cannot convert {src} to {target}"); } @@ -900,6 +903,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s if (!Converter.ToManaged(item, elementType, out obj, setError)) { Runtime.XDecref(item); + Runtime.XDecref(IterObject); return false; } @@ -908,6 +912,12 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s } Runtime.XDecref(IterObject); + if (Exceptions.ErrorOccurred()) + { + if (!setError) Exceptions.Clear(); + return false; + } + Array items = Array.CreateInstance(elementType, list.Count); list.CopyTo(items, 0); diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs index b10d0c59f..5711b9f87 100644 --- a/src/runtime/converterextensions.cs +++ b/src/runtime/converterextensions.cs @@ -107,6 +107,8 @@ static IPyObjectEncoder[] GetEncoders(Type type) #region Decoding static readonly ConcurrentDictionary pythonToClr = new ConcurrentDictionary(); + internal static bool TryDecode(BorrowedReference value, BorrowedReference type, Type targetType, out object result) + => TryDecode(value.DangerousGetAddress(), type.DangerousGetAddress(), targetType, out result); internal static bool TryDecode(IntPtr pyHandle, IntPtr pyType, Type targetType, out object result) { if (pyHandle == IntPtr.Zero) throw new ArgumentNullException(nameof(pyHandle)); diff --git a/src/runtime/delegatemanager.cs b/src/runtime/delegatemanager.cs index 0a848904a..22f603400 100644 --- a/src/runtime/delegatemanager.cs +++ b/src/runtime/delegatemanager.cs @@ -296,8 +296,7 @@ private object TrueDispatch(object[] args) if (op == IntPtr.Zero) { - var e = new PythonException(); - throw e; + throw PythonException.ThrowLastAsClrException(); } try @@ -324,7 +323,7 @@ private object TrueDispatch(object[] args) if (!Converter.ToManaged(op, t, out object newArg, true)) { Exceptions.RaiseTypeError($"The Python function did not return {t.GetElementType()} (the out parameter type)"); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } args[i] = newArg; break; @@ -344,7 +343,7 @@ private object TrueDispatch(object[] args) if (!Converter.ToManaged(item, t, out object newArg, true)) { Exceptions.RaiseTypeError($"The Python function returned a tuple where element {i} was not {t.GetElementType()} (the out parameter type)"); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } args[i] = newArg; } @@ -357,7 +356,7 @@ private object TrueDispatch(object[] args) if (!Converter.ToManaged(item0, rtype, out object result0, true)) { Exceptions.RaiseTypeError($"The Python function returned a tuple where element 0 was not {rtype} (the return type)"); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return result0; } @@ -381,7 +380,7 @@ private object TrueDispatch(object[] args) } string returnValueString = isVoid ? "" : "the return value and "; Exceptions.RaiseTypeError($"Expected a tuple ({sb}) of {returnValueString}the values for out and ref parameters, got {tpName}."); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -393,7 +392,7 @@ private object TrueDispatch(object[] args) object result; if (!Converter.ToManaged(op, rtype, out result, true)) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return result; diff --git a/src/runtime/eventbinding.cs b/src/runtime/eventbinding.cs index 3f5b7b007..65c8fdccf 100644 --- a/src/runtime/eventbinding.cs +++ b/src/runtime/eventbinding.cs @@ -103,22 +103,10 @@ public static IntPtr tp_repr(IntPtr ob) return Runtime.PyString_FromString(s); } - - /// - /// EventBinding dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) - { - var self = (EventBinding)GetManagedObject(ob); - Runtime.XDecref(self.target); - self.Dealloc(); - } - - public static int tp_clear(IntPtr ob) + protected override void Clear() { - var self = (EventBinding)GetManagedObject(ob); - Runtime.Py_CLEAR(ref self.target); - return 0; + Runtime.Py_CLEAR(ref this.target); + base.Clear(); } } } diff --git a/src/runtime/eventobject.cs b/src/runtime/eventobject.cs index 4dc785ddd..941bbdf46 100644 --- a/src/runtime/eventobject.cs +++ b/src/runtime/eventobject.cs @@ -198,17 +198,14 @@ public static IntPtr tp_repr(IntPtr ob) } - /// - /// Descriptor dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) + protected override void Clear() { - var self = (EventObject)GetManagedObject(ob); - if (self.unbound != null) + if (this.unbound is not null) { - Runtime.XDecref(self.unbound.pyHandle); + Runtime.XDecref(this.unbound.pyHandle); + this.unbound = null; } - self.Dealloc(); + base.Clear(); } } diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index 163b0a11e..bbdcdad30 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -1,7 +1,8 @@ using System; +using System.Diagnostics; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; -using System.Text; namespace Python.Runtime { @@ -23,19 +24,10 @@ internal ExceptionClassObject(Type tp) : base(tp) { } - internal static Exception ToException(IntPtr ob) + internal static Exception ToException(BorrowedReference ob) { var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return null; - } - var e = co.inst as Exception; - if (e == null) - { - return null; - } - return e; + return co?.inst as Exception; } /// @@ -43,7 +35,7 @@ internal static Exception ToException(IntPtr ob) /// public new static IntPtr tp_repr(IntPtr ob) { - Exception e = ToException(ob); + Exception e = ToException(new BorrowedReference(ob)); if (e == null) { return Exceptions.RaiseTypeError("invalid object"); @@ -66,7 +58,7 @@ internal static Exception ToException(IntPtr ob) /// public new static IntPtr tp_str(IntPtr ob) { - Exception e = ToException(ob); + Exception e = ToException(new BorrowedReference(ob)); if (e == null) { return Exceptions.RaiseTypeError("invalid object"); @@ -93,7 +85,7 @@ internal static Exception ToException(IntPtr ob) /// /// Readability of the Exceptions class improvements as we look toward version 2.7 ... /// - public static class Exceptions + internal static class Exceptions { internal static PyModule warnings_module; internal static PyModule exceptions_module; @@ -155,8 +147,7 @@ internal static void Shutdown() /// __getattr__ implementation, and thus dereferencing a NULL /// pointer. /// - /// The python object wrapping - internal static void SetArgsAndCause(Exception e, IntPtr ob) + internal static void SetArgsAndCause(BorrowedReference ob, Exception e) { IntPtr args; if (!string.IsNullOrEmpty(e.Message)) @@ -170,14 +161,16 @@ internal static void SetArgsAndCause(Exception e, IntPtr ob) args = Runtime.PyTuple_New(0); } - if (Runtime.PyObject_SetAttrString(ob, "args", args) != 0) - throw new PythonException(); + using var argsTuple = NewReference.DangerousFromPointer(args); + + if (Runtime.PyObject_SetAttrString(ob, "args", argsTuple) != 0) + throw PythonException.ThrowLastAsClrException(); if (e.InnerException != null) { // Note: For an AggregateException, InnerException is only the first of the InnerExceptions. using var cause = CLRObject.GetReference(e.InnerException); - Runtime.PyException_SetCause(ob, cause.DangerousMoveToPointer()); + Runtime.PyException_SetCause(ob, cause.Steal()); } } @@ -189,7 +182,7 @@ internal static void ErrorCheck(BorrowedReference pointer) { if (pointer.IsNull) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -202,7 +195,7 @@ internal static void ErrorOccurredCheck(IntPtr pointer) { if (pointer == IntPtr.Zero || ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -210,7 +203,7 @@ internal static IntPtr ErrorCheckIfNull(IntPtr pointer) { if (pointer == IntPtr.Zero && ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return pointer; } @@ -227,19 +220,6 @@ public static bool ExceptionMatches(IntPtr ob) return Runtime.PyErr_ExceptionMatches(ob) != 0; } - /// - /// ExceptionMatches Method - /// - /// - /// Returns true if the given Python exception matches the given - /// Python object. This is a wrapper for PyErr_GivenExceptionMatches. - /// - public static bool ExceptionMatches(IntPtr exc, IntPtr ob) - { - int i = Runtime.PyErr_GivenExceptionMatches(exc, ob); - return i != 0; - } - /// /// SetError Method /// @@ -264,6 +244,7 @@ public static void SetError(IntPtr type, IntPtr exceptionObject) Runtime.PyErr_SetObject(new BorrowedReference(type), new BorrowedReference(exceptionObject)); } + internal const string DispatchInfoAttribute = "__dispatch_info__"; /// /// SetError Method /// @@ -272,8 +253,10 @@ public static void SetError(IntPtr type, IntPtr exceptionObject) /// object. The CLR exception instance is wrapped as a Python /// object, allowing it to be handled naturally from Python. /// - public static void SetError(Exception e) + public static bool SetError(Exception e) { + Debug.Assert(e is not null); + // Because delegates allow arbitrary nesting of Python calling // managed calling Python calling... etc. it is possible that we // might get a managed exception raised that is a wrapper for a @@ -282,31 +265,36 @@ public static void SetError(Exception e) var pe = e as PythonException; if (pe != null) { - Runtime.XIncref(pe.PyType); - Runtime.XIncref(pe.PyValue); - Runtime.XIncref(pe.PyTB); - Runtime.PyErr_Restore(pe.PyType, pe.PyValue, pe.PyTB); - return; + pe.Restore(); + return true; } - IntPtr op = CLRObject.GetInstHandle(e); - IntPtr etype = Runtime.PyObject_GetAttr(op, PyIdentifier.__class__); - Runtime.PyErr_SetObject(new BorrowedReference(etype), new BorrowedReference(op)); - Runtime.XDecref(etype); - Runtime.XDecref(op); + using var instance = Converter.ToPythonReference(e); + if (instance.IsNull()) return false; + + var exceptionInfo = ExceptionDispatchInfo.Capture(e); + using var pyInfo = Converter.ToPythonReference(exceptionInfo); + + if (Runtime.PyObject_SetAttrString(instance, DispatchInfoAttribute, pyInfo) != 0) + return false; + + Debug.Assert(Runtime.PyObject_TypeCheck(instance, new BorrowedReference(BaseException))); + + var type = Runtime.PyObject_TYPE(instance); + Runtime.PyErr_SetObject(type, instance); + return true; } /// /// When called after SetError, sets the cause of the error. /// /// The cause of the current error - public static void SetCause(PythonException cause) + public static void SetCause(Exception cause) { - var currentException = new PythonException(); + var currentException = PythonException.FetchCurrentRaw(); currentException.Normalize(); - cause.Normalize(); - Runtime.XIncref(cause.PyValue); - Runtime.PyException_SetCause(currentException.PyValue, cause.PyValue); + using var causeInstance = Converter.ToPythonReference(cause); + Runtime.PyException_SetCause(currentException.Value!.Reference, causeInstance.Steal()); currentException.Restore(); } @@ -319,7 +307,7 @@ public static void SetCause(PythonException cause) /// public static bool ErrorOccurred() { - return Runtime.PyErr_Occurred() != IntPtr.Zero; + return Runtime.PyErr_Occurred() != null; } /// @@ -393,16 +381,21 @@ public static void deprecation(string message) /// IntPtr.Zero internal static IntPtr RaiseTypeError(string message) { - PythonException previousException = null; - if (ErrorOccurred()) - { - previousException = new PythonException(); - } + var cause = PythonException.FetchCurrentOrNullRaw(); + cause?.Normalize(); + Exceptions.SetError(Exceptions.TypeError, message); - if (previousException != null) - { - SetCause(previousException); - } + + if (cause is null) return IntPtr.Zero; + + var typeError = PythonException.FetchCurrentRaw(); + typeError.Normalize(); + + Runtime.PyException_SetCause( + typeError.Value!.Reference, + new NewReference(cause.Value!.Reference).Steal()); + typeError.Restore(); + return IntPtr.Zero; } diff --git a/src/runtime/extensiontype.cs b/src/runtime/extensiontype.cs index 34a82fe37..78df805ee 100644 --- a/src/runtime/extensiontype.cs +++ b/src/runtime/extensiontype.cs @@ -54,20 +54,23 @@ void SetupGc () } - /// - /// Common finalization code to support custom tp_deallocs. - /// - public static void FinalizeObject(ManagedType self) + protected virtual void Dealloc() { - ClearObjectDict(self.pyHandle); - Runtime.PyObject_GC_Del(self.pyHandle); - // Not necessary for decref of `tpHandle`. - self.FreeGCHandle(); + var type = Runtime.PyObject_TYPE(this.ObjectReference); + Runtime.PyObject_GC_Del(this.pyHandle); + // Not necessary for decref of `tpHandle` - it is borrowed + + this.FreeGCHandle(); + + // we must decref our type: https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_dealloc + Runtime.XDecref(type.DangerousGetAddress()); } - protected void Dealloc() + /// DecRefs and nulls any fields pointing back to Python + protected virtual void Clear() { - FinalizeObject(this); + ClearObjectDict(this.pyHandle); + // Not necessary for decref of `tpHandle` - it is borrowed } /// @@ -96,15 +99,20 @@ public static int tp_descr_set(IntPtr ds, IntPtr ob, IntPtr val) } - /// - /// Default dealloc implementation. - /// public static void tp_dealloc(IntPtr ob) { // Clean up a Python instance of this extension type. This // frees the allocated Python object and decrefs the type. var self = (ExtensionType)GetManagedObject(ob); - self.Dealloc(); + self?.Clear(); + self?.Dealloc(); + } + + public static int tp_clear(IntPtr ob) + { + var self = (ExtensionType)GetManagedObject(ob); + self?.Clear(); + return 0; } protected override void OnLoad(InterDomainContext context) diff --git a/src/runtime/finalizer.cs b/src/runtime/finalizer.cs index be4466791..cfff54070 100644 --- a/src/runtime/finalizer.cs +++ b/src/runtime/finalizer.cs @@ -160,7 +160,7 @@ private void DisposeAll() { // Python requires finalizers to preserve exception: // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation - Runtime.PyErr_Restore(errType, errVal, traceback); + Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), traceback.StealNullable()); } } } diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index be2281c8f..9ac492d21 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -268,9 +268,7 @@ public static IntPtr __import__(IntPtr self, IntPtr argsRaw, IntPtr kw) return IntPtr.Zero; } // Save the exception - var originalException = new PythonException(); - // Otherwise, just clear the it. - Exceptions.Clear(); + var originalException = PythonException.FetchCurrentRaw(); string[] names = realname.Split('.'); diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index e2f7a171f..e2f042bb8 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -79,8 +79,12 @@ internal void FreeGCHandle() } } + /// + /// Given a Python object, return the associated managed object or null. + /// internal static ManagedType? GetManagedObject(BorrowedReference ob) => GetManagedObject(ob.DangerousGetAddress()); + /// /// Given a Python object, return the associated managed object or null. /// diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index a25df30e5..014c5917c 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -171,7 +171,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) Marshal.WriteIntPtr(type, Offsets.tp_clr_inst, gc); if (Runtime.PyType_Ready(type) != 0) - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); return type; } diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index d9572051c..1b7cc4736 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -341,13 +341,13 @@ public MatchedMethod(int kwargsMatched, int defaultsNeeded, object[] margs, int private readonly struct MismatchedMethod { - public MismatchedMethod(PythonException exception, MethodBase mb) + public MismatchedMethod(Exception exception, MethodBase mb) { Exception = exception; Method = mb; } - public PythonException Exception { get; } + public Exception Exception { get; } public MethodBase Method { get; } } @@ -438,8 +438,8 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth outs: out outs); if (margs == null) { - mismatchedMethods.Add(new MismatchedMethod(new PythonException(), mi)); - Exceptions.Clear(); + var mismatchCause = PythonException.FetchCurrent(); + mismatchedMethods.Add(new MismatchedMethod(mismatchCause, mi)); continue; } if (isOperator) @@ -928,7 +928,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i value.Append(": "); Runtime.PyErr_Fetch(out var errType, out var errVal, out var errTrace); AppendArgumentTypes(to: value, args); - Runtime.PyErr_Restore(errType, errVal, errTrace); + Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), errTrace.StealNullable()); Exceptions.RaiseTypeError(value.ToString()); return IntPtr.Zero; } diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs index f33015ba4..c1e729f9e 100644 --- a/src/runtime/methodbinding.cs +++ b/src/runtime/methodbinding.cs @@ -31,7 +31,7 @@ public MethodBinding(MethodObject m, IntPtr target, IntPtr targetType) { Runtime.XIncref(targetType); } - + this.targetType = targetType; this.info = null; @@ -42,12 +42,6 @@ public MethodBinding(MethodObject m, IntPtr target) : this(m, target, IntPtr.Zer { } - private void ClearMembers() - { - Runtime.Py_CLEAR(ref target); - Runtime.Py_CLEAR(ref targetType); - } - /// /// Implement binding of generic methods using the subscript syntax []. /// @@ -235,21 +229,11 @@ public static IntPtr tp_repr(IntPtr ob) return Runtime.PyString_FromString($"<{type} method '{name}'>"); } - /// - /// MethodBinding dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) + protected override void Clear() { - var self = (MethodBinding)GetManagedObject(ob); - self.ClearMembers(); - self.Dealloc(); - } - - public static int tp_clear(IntPtr ob) - { - var self = (MethodBinding)GetManagedObject(ob); - self.ClearMembers(); - return 0; + Runtime.Py_CLEAR(ref this.target); + Runtime.Py_CLEAR(ref this.targetType); + base.Clear(); } protected override void OnSave(InterDomainContext context) diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index 37c01f5c5..2787ec999 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -120,16 +120,6 @@ internal bool IsStatic() return is_static; } - private void ClearMembers() - { - Runtime.Py_CLEAR(ref doc); - if (unbound != null) - { - Runtime.XDecref(unbound.pyHandle); - unbound = null; - } - } - /// /// Descriptor __getattribute__ implementation. /// @@ -210,23 +200,17 @@ public static IntPtr tp_repr(IntPtr ob) return Runtime.PyString_FromString($""); } - /// - /// Descriptor dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) + protected override void Clear() { - var self = (MethodObject)GetManagedObject(ob); - self.ClearMembers(); - ClearObjectDict(ob); - self.Dealloc(); - } + Runtime.Py_CLEAR(ref this.doc); + if (this.unbound != null) + { + Runtime.XDecref(this.unbound.pyHandle); + this.unbound = null; + } - public static int tp_clear(IntPtr ob) - { - var self = (MethodObject)GetManagedObject(ob); - self.ClearMembers(); - ClearObjectDict(ob); - return 0; + ClearObjectDict(this.pyHandle); + base.Clear(); } protected override void OnSave(InterDomainContext context) diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 3c4e02a23..dfb6fdf55 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -153,7 +153,7 @@ private void StoreAttribute(string name, ManagedType ob) { if (Runtime.PyDict_SetItemString(dict, name, ob.pyHandle) != 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } ob.IncrRefCount(); cache[name] = ob; @@ -274,7 +274,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) Exceptions.SetError(e); return IntPtr.Zero; } - + if (attr == null) { @@ -295,13 +295,6 @@ public static IntPtr tp_repr(IntPtr ob) return Runtime.PyString_FromString($""); } - public new static void tp_dealloc(IntPtr ob) - { - var self = (ModuleObject)GetManagedObject(ob); - tp_clear(ob); - self.Dealloc(); - } - public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg) { var self = (ModuleObject)GetManagedObject(ob); @@ -315,17 +308,16 @@ public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg) return 0; } - public static int tp_clear(IntPtr ob) + protected override void Clear() { - var self = (ModuleObject)GetManagedObject(ob); - Runtime.Py_CLEAR(ref self.dict); - ClearObjectDict(ob); - foreach (var attr in self.cache.Values) + Runtime.Py_CLEAR(ref this.dict); + ClearObjectDict(this.pyHandle); + foreach (var attr in this.cache.Values) { Runtime.XDecref(attr.pyHandle); } - self.cache.Clear(); - return 0; + this.cache.Clear(); + base.Clear(); } protected override void OnSave(InterDomainContext context) @@ -345,13 +337,13 @@ protected override void OnSave(InterDomainContext context) if ((Runtime.PyDict_DelItemString(DictRef, pair.Key) == -1) && (Exceptions.ExceptionMatches(Exceptions.KeyError))) { - // Trying to remove a key that's not in the dictionary + // Trying to remove a key that's not in the dictionary // raises an error. We don't care about it. Runtime.PyErr_Clear(); } else if (Exceptions.ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } pair.Value.DecrRefCount(); } @@ -496,7 +488,7 @@ public static Assembly AddReference(string name) /// clr.GetClrType(IComparable) gives you the Type for IComparable, /// that you can e.g. perform reflection on. Similar to typeof(IComparable) in C# /// or clr.GetClrType(IComparable) in IronPython. - /// + /// /// /// /// The Type object diff --git a/src/runtime/native/TypeOffset.cs b/src/runtime/native/TypeOffset.cs index 4e5a726bc..a73a9ae43 100644 --- a/src/runtime/native/TypeOffset.cs +++ b/src/runtime/native/TypeOffset.cs @@ -153,7 +153,6 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties) "__instancecheck__", "__subclasscheck__", "AddReference", - "FinalizeObject", "FindAssembly", "get_SuppressDocs", "get_SuppressOverloads", diff --git a/src/runtime/overload.cs b/src/runtime/overload.cs index e9fa91d3b..8222dc136 100644 --- a/src/runtime/overload.cs +++ b/src/runtime/overload.cs @@ -58,14 +58,10 @@ public static IntPtr tp_repr(IntPtr op) return doc; } - /// - /// OverloadMapper dealloc implementation. - /// - public new static void tp_dealloc(IntPtr ob) + protected override void Clear() { - var self = (OverloadMapper)GetManagedObject(ob); - Runtime.XDecref(self.target); - self.Dealloc(); + Runtime.Py_CLEAR(ref this.target); + base.Clear(); } } } diff --git a/src/runtime/pybuffer.cs b/src/runtime/pybuffer.cs index cf657a033..9fe22aca7 100644 --- a/src/runtime/pybuffer.cs +++ b/src/runtime/pybuffer.cs @@ -18,7 +18,7 @@ unsafe internal PyBuffer(PyObject exporter, PyBUF flags) if (Runtime.PyObject_GetBuffer(exporter.Handle, ref _view, (int)flags) < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } _exporter = exporter; @@ -127,7 +127,7 @@ public void FromContiguous(IntPtr buf, long len, BufferOrderStyle fort) throw new NotSupportedException("FromContiguous requires at least Python 3.7"); if (Runtime.PyBuffer_FromContiguous(ref _view, buf, (IntPtr)len, OrderStyleToChar(fort, false)) < 0) - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } /// @@ -141,7 +141,7 @@ public void ToContiguous(IntPtr buf, BufferOrderStyle order) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyBuffer_ToContiguous(buf, ref _view, _view.len, OrderStyleToChar(order, true)) < 0) - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } /// @@ -167,7 +167,7 @@ public void FillInfo(IntPtr exporter, IntPtr buf, long len, bool _readonly, int if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyBuffer_FillInfo(ref _view, exporter, buf, (IntPtr)len, Convert.ToInt32(_readonly), flags) < 0) - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } /// diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index 4b850a9f9..0a5b2ad82 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -34,7 +34,7 @@ public PyDict() : base(Runtime.PyDict_New()) { if (obj == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -107,7 +107,7 @@ public PyObject Keys() using var items = Runtime.PyDict_Keys(Reference); if (items.IsNull()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return items.MoveToPyObject(); } @@ -124,7 +124,7 @@ public PyObject Values() IntPtr items = Runtime.PyDict_Values(obj); if (items == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(items); } @@ -143,7 +143,7 @@ public PyObject Items() { if (items.IsNull()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return items.MoveToPyObject(); @@ -166,7 +166,7 @@ public PyDict Copy() IntPtr op = Runtime.PyDict_Copy(obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyDict(op); } @@ -183,7 +183,7 @@ public void Update(PyObject other) int result = Runtime.PyDict_Update(Reference, other.Reference); if (result < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } diff --git a/src/runtime/pyiter.cs b/src/runtime/pyiter.cs index 2016ef4f8..da2a600c6 100644 --- a/src/runtime/pyiter.cs +++ b/src/runtime/pyiter.cs @@ -57,7 +57,7 @@ public bool MoveNext() { if (Exceptions.ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } // stop holding the previous object, if there was one diff --git a/src/runtime/pylist.cs b/src/runtime/pylist.cs index 7f5566401..039f5e313 100644 --- a/src/runtime/pylist.cs +++ b/src/runtime/pylist.cs @@ -61,7 +61,7 @@ public PyList() : base(Runtime.PyList_New(0)) { if (obj == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -77,7 +77,7 @@ private static IntPtr FromArray(PyObject[] items) if (r < 0) { Runtime.Py_DecRef(val); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } return val; @@ -118,7 +118,7 @@ public static PyList AsList(PyObject value) IntPtr op = Runtime.PySequence_List(value.obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyList(op); } @@ -135,7 +135,7 @@ public void Append(PyObject item) int r = Runtime.PyList_Append(this.Reference, item.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -150,7 +150,7 @@ public void Insert(int index, PyObject item) int r = Runtime.PyList_Insert(this.Reference, index, item.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -166,7 +166,7 @@ public void Reverse() int r = Runtime.PyList_Reverse(this.Reference); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -182,7 +182,7 @@ public void Sort() int r = Runtime.PyList_Sort(this.Reference); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } } diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 7a1517102..dea12ba1b 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -27,7 +27,8 @@ public partial class PyObject : DynamicObject, IEnumerable, IDisposabl protected internal IntPtr obj = IntPtr.Zero; - internal BorrowedReference Reference => new BorrowedReference(obj); + public static PyObject None => new PyObject(new BorrowedReference(Runtime.PyNone)); + internal BorrowedReference Reference => new BorrowedReference(this.obj); /// /// PyObject Constructor @@ -78,6 +79,17 @@ internal PyObject(BorrowedReference reference) #endif } + internal PyObject(StolenReference reference) + { + if (reference == null) throw new ArgumentNullException(nameof(reference)); + + obj = reference.DangerousGetAddressOrNull(); + Finalizer.Instance.ThrottledCollect(); +#if TRACE_ALLOC + Traceback = new StackTrace(1); +#endif + } + // Ensure that encapsulated Python object is decref'ed appropriately // when the managed wrapper is garbage-collected. ~PyObject() @@ -123,6 +135,13 @@ public static PyObject FromManagedObject(object ob) return new PyObject(op); } + /// + /// Creates new from a nullable reference. + /// When is null, null is returned. + /// + internal static PyObject FromNullableReference(BorrowedReference reference) + => reference.IsNull ? null : new PyObject(reference); + /// /// AsManagedObject Method @@ -136,7 +155,8 @@ public object AsManagedObject(Type t) object result; if (!Converter.ToManaged(obj, t, out result, true)) { - throw new InvalidCastException("cannot convert object to target type", new PythonException()); + throw new InvalidCastException("cannot convert object to target type", + PythonException.FetchCurrentOrNull(out _)); } return result; } @@ -198,7 +218,7 @@ protected virtual void Dispose(bool disposing) { // Python requires finalizers to preserve exception: // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation - Runtime.PyErr_Restore(errType, errVal, traceback); + Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), traceback.StealNullable()); } } else @@ -219,6 +239,9 @@ public void Dispose() GC.SuppressFinalize(this); } + internal BorrowedReference GetPythonTypeReference() + => new BorrowedReference(Runtime.PyObject_TYPE(obj)); + /// /// GetPythonType Method /// @@ -291,7 +314,7 @@ public PyObject GetAttr(string name) IntPtr op = Runtime.PyObject_GetAttrString(obj, name); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } @@ -333,7 +356,7 @@ public PyObject GetAttr(PyObject name) IntPtr op = Runtime.PyObject_GetAttr(obj, name.obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } @@ -376,7 +399,7 @@ public void SetAttr(string name, PyObject value) int r = Runtime.PyObject_SetAttrString(obj, name, value.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -397,7 +420,7 @@ public void SetAttr(PyObject name, PyObject value) int r = Runtime.PyObject_SetAttr(obj, name.obj, value.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -416,7 +439,7 @@ public void DelAttr(string name) int r = Runtime.PyObject_SetAttrString(obj, name, IntPtr.Zero); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -436,7 +459,7 @@ public void DelAttr(PyObject name) int r = Runtime.PyObject_SetAttr(obj, name.obj, IntPtr.Zero); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -456,7 +479,7 @@ public virtual PyObject GetItem(PyObject key) IntPtr op = Runtime.PyObject_GetItem(obj, key.obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } @@ -514,7 +537,7 @@ public virtual void SetItem(PyObject key, PyObject value) int r = Runtime.PyObject_SetItem(obj, key.obj, value.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -573,7 +596,7 @@ public virtual void DelItem(PyObject key) int r = Runtime.PyObject_DelItem(obj, key.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -623,7 +646,7 @@ public virtual long Length() var s = Runtime.PyObject_Size(Reference); if (s < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return s; } @@ -684,7 +707,7 @@ public PyObject GetIterator() IntPtr r = Runtime.PyObject_GetIter(obj); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(r); } @@ -721,7 +744,7 @@ public PyObject Invoke(params PyObject[] args) t.Dispose(); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(r); } @@ -741,7 +764,7 @@ public PyObject Invoke(PyTuple args) IntPtr r = Runtime.PyObject_Call(obj, args.obj, IntPtr.Zero); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(r); } @@ -764,7 +787,7 @@ public PyObject Invoke(PyObject[] args, PyDict kw) t.Dispose(); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(r); } @@ -784,7 +807,7 @@ public PyObject Invoke(PyTuple args, PyDict kw) IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw?.obj ?? IntPtr.Zero); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(r); } @@ -1009,7 +1032,7 @@ public PyList Dir() IntPtr r = Runtime.PyObject_Dir(obj); if (r == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyList(r); } @@ -1067,7 +1090,7 @@ public override bool Equals(object o) int r = Runtime.PyObject_Compare(obj, ((PyObject)o).obj); if (Exceptions.ErrorOccurred()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return r == 0; } @@ -1121,7 +1144,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value) int r = Runtime.PyObject_SetAttrString(obj, binder.Name, ptr); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } Runtime.XDecref(ptr); return true; @@ -1193,7 +1216,7 @@ private static void AddArgument(IntPtr argtuple, int i, object target) if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -1441,4 +1464,13 @@ public override IEnumerable GetDynamicMemberNames() } } } + + internal static class PyObjectExtensions + { + internal static NewReference NewReferenceOrNull(this PyObject self) + => NewReference.DangerousFromPointer( + (self?.obj ?? IntPtr.Zero) == IntPtr.Zero + ? IntPtr.Zero + : Runtime.SelfIncRef(self.obj)); + } } diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 4d09004bd..e1b499c5c 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -184,7 +184,7 @@ public void ImportAll(PyScope scope) int result = Runtime.PyDict_Update(VarsRef, scope.VarsRef); if (result < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -204,7 +204,7 @@ public void ImportAll(PyObject module) int result = Runtime.PyDict_Update(VarsRef, module_dict); if (result < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -219,7 +219,7 @@ public void ImportAll(PyDict dict) int result = Runtime.PyDict_Update(VarsRef, dict.Reference); if (result < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -313,11 +313,10 @@ public void Exec(string code, PyDict locals = null) private void Exec(string code, BorrowedReference _globals, BorrowedReference _locals) { - NewReference reference = Runtime.PyRun_String( + using NewReference reference = Runtime.PyRun_String( code, RunFlagType.File, _globals, _locals ); PythonException.ThrowIfIsNull(reference); - reference.Dispose(); } /// @@ -342,7 +341,7 @@ private void SetPyValue(string name, IntPtr value) int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } } @@ -361,7 +360,7 @@ public void Remove(string name) int r = Runtime.PyObject_DelItem(variables, pyKey.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } } @@ -416,7 +415,7 @@ public bool TryGet(string name, out PyObject value) IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } if (op == Runtime.PyNone) { @@ -539,7 +538,7 @@ internal PyScope NewScope(string name) var module = Runtime.PyModule_New(name); if (module.IsNull()) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyScope(ref module, this); } diff --git a/src/runtime/pysequence.cs b/src/runtime/pysequence.cs index 1850ef7de..536cd3e46 100644 --- a/src/runtime/pysequence.cs +++ b/src/runtime/pysequence.cs @@ -42,7 +42,7 @@ public PyObject GetSlice(int i1, int i2) IntPtr op = Runtime.PySequence_GetSlice(obj, i1, i2); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } @@ -59,7 +59,7 @@ public void SetSlice(int i1, int i2, PyObject v) int r = Runtime.PySequence_SetSlice(obj, i1, i2, v.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -75,7 +75,7 @@ public void DelSlice(int i1, int i2) int r = Runtime.PySequence_DelSlice(obj, i1, i2); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -111,7 +111,7 @@ public bool Contains(PyObject item) int r = Runtime.PySequence_Contains(obj, item.obj); if (r < 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return r != 0; } @@ -129,7 +129,7 @@ public PyObject Concat(PyObject other) IntPtr op = Runtime.PySequence_Concat(obj, other.obj); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } @@ -147,7 +147,7 @@ public PyObject Repeat(int count) IntPtr op = Runtime.PySequence_Repeat(obj, count); if (op == IntPtr.Zero) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } return new PyObject(op); } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 5925880c0..419d4554a 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -523,7 +523,7 @@ public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = nu using PyObject result = RunString(code, globalsRef, localsRef, RunFlagType.File); if (result.obj != Runtime.PyNone) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } /// @@ -538,7 +538,7 @@ internal static void Exec(string code, BorrowedReference globals, BorrowedRefere using PyObject result = RunString(code, globals: globals, locals: locals, RunFlagType.File); if (result.obj != Runtime.PyNone) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -773,10 +773,8 @@ public static void With(PyObject obj, Action Body) // Behavior described here: // https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers - IntPtr type = Runtime.PyNone; - IntPtr val = Runtime.PyNone; - IntPtr traceBack = Runtime.PyNone; - PythonException ex = null; + Exception ex = null; + PythonException pyError = null; try { @@ -785,17 +783,21 @@ public static void With(PyObject obj, Action Body) Body(enterResult); } catch (PythonException e) + { + ex = pyError = e; + } + catch (Exception e) { ex = e; - type = ex.PyType.Coalesce(type); - val = ex.PyValue.Coalesce(val); - traceBack = ex.PyTB.Coalesce(traceBack); + Exceptions.SetError(e); + pyError = PythonException.FetchCurrentRaw(); } - Runtime.XIncref(type); - Runtime.XIncref(val); - Runtime.XIncref(traceBack); - var exitResult = obj.InvokeMethod("__exit__", new PyObject(type), new PyObject(val), new PyObject(traceBack)); + PyObject type = pyError?.Type ?? PyObject.None; + PyObject val = pyError?.Value ?? PyObject.None; + PyObject traceBack = pyError?.Traceback ?? PyObject.None; + + var exitResult = obj.InvokeMethod("__exit__", type, val, traceBack); if (ex != null && !exitResult.IsTrue()) throw ex; } diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 648888293..cca7c439f 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -1,6 +1,9 @@ +#nullable enable using System; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; using System.Text; namespace Python.Runtime @@ -9,134 +12,236 @@ namespace Python.Runtime /// Provides a managed interface to exceptions thrown by the Python /// runtime. /// - public class PythonException : System.Exception, IDisposable + public class PythonException : System.Exception { - private IntPtr _pyType = IntPtr.Zero; - private IntPtr _pyValue = IntPtr.Zero; - private IntPtr _pyTB = IntPtr.Zero; - private readonly string _tb = ""; - private readonly string _message = ""; - private readonly string _pythonTypeName = ""; - private bool disposed = false; - private bool _finalized = false; - - public PythonException() + public PythonException(PyType type, PyObject? value, PyObject? traceback, + string message, Exception? innerException) + : base(message, innerException) { - IntPtr gs = PythonEngine.AcquireLock(); - Runtime.PyErr_Fetch(out _pyType, out _pyValue, out _pyTB); - if (_pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) + Type = type ?? throw new ArgumentNullException(nameof(type)); + Value = value; + Traceback = traceback; + } + + public PythonException(PyType type, PyObject? value, PyObject? traceback, + Exception? innerException) + : this(type, value, traceback, GetMessage(value, type), innerException) { } + + public PythonException(PyType type, PyObject? value, PyObject? traceback) + : this(type, value, traceback, innerException: null) { } + + /// + /// Rethrows the last Python exception as corresponding CLR exception. + /// It is recommended to call this as throw ThrowLastAsClrException() + /// to assist control flow checks. + /// + internal static Exception ThrowLastAsClrException() + { + var exception = FetchCurrentOrNull(out ExceptionDispatchInfo? dispatchInfo) + ?? throw new InvalidOperationException("No exception is set"); + dispatchInfo?.Throw(); + // when dispatchInfo is not null, this line will not be reached + throw exception; + } + + internal static PythonException? FetchCurrentOrNullRaw() + { + using var _ = new Py.GILState(); + + Runtime.PyErr_Fetch(type: out var type, val: out var value, tb: out var traceback); + + if (type.IsNull()) { - string type; - string message; - Runtime.XIncref(_pyType); - using (var pyType = new PyObject(_pyType)) - using (PyObject pyTypeName = pyType.GetAttr("__name__")) - { - type = pyTypeName.ToString(); - } + Debug.Assert(value.IsNull()); + Debug.Assert(traceback.IsNull()); + return null; + } - _pythonTypeName = type; + return new PythonException( + type: new PyType(type.Steal()), + value: value.MoveToPyObjectOrNull(), + traceback: traceback.MoveToPyObjectOrNull()); + } + internal static PythonException FetchCurrentRaw() + => FetchCurrentOrNullRaw() + ?? throw new InvalidOperationException("No exception is set"); - // TODO: If pyValue has a __cause__ attribute, then we could set this.InnerException to the equivalent managed exception. - Runtime.XIncref(_pyValue); - using (var pyValue = new PyObject(_pyValue)) - { - message = pyValue.ToString(); - } - _message = type + " : " + message; + internal static Exception? FetchCurrentOrNull(out ExceptionDispatchInfo? dispatchInfo) + { + dispatchInfo = null; + + // prevent potential interop errors in this method + // from crashing process with undebuggable StackOverflowException + RuntimeHelpers.EnsureSufficientExecutionStack(); + + using var _ = new Py.GILState(); + Runtime.PyErr_Fetch(out var type, out var value, out var traceback); + if (type.IsNull()) + { + Debug.Assert(value.IsNull()); + Debug.Assert(traceback.IsNull()); + return null; } - if (_pyTB != IntPtr.Zero) + Runtime.PyErr_NormalizeException(type: ref type, val: ref value, tb: ref traceback); + + try { - using var tb_module = PyModule.Import("traceback"); + return FromPyErr(typeRef: type, valRef: value, tbRef: traceback, out dispatchInfo); + } + finally + { + type.Dispose(); + value.Dispose(); + traceback.Dispose(); + } + } - Runtime.XIncref(_pyTB); - using var pyTB = new PyObject(_pyTB); + internal static Exception FetchCurrent() + => FetchCurrentOrNull(out _) + ?? throw new InvalidOperationException("No exception is set"); - using var tbList = tb_module.InvokeMethod("format_tb", pyTB); + private static ExceptionDispatchInfo? TryGetDispatchInfo(BorrowedReference exception) + { + if (exception.IsNull) return null; - var sb = new StringBuilder(); - // Reverse Python's traceback list to match the order used in C# - // stacktraces - foreach (var line in tbList.Reverse()) { - sb.Append(line.ToString()); + var pyInfo = Runtime.PyObject_GetAttrString(exception, Exceptions.DispatchInfoAttribute); + if (pyInfo.IsNull()) + { + if (Exceptions.ExceptionMatches(Exceptions.AttributeError)) + { + Exceptions.Clear(); } - _tb = sb.ToString(); + return null; } - PythonEngine.ReleaseLock(gs); - } - // Ensure that encapsulated Python objects are decref'ed appropriately - // when the managed exception wrapper is garbage-collected. + try + { + if (Converter.ToManagedValue(pyInfo, typeof(ExceptionDispatchInfo), out object result, setError: false)) + { + return (ExceptionDispatchInfo)result; + } - ~PythonException() - { - if (_finalized || disposed) + return null; + } + finally { - return; + pyInfo.Dispose(); } - _finalized = true; - Finalizer.Instance.AddFinalizedObject(ref _pyType); - Finalizer.Instance.AddFinalizedObject(ref _pyValue); - Finalizer.Instance.AddFinalizedObject(ref _pyTB); } /// - /// Restores python error. + /// Requires lock to be acquired elsewhere /// - public void Restore() + private static Exception FromPyErr(BorrowedReference typeRef, BorrowedReference valRef, BorrowedReference tbRef, + out ExceptionDispatchInfo? exceptionDispatchInfo) { - IntPtr gs = PythonEngine.AcquireLock(); - Runtime.PyErr_Restore(_pyType, _pyValue, _pyTB); - _pyType = IntPtr.Zero; - _pyValue = IntPtr.Zero; - _pyTB = IntPtr.Zero; - PythonEngine.ReleaseLock(gs); + if (valRef == null) throw new ArgumentNullException(nameof(valRef)); + + var type = PyType.FromReference(typeRef); + var value = new PyObject(valRef); + var traceback = PyObject.FromNullableReference(tbRef); + + exceptionDispatchInfo = TryGetDispatchInfo(valRef); + if (exceptionDispatchInfo != null) + { + return exceptionDispatchInfo.SourceException; + } + + if (ManagedType.GetManagedObject(valRef) is CLRObject { inst: Exception e }) + { + return e; + } + + if (PyObjectConversions.TryDecode(valRef, typeRef, typeof(Exception), out object decoded) + && decoded is Exception decodedException) + { + return decodedException; + } + + using var cause = Runtime.PyException_GetCause(valRef); + Exception? inner = FromCause(cause); + return new PythonException(type, value, traceback, inner); } - /// - /// PyType Property - /// - /// - /// Returns the exception type as a Python object. - /// - public IntPtr PyType + private static Exception? FromCause(BorrowedReference cause) { - get { return _pyType; } + if (cause == null || cause.IsNone()) return null; + + Debug.Assert(Runtime.PyObject_TypeCheck(cause, new BorrowedReference(Exceptions.BaseException))); + + using var innerTraceback = Runtime.PyException_GetTraceback(cause); + return FromPyErr( + typeRef: Runtime.PyObject_TYPE(cause), + valRef: cause, + tbRef: innerTraceback, + out _); + } - /// - /// PyValue Property - /// - /// - /// Returns the exception value as a Python object. - /// - public IntPtr PyValue + private static string GetMessage(PyObject? value, PyType type) { - get { return _pyValue; } + if (type is null) throw new ArgumentNullException(nameof(type)); + + if (value != null && !value.IsNone()) + { + return value.ToString(); + } + + return type.Name; } - /// - /// PyTB Property - /// - /// - /// Returns the TraceBack as a Python object. - /// - public IntPtr PyTB + private static string TracebackToString(PyObject traceback) + { + if (traceback is null) + { + throw new ArgumentNullException(nameof(traceback)); + } + + using var tracebackModule = PyModule.Import("traceback"); + using var stackLines = new PyList(tracebackModule.InvokeMethod("format_tb", traceback)); + stackLines.Reverse(); + var result = new StringBuilder(); + foreach (PyObject stackLine in stackLines) + { + result.Append(stackLine); + stackLine.Dispose(); + } + return result.ToString(); + } + + /// Restores python error. + public void Restore() { - get { return _pyTB; } + CheckRuntimeIsRunning(); + + using var _ = new Py.GILState(); + + NewReference type = Type.NewReferenceOrNull(); + NewReference value = Value.NewReferenceOrNull(); + NewReference traceback = Traceback.NewReferenceOrNull(); + + Runtime.PyErr_Restore( + type: type.Steal(), + val: value.StealNullable(), + tb: traceback.StealNullable()); } /// - /// Message Property + /// Returns the exception type as a Python object. + /// + public PyType Type { get; private set; } + + /// + /// Returns the exception value as a Python object. /// + /// + public PyObject? Value { get; private set; } + /// - /// A string representing the python exception message. + /// Returns the TraceBack as a Python object. /// - public override string Message - { - get { return _message; } - } + public PyObject? Traceback { get; } /// /// StackTrace Property @@ -144,29 +249,69 @@ public override string Message /// /// A string representing the python exception stack trace. /// - public override string StackTrace => $"{_tb}===\n{base.StackTrace}"; + public override string StackTrace + { + get + { + if (Traceback is null) return base.StackTrace; - /// - /// Python error type name. - /// - public string PythonTypeName + if (!PythonEngine.IsInitialized && Runtime.Py_IsInitialized() == 0) + return "Python stack unavailable as runtime was shut down\n" + base.StackTrace; + + using var _ = new Py.GILState(); + return TracebackToString(Traceback) + base.StackTrace; + } + } + + public bool IsNormalized { - get { return _pythonTypeName; } + get + { + if (Value is null) return false; + + CheckRuntimeIsRunning(); + + using var _ = new Py.GILState(); + return Runtime.PyObject_TypeCheck(Value.Reference, Type.Reference); + } } /// - /// Replaces PyValue with an instance of PyType, if PyValue is not already an instance of PyType. - /// Often PyValue is a string and this method will replace it with a proper exception object. - /// Must not be called when an error is set. + /// Replaces Value with an instance of Type, if Value is not already an instance of Type. /// public void Normalize() { + CheckRuntimeIsRunning(); + IntPtr gs = PythonEngine.AcquireLock(); try { if (Exceptions.ErrorOccurred()) throw new InvalidOperationException("Cannot normalize when an error is set"); + // If an error is set and this PythonException is unnormalized, the error will be cleared and the PythonException will be replaced by a different error. - Runtime.PyErr_NormalizeException(ref _pyType, ref _pyValue, ref _pyTB); + NewReference value = Value.NewReferenceOrNull(); + NewReference type = Type.NewReferenceOrNull(); + NewReference tb = Traceback.NewReferenceOrNull(); + + Runtime.PyErr_NormalizeException(type: ref type, val: ref value, tb: ref tb); + + Value = value.MoveToPyObject(); + Type = new PyType(type.Steal()); + try + { + Debug.Assert(Traceback is null == tb.IsNull()); + if (!tb.IsNull()) + { + Debug.Assert(Traceback!.Reference == tb); + + int r = Runtime.PyException_SetTraceback(Value.Reference, tb); + ThrowIfIsNotZero(r); + } + } + finally + { + tb.Dispose(); + } } finally { @@ -180,129 +325,79 @@ public void Normalize() /// public string Format() { - string res; - IntPtr gs = PythonEngine.AcquireLock(); - try - { - if (_pyTB != IntPtr.Zero && _pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) - { - IntPtr tb = _pyTB; - IntPtr type = _pyType; - IntPtr value = _pyValue; - - 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)) - using (PyObject tb_mod = PyModule.Import("traceback")) - { - var buffer = new StringBuilder(); - var values = tb_mod.InvokeMethod("format_exception", pyType, pyValue, pyTB); - foreach (PyObject val in values) - { - buffer.Append(val.ToString()); - } - res = buffer.ToString(); - } - } - else - { - res = StackTrace; - } - } - finally + CheckRuntimeIsRunning(); + + using var _ = new Py.GILState(); + + var copy = Clone(); + copy.Normalize(); + + if (copy.Traceback is null || copy.Value is null) + return StackTrace; + + using var traceback = PyModule.Import("traceback"); + var buffer = new StringBuilder(); + using var values = traceback.InvokeMethod("format_exception", copy.Type, copy.Value, copy.Traceback); + foreach (PyObject val in values) { - PythonEngine.ReleaseLock(gs); + buffer.Append(val); + val.Dispose(); } - return res; + return buffer.ToString(); + } - public bool IsMatches(IntPtr exc) + public PythonException Clone() + => new PythonException(type: Type, value: Value, traceback: Traceback, + Message, InnerException); + + internal bool Is(IntPtr type) { - return Runtime.PyErr_GivenExceptionMatches(PyType, exc) != 0; + return Runtime.PyErr_GivenExceptionMatches( + given: (Value ?? Type).Reference, + typeOrTypes: new BorrowedReference(type)) != 0; } - /// - /// Dispose Method - /// - /// - /// The Dispose method provides a way to explicitly release the - /// Python objects represented by a PythonException. - /// If object not properly disposed can cause AppDomain unload issue. - /// See GH#397 and GH#400. - /// - public void Dispose() + private static void CheckRuntimeIsRunning() { - if (!disposed) - { - if (Runtime.Py_IsInitialized() > 0 && !Runtime.IsFinalizing) - { - IntPtr gs = PythonEngine.AcquireLock(); - if (_pyType != IntPtr.Zero) - { - Runtime.XDecref(_pyType); - _pyType= IntPtr.Zero; - } - - if (_pyValue != IntPtr.Zero) - { - Runtime.XDecref(_pyValue); - _pyValue = IntPtr.Zero; - } - - // XXX Do we ever get TraceBack? // - if (_pyTB != IntPtr.Zero) - { - Runtime.XDecref(_pyTB); - _pyTB = IntPtr.Zero; - } - PythonEngine.ReleaseLock(gs); - } - GC.SuppressFinalize(this); - disposed = true; - } + if (!PythonEngine.IsInitialized && Runtime.Py_IsInitialized() == 0) + throw new InvalidOperationException("Python runtime must be running"); } /// - /// Matches Method + /// Returns true if the current Python exception + /// matches the given exception type. /// - /// - /// Returns true if the Python exception type represented by the - /// PythonException instance matches the given exception type. - /// - public static bool Matches(IntPtr ob) + internal static bool CurrentMatches(IntPtr ob) { return Runtime.PyErr_ExceptionMatches(ob) != 0; } - [System.Diagnostics.DebuggerHidden] - public static void ThrowIfIsNull(IntPtr ob) + internal static BorrowedReference ThrowIfIsNull(BorrowedReference ob) { - if (ob == IntPtr.Zero) + if (ob == null) { - throw new PythonException(); + throw ThrowLastAsClrException(); } + + return ob; } - [System.Diagnostics.DebuggerHidden] - internal static void ThrowIfIsNull(BorrowedReference reference) + public static IntPtr ThrowIfIsNull(IntPtr ob) { - if (reference.IsNull) + if (ob == IntPtr.Zero) { - throw new PythonException(); + throw ThrowLastAsClrException(); } + + return ob; } - [System.Diagnostics.DebuggerHidden] public static void ThrowIfIsNotZero(int value) { if (value != 0) { - throw new PythonException(); + throw ThrowLastAsClrException(); } } } diff --git a/src/runtime/pytuple.cs b/src/runtime/pytuple.cs index 530ced3d2..5a18b6bed 100644 --- a/src/runtime/pytuple.cs +++ b/src/runtime/pytuple.cs @@ -77,7 +77,7 @@ private static IntPtr FromArray(PyObject[] items) if (res != 0) { Runtime.Py_DecRef(val); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } return val; diff --git a/src/runtime/pytype.cs b/src/runtime/pytype.cs index e3d95db8d..e9c80ebf3 100644 --- a/src/runtime/pytype.cs +++ b/src/runtime/pytype.cs @@ -20,6 +20,30 @@ internal PyType(BorrowedReference reference) : base(reference) throw new ArgumentException("object is not a type"); } + internal PyType(StolenReference reference) : base(EnsureIsType(in reference)) + { + } + + internal new static PyType? FromNullableReference(BorrowedReference reference) + => reference == null + ? null + : new PyType(new NewReference(reference).Steal()); + + internal static PyType FromReference(BorrowedReference reference) + => FromNullableReference(reference) ?? throw new ArgumentNullException(nameof(reference)); + + public string Name + { + get + { + var namePtr = new StrPtr + { + RawPointer = Marshal.ReadIntPtr(Handle, TypeOffset.tp_name), + }; + return namePtr.ToString(System.Text.Encoding.UTF8)!; + } + } + /// Checks if specified object is a Python type. public static bool IsType(PyObject value) { @@ -34,6 +58,19 @@ internal IntPtr GetSlot(TypeSlotID slot) return Exceptions.ErrorCheckIfNull(result); } + private static IntPtr EnsureIsType(in StolenReference reference) + { + IntPtr address = reference.DangerousGetAddressOrNull(); + if (address == IntPtr.Zero) + throw new ArgumentNullException(nameof(reference)); + return EnsureIsType(address); + } + + private static IntPtr EnsureIsType(IntPtr ob) + => Runtime.PyType_Check(ob) + ? ob + : throw new ArgumentException("object is not a type"); + private static BorrowedReference FromObject(PyObject o) { if (o is null) throw new ArgumentNullException(nameof(o)); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 8a3ad9231..6086135f5 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -424,13 +424,8 @@ private static void RunExitFuncs() { atexit = Py.Import("atexit"); } - catch (PythonException e) + catch (PythonException e) when (e.Is(Exceptions.ImportError)) { - if (!e.IsMatches(Exceptions.ImportError)) - { - throw; - } - e.Dispose(); // The runtime may not provided `atexit` module. return; } @@ -443,7 +438,6 @@ private static void RunExitFuncs() catch (PythonException e) { Console.Error.WriteLine(e); - e.Dispose(); } } } @@ -498,9 +492,9 @@ private static void PyDictTryDelItem(BorrowedReference dict, string key) { return; } - if (!PythonException.Matches(Exceptions.KeyError)) + if (!PythonException.CurrentMatches(Exceptions.KeyError)) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } PyErr_Clear(); } @@ -597,9 +591,9 @@ public static PyObject None /// internal static void CheckExceptionOccurred() { - if (PyErr_Occurred() != IntPtr.Zero) + if (PyErr_Occurred() != null) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } } @@ -730,6 +724,9 @@ internal static IntPtr SelfIncRef(IntPtr op) internal static unsafe void XDecref(IntPtr op) { +#if DEBUG + Debug.Assert(op == IntPtr.Zero || Refcount(op) > 0); +#endif #if !CUSTOM_INCDEC_REF Py_DecRef(op); return; @@ -1053,6 +1050,11 @@ internal static int PyObject_SetAttrString(IntPtr pointer, string name, IntPtr v using var namePtr = new StrPtr(name, Encoding.UTF8); return Delegates.PyObject_SetAttrString(pointer, namePtr, value); } + internal static int PyObject_SetAttrString(BorrowedReference @object, string name, BorrowedReference value) + { + using var namePtr = new StrPtr(name, Encoding.UTF8); + return Delegates.PyObject_SetAttrString(@object.DangerousGetAddress(), namePtr, value.DangerousGetAddress()); + } internal static int PyObject_HasAttr(BorrowedReference pointer, BorrowedReference name) => Delegates.PyObject_HasAttr(pointer, name); @@ -1140,17 +1142,28 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2) internal static IntPtr PyObject_Repr(IntPtr pointer) { - Debug.Assert(PyErr_Occurred() == IntPtr.Zero); + AssertNoErorSet(); + return Delegates.PyObject_Repr(pointer); } internal static IntPtr PyObject_Str(IntPtr pointer) { - Debug.Assert(PyErr_Occurred() == IntPtr.Zero); + AssertNoErorSet(); + return Delegates.PyObject_Str(pointer); } + [Conditional("DEBUG")] + internal static void AssertNoErorSet() + { + if (Exceptions.ErrorOccurred()) + throw new InvalidOperationException( + "Can't call with exception set", + PythonException.FetchCurrent()); + } + internal static IntPtr PyObject_Dir(IntPtr pointer) => Delegates.PyObject_Dir(pointer); @@ -2096,19 +2109,19 @@ internal static void PyErr_SetString(IntPtr ob, string message) internal static int PyErr_ExceptionMatches(IntPtr exception) => Delegates.PyErr_ExceptionMatches(exception); - internal static int PyErr_GivenExceptionMatches(IntPtr ob, IntPtr val) => Delegates.PyErr_GivenExceptionMatches(ob, val); + internal static int PyErr_GivenExceptionMatches(BorrowedReference given, BorrowedReference typeOrTypes) => Delegates.PyErr_GivenExceptionMatches(given, typeOrTypes); - internal static void PyErr_NormalizeException(ref IntPtr ob, ref IntPtr val, ref IntPtr tb) => Delegates.PyErr_NormalizeException(ref ob, ref val, ref tb); + internal static void PyErr_NormalizeException(ref NewReference type, ref NewReference val, ref NewReference tb) => Delegates.PyErr_NormalizeException(ref type, ref val, ref tb); - internal static IntPtr PyErr_Occurred() => Delegates.PyErr_Occurred(); + internal static BorrowedReference PyErr_Occurred() => Delegates.PyErr_Occurred(); - internal static void PyErr_Fetch(out IntPtr ob, out IntPtr val, out IntPtr tb) => Delegates.PyErr_Fetch(out ob, out val, out tb); + internal static void PyErr_Fetch(out NewReference type, out NewReference val, out NewReference tb) => Delegates.PyErr_Fetch(out type, out val, out tb); - internal static void PyErr_Restore(IntPtr ob, IntPtr val, IntPtr tb) => Delegates.PyErr_Restore(ob, val, tb); + internal static void PyErr_Restore(StolenReference type, StolenReference val, StolenReference tb) => Delegates.PyErr_Restore(type, val, tb); internal static void PyErr_Clear() => Delegates.PyErr_Clear(); @@ -2116,11 +2129,19 @@ internal static void PyErr_SetString(IntPtr ob, string message) internal static void PyErr_Print() => Delegates.PyErr_Print(); + + internal static NewReference PyException_GetCause(BorrowedReference ex) + => Delegates.PyException_GetCause(ex); + internal static NewReference PyException_GetTraceback(BorrowedReference ex) + => Delegates.PyException_GetTraceback(ex); + /// /// Set the cause associated with the exception to cause. Use NULL to clear it. There is no type check to make sure that cause is either an exception instance or None. This steals a reference to cause. /// - - internal static void PyException_SetCause(IntPtr ex, IntPtr cause) => Delegates.PyException_SetCause(ex, cause); + internal static void PyException_SetCause(BorrowedReference ex, StolenReference cause) + => Delegates.PyException_SetCause(ex, cause); + internal static int PyException_SetTraceback(BorrowedReference ex, BorrowedReference tb) + => Delegates.PyException_SetTraceback(ex, tb); //==================================================================== // Cell API @@ -2506,11 +2527,11 @@ static Delegates() PyErr_SetFromErrno = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_SetFromErrno), GetUnmanagedDll(_PythonDll)); PyErr_SetNone = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_SetNone), GetUnmanagedDll(_PythonDll)); PyErr_ExceptionMatches = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_ExceptionMatches), GetUnmanagedDll(_PythonDll)); - PyErr_GivenExceptionMatches = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_GivenExceptionMatches), GetUnmanagedDll(_PythonDll)); - PyErr_NormalizeException = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_NormalizeException), GetUnmanagedDll(_PythonDll)); - PyErr_Occurred = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Occurred), GetUnmanagedDll(_PythonDll)); - PyErr_Fetch = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Fetch), GetUnmanagedDll(_PythonDll)); - PyErr_Restore = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Restore), GetUnmanagedDll(_PythonDll)); + PyErr_GivenExceptionMatches = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_GivenExceptionMatches), GetUnmanagedDll(_PythonDll)); + PyErr_NormalizeException = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_NormalizeException), GetUnmanagedDll(_PythonDll)); + PyErr_Occurred = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Occurred), GetUnmanagedDll(_PythonDll)); + PyErr_Fetch = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Fetch), GetUnmanagedDll(_PythonDll)); + PyErr_Restore = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Restore), GetUnmanagedDll(_PythonDll)); PyErr_Clear = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Clear), GetUnmanagedDll(_PythonDll)); PyErr_Print = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyErr_Print), GetUnmanagedDll(_PythonDll)); PyCell_Get = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyCell_Get), GetUnmanagedDll(_PythonDll)); @@ -2527,7 +2548,10 @@ static Delegates() PyLong_AsSignedSize_t = (delegate* unmanaged[Cdecl])GetFunctionByName("PyLong_AsSsize_t", GetUnmanagedDll(_PythonDll)); PyExplicitlyConvertToInt64 = (delegate* unmanaged[Cdecl])GetFunctionByName("PyLong_AsLongLong", GetUnmanagedDll(_PythonDll)); PyDict_GetItemWithError = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyDict_GetItemWithError), GetUnmanagedDll(_PythonDll)); - PyException_SetCause = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_SetCause), GetUnmanagedDll(_PythonDll)); + PyException_GetCause = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_GetCause), GetUnmanagedDll(_PythonDll)); + PyException_GetTraceback = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_GetTraceback), GetUnmanagedDll(_PythonDll)); + PyException_SetCause = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_SetCause), GetUnmanagedDll(_PythonDll)); + PyException_SetTraceback = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_SetTraceback), GetUnmanagedDll(_PythonDll)); PyThreadState_SetAsyncExcLLP64 = (delegate* unmanaged[Cdecl])GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll)); PyThreadState_SetAsyncExcLP64 = (delegate* unmanaged[Cdecl])GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll)); PyType_GetSlot = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyType_GetSlot), GetUnmanagedDll(_PythonDll)); @@ -2791,11 +2815,11 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyErr_SetFromErrno { get; } internal static delegate* unmanaged[Cdecl] PyErr_SetNone { get; } internal static delegate* unmanaged[Cdecl] PyErr_ExceptionMatches { get; } - internal static delegate* unmanaged[Cdecl] PyErr_GivenExceptionMatches { get; } - internal static delegate* unmanaged[Cdecl] PyErr_NormalizeException { get; } - internal static delegate* unmanaged[Cdecl] PyErr_Occurred { get; } - internal static delegate* unmanaged[Cdecl] PyErr_Fetch { get; } - internal static delegate* unmanaged[Cdecl] PyErr_Restore { get; } + internal static delegate* unmanaged[Cdecl] PyErr_GivenExceptionMatches { get; } + internal static delegate* unmanaged[Cdecl] PyErr_NormalizeException { get; } + internal static delegate* unmanaged[Cdecl] PyErr_Occurred { get; } + internal static delegate* unmanaged[Cdecl] PyErr_Fetch { get; } + internal static delegate* unmanaged[Cdecl] PyErr_Restore { get; } internal static delegate* unmanaged[Cdecl] PyErr_Clear { get; } internal static delegate* unmanaged[Cdecl] PyErr_Print { get; } internal static delegate* unmanaged[Cdecl] PyCell_Get { get; } @@ -2812,7 +2836,10 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyLong_AsSignedSize_t { get; } internal static delegate* unmanaged[Cdecl] PyExplicitlyConvertToInt64 { get; } internal static delegate* unmanaged[Cdecl] PyDict_GetItemWithError { get; } - internal static delegate* unmanaged[Cdecl] PyException_SetCause { get; } + internal static delegate* unmanaged[Cdecl] PyException_GetCause { get; } + internal static delegate* unmanaged[Cdecl] PyException_GetTraceback { get; } + internal static delegate* unmanaged[Cdecl] PyException_SetCause { get; } + internal static delegate* unmanaged[Cdecl] PyException_SetTraceback { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_SetAsyncExcLLP64 { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_SetAsyncExcLP64 { get; } internal static delegate* unmanaged[Cdecl] PyObject_GenericGetDict { get; } diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 9707568b5..13d822c09 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -167,7 +167,7 @@ internal static unsafe PyType CreateType(Type impl) if (Runtime.PyType_Ready(type) != 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } // TODO: use PyType(TypeSpec) constructor @@ -291,7 +291,7 @@ internal static PyType CreateType(ClassBase impl, Type clrType) if (Runtime.PyType_Ready(type) != 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } var dict = new BorrowedReference(Marshal.ReadIntPtr(type, TypeOffset.tp_dict)); @@ -493,7 +493,7 @@ internal static IntPtr CreateMetaType(Type impl, out SlotsHolder slotsHolder) if (Runtime.PyType_Ready(type) != 0) { - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); @@ -880,14 +880,14 @@ public static IntPtr CreateObjectType() if (Runtime.PyDict_SetItemString(globals, "__builtins__", Runtime.PyEval_GetBuiltins()) != 0) { globals.Dispose(); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } const string code = "class A(object): pass"; using var resRef = Runtime.PyRun_String(code, RunFlagType.File, globals, globals); if (resRef.IsNull()) { globals.Dispose(); - throw new PythonException(); + throw PythonException.ThrowLastAsClrException(); } resRef.Dispose(); BorrowedReference A = Runtime.PyDict_GetItemString(globals, "A");