X Tutup
using System; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Security.Permissions; using System.Text; using Python.Runtime.Native; namespace Python.Runtime { /// /// Provides a managed interface to exceptions thrown by the Python /// runtime. /// [Serializable] public class PythonException : System.Exception { public PythonException(PyType type, PyObject? value, PyObject? traceback, string message, Exception? innerException) : base(message, innerException) { 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. /// [DebuggerHidden] internal static Exception ThrowLastAsClrException() { // prevent potential interop errors in this method // from crashing process with undebuggable StackOverflowException RuntimeHelpers.EnsureSufficientExecutionStack(); 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()) { Debug.Assert(value.IsNull()); Debug.Assert(traceback.IsNull()); return null; } 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"); internal static Exception? PeekCurrentOrNull(out ExceptionDispatchInfo? dispatchInfo) { using var _ = new Py.GILState(); Runtime.PyErr_Fetch(out var type, out var value, out var traceback); Runtime.PyErr_Restore( new NewReference(type, canBeNull: true).StealNullable(), new NewReference(value, canBeNull: true).StealNullable(), new NewReference(traceback, canBeNull: true).StealNullable()); var err = FetchCurrentOrNull(out dispatchInfo); Runtime.PyErr_Restore(type.StealNullable(), value.StealNullable(), traceback.StealNullable()); return err; } 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; } try { if (TryDecodePyErr(type.Borrow(), value.BorrowNullable(), traceback.BorrowNullable()) is { } pyErr) { type.Dispose(); value.Dispose(); traceback.Dispose(); return pyErr; } } catch { type.Dispose(); value.Dispose(); traceback.Dispose(); throw; } Runtime.PyErr_NormalizeException(type: ref type, val: ref value, tb: ref traceback); try { return FromPyErr(typeRef: type.Borrow(), valRef: value.Borrow(), tbRef: traceback.BorrowNullable(), out dispatchInfo); } finally { type.Dispose(); value.Dispose(); traceback.Dispose(); } } internal static Exception FetchCurrent() => FetchCurrentOrNull(out _) ?? throw new InvalidOperationException("No exception is set"); private static ExceptionDispatchInfo? TryGetDispatchInfo(BorrowedReference exception) { if (exception.IsNull) return null; using var pyInfo = Runtime.PyObject_GetAttrString(exception, Exceptions.DispatchInfoAttribute); if (pyInfo.IsNull()) { if (Exceptions.ExceptionMatches(Exceptions.AttributeError)) { Exceptions.Clear(); } return null; } if (Converter.ToManagedValue(pyInfo.Borrow(), typeof(ExceptionDispatchInfo), out object? result, setError: false)) { return (ExceptionDispatchInfo)result!; } return null; } /// /// Requires lock to be acquired elsewhere /// private static Exception FromPyErr(BorrowedReference typeRef, BorrowedReference valRef, BorrowedReference tbRef, out ExceptionDispatchInfo? exceptionDispatchInfo) { 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 (TryDecodePyErr(typeRef, valRef, tbRef) is { } pyErr) { return pyErr; } 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.BorrowNullable()); return new PythonException(type, value, traceback, inner); } private static PyDict ToPyErrArgs(BorrowedReference typeRef, BorrowedReference valRef, BorrowedReference tbRef) { using var type = PyType.FromReference(typeRef); using var value = PyObject.FromNullableReference(valRef); using var traceback = PyObject.FromNullableReference(tbRef); var errorDict = new PyDict(); errorDict["type"] = type; if (value is not null) errorDict["value"] = value; if (traceback is not null) errorDict["traceback"] = traceback; return errorDict; } private static Exception? TryDecodePyErr(BorrowedReference typeRef, BorrowedReference valRef, BorrowedReference tbRef) { using var pyErrType = Runtime.InteropModule.GetAttr("PyErr"); using var errorDict = ToPyErrArgs(typeRef, valRef, tbRef); using var pyErrInfo = pyErrType.Invoke(new PyTuple(), errorDict); if (PyObjectConversions.TryDecode(pyErrInfo.Reference, pyErrType.Reference, typeof(Exception), out object? decoded) && decoded is Exception decodedPyErrInfo) { return decodedPyErrInfo; } return null; } private static Exception? FromCause(BorrowedReference cause) { if (cause == null || cause.IsNone()) return null; Debug.Assert(Runtime.PyObject_TypeCheck(cause, Exceptions.BaseException)); using var innerTraceback = Runtime.PyException_GetTraceback(cause); return FromPyErr( typeRef: Runtime.PyObject_TYPE(cause), valRef: cause, tbRef: innerTraceback.BorrowNullable(), out _); } private static string GetMessage(PyObject? value, PyType type) { if (type is null) throw new ArgumentNullException(nameof(type)); if (value != null && !value.IsNone()) { return value.ToString() ?? "no message"; } return type.Name; } 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. internal void Restore() { NewReference type = Type.NewReferenceOrNull(); NewReference value = Value.NewReferenceOrNull(); NewReference traceback = Traceback.NewReferenceOrNull(); Runtime.PyErr_Restore( type: type.Steal(), val: value.StealNullable(), tb: traceback.StealNullable()); } /// /// 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; } /// /// Returns the TraceBack as a Python object. /// public PyObject? Traceback { get; } /// /// StackTrace Property /// /// /// A string representing the python exception stack trace. /// public override string StackTrace { get { if (Traceback is null) return base.StackTrace; 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 { if (Value is null) return false; CheckRuntimeIsRunning(); using var _ = new Py.GILState(); return Runtime.PyObject_TypeCheck(Value.Reference, Type.Reference); } } /// /// Replaces Value with an instance of Type, if Value is not already an instance of Type. /// public void Normalize() { CheckRuntimeIsRunning(); PyGILState 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. 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.Borrow()); int r = Runtime.PyException_SetTraceback(Value.Reference, tb.Borrow()); ThrowIfIsNotZero(r); } } finally { tb.Dispose(); } } finally { PythonEngine.ReleaseLock(gs); } } /// /// Formats this PythonException object into a message as would be printed /// out via the Python console. See traceback.format_exception /// public string Format() { 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 PyIter.GetIter(values)) { buffer.Append(val); val.Dispose(); } return buffer.ToString(); } public PythonException Clone() => new( type: Type, value: Value, traceback: Traceback, Message, InnerException ); #region Serializable [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] protected PythonException(SerializationInfo info, StreamingContext context) : base(info, context) { Type = (PyType)info.GetValue(nameof(Type), typeof(PyType)); Value = (PyObject)info.GetValue(nameof(Value), typeof(PyObject)); Traceback = (PyObject)info.GetValue(nameof(Traceback), typeof(PyObject)); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) { throw new ArgumentNullException(nameof(info)); } base.GetObjectData(info, context); info.AddValue(nameof(Type), Type); info.AddValue(nameof(Value), Value); info.AddValue(nameof(Traceback), Traceback); } #endregion internal bool Is(BorrowedReference type) { return Runtime.PyErr_GivenExceptionMatches( given: (Value ?? Type).Reference, typeOrTypes: type) != 0; } private static void CheckRuntimeIsRunning() { if (!PythonEngine.IsInitialized && Runtime.Py_IsInitialized() == 0) throw new InvalidOperationException("Python runtime must be running"); } /// /// Returns true if the current Python exception /// matches the given exception type. /// internal static bool CurrentMatches(BorrowedReference ob) { return Runtime.PyErr_ExceptionMatches(ob) != 0; } [DebuggerHidden] internal static void ThrowIfIsNull(in NewReference ob) { if (ob.BorrowNullable() == null) { throw ThrowLastAsClrException(); } } internal static BorrowedReference ThrowIfIsNull(BorrowedReference ob) => Exceptions.ErrorCheck(ob); [DebuggerHidden] internal static void ThrowIfIsNotZero(int value) { if (value != 0) { throw ThrowLastAsClrException(); } } } }
X Tutup