[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/QuantConnect/pythonnet/master/src/runtime/PythonException.cs [Back]  [Original]

using System;
using System.Diagnostics;
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? 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;
            }

            try
            {
                var normalizedValue = new NewReference(value.Borrow());
                Runtime.PyErr_NormalizeException(type: ref type, val: ref normalizedValue, tb: ref traceback);

                return FromPyErr(typeRef: type.Borrow(), valRef: value.Borrow(), nValRef: normalizedValue.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, out var _))
            {
                return (ExceptionDispatchInfo)result!;
            }

            return null;
        }

        /// 
        /// Requires lock to be acquired elsewhere
        /// 
        private static Exception FromPyErr(BorrowedReference typeRef, BorrowedReference valRef, BorrowedReference nValRef, 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);

            Exception exception = null;

            exceptionDispatchInfo = TryGetDispatchInfo(valRef);
            if (exceptionDispatchInfo != null)
            {
                exception = exceptionDispatchInfo.SourceException;
                exceptionDispatchInfo = null;
            }
            else if (ManagedType.GetManagedObject(valRef) is CLRObject { inst: Exception e })
            {
                exception = e;
            }
            else if (TryDecodePyErr(typeRef, valRef, tbRef) is { } pyErr)
            {
                exception = pyErr;
            }
            else if (PyObjectConversions.TryDecode(valRef, typeRef, typeof(Exception), out object? decoded)
                && decoded is Exception decodedException)
            {
                exception = decodedException;
            }

            if (exception is not null)
            {
                // Return ClrBubbledExceptions when they are bubbled from Python -> C# -> Python -> C# -> ...
                // or when the traceback is not available, so we fall back  to the original behavior
                if (exception is ClrBubbledException || traceback is null)
                {
                    return exception;
                }

                using var _ = new Py.GILState();
                return new ClrBubbledException(exception, TracebackToString(traceback));
            }

            using var cause = Runtime.PyException_GetCause(nValRef);
            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,
                nValRef: 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())
            {
                var message = value.ToString() ?? "no message";

                // Python 3.12+ eagerly normalizes the error indicator, so a SyntaxError
                // reaches us as the exception instance whose str() omits the offending
                // source line. Pre-3.12 we received the raw args tuple, whose str()
                // included it. Re-append the source text so the message stays complete
                // for callers that surface it (e.g. compile diagnostics). This is a
                // no-op on  new PythonException(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();
            }
        }
    }
}

Web Proxy Viewer  |  New URL  |  Original Page