File: Utilities\Contracts.cs
Web Access
Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// We want the conditional code in this file to always be available to
// client assemblies that might be DEBUG versions. That is, if someone uses
// the release build of this assembly to build a DEBUG version of their code,
// we want Contracts.Assert to be fully functional for that client.
#define DEBUG
 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
 
#if CPUMATH_INFRASTRUCTURE
namespace Microsoft.ML.Internal.CpuMath.Core
#else
namespace Microsoft.ML.Runtime
#endif
{
    using Conditional = System.Diagnostics.ConditionalAttribute;
    using Debug = System.Diagnostics.Debug;
 
    /// <summary>
    /// Interface for "processing" exceptions before they are thrown. This can
    /// be used to add context to the exception, wrap the exception in another one,
    /// totally replace the exception, etc. It is not legal to return null from
    /// Process (unless null was passed in, which really shouldn't happen).
    /// </summary>
#if CPUMATH_INFRASTRUCTURE
    internal interface IExceptionContext
#else
    public interface IExceptionContext
#endif
    {
        TException Process<TException>(TException ex)
            where TException : Exception;
 
        /// <summary>
        /// A string describing the context itself.
        /// </summary>
        string ContextDescription { get; }
    }
 
#if CPUMATH_INFRASTRUCTURE
    [Flags]
    internal enum MessageSensitivity
    {
        None = 0,
        Unknown = ~None
    }
#endif
 
    [BestFriend]
    internal static partial class Contracts
    {
        public const string IsMarkedKey = "ML_IsMarked";
        public const string SensitivityKey = "ML_Sensitivity";
 
        // This is the assert handler. Typically unit tests set a handler that throws
        // a test failure.
        private static volatile Action<string, IExceptionContext> _handler;
 
        private static string GetMsg(string msg, params object[] args)
        {
            try
            {
                msg = string.Format(CultureInfo.InvariantCulture, msg, args);
            }
            catch (FormatException ex)
            {
                Contracts.Assert(false, "Format string arg mismatch: " + ex.Message);
            }
            return msg;
        }
 
        private static int Size<T>(ICollection<T> c)
        {
            return c == null ? 0 : c.Count;
        }
 
        /// <summary>
        /// Does standard processing of an exception (typically called after construction
        /// but before it is thrown).
        /// </summary>
        public static TException Process<TException>(this TException ex, IExceptionContext ectx = null)
            where TException : Exception
        {
            AssertValue(ex);
            AssertValueOrNull(ectx);
            ex = ectx != null ? ectx.Process(ex) : Mark(ex);
            return ex;
        }
 
        /// <summary>
        /// Mark the exception by setting <see cref="IsMarkedKey"/> in the exception
        /// <see cref="Exception.Data"/> to 1.
        /// </summary>
        public static TException Mark<TException>(TException ex)
            where TException : Exception
        {
            AssertValue(ex);
            ex.Data[IsMarkedKey] = 1;
            return ex;
        }
 
        /// <summary>
        /// Indicates whether the exception was "marked" the Contracts code.
        /// </summary>
        public static bool IsMarked(this Exception ex)
        {
            AssertValue(ex);
            return ex.Data.Contains(IsMarkedKey);
        }
 
        /// <summary>
        /// Exceptions whose message communicates potentially sensitive information should be
        /// marked using this method, before they are thrown. Note that if the exception already
        /// had this flag set, the message will be flagged with the bitwise or of the existing
        /// flag, alongside the passed in sensitivity.
        /// </summary>
        public static TException MarkSensitive<TException>(this TException ex, MessageSensitivity sensitivity)
            where TException : Exception
        {
            AssertValue(ex);
            MessageSensitivity innerSensitivity;
            if (!ex.Data.Contains(SensitivityKey))
                innerSensitivity = MessageSensitivity.None;
            else
                innerSensitivity = (ex.Data[SensitivityKey] as MessageSensitivity?) ?? MessageSensitivity.None;
            ex.Data[SensitivityKey] = innerSensitivity | sensitivity;
            return ex;
        }
 
        /// <summary>
        /// This is a convenience method to get the sensitivity of an exception,
        /// as encoded with <see cref="SensitivityKey"/>. If there is no key, then
        /// the message is assumed to be of unknown sensitivity, i.e., it is assumed
        /// that it might contain literally anything.
        /// </summary>
        /// <param name="ex">The exception to query</param>
        /// <returns>The value encoded at the <see cref="SensitivityKey"/>, if it is
        /// a <see cref="MessageSensitivity"/> value. If neither of these conditions
        /// hold then <see cref="MessageSensitivity.Unknown"/> is returned.</returns>
        public static MessageSensitivity Sensitivity(this Exception ex)
        {
            AssertValue(ex);
            if (!ex.Data.Contains(SensitivityKey))
                return MessageSensitivity.Unknown;
            return (ex.Data[SensitivityKey] as MessageSensitivity?) ?? MessageSensitivity.Unknown;
        }
 
#if !CPUMATH_INFRASTRUCTURE
        /// <summary>
        /// This is an internal convenience implementation of an exception context to make marking
        /// exceptions with a specific sensitivity flag a bit less onerous. The alternative to a scheme
        /// like this, where messages are marked through use of <see cref="Process{TException}(TException)"/>,
        /// would be that every check and exception method in this file would need some "peer" where
        /// sensitivity was set. Since there are so many, we have this method instead. I'm not sure if
        /// there will be performance implications. There shouldn't be, since checks rarely happen in
        /// tight loops.
        /// </summary>
        private readonly struct SensitiveExceptionContext : IExceptionContext
        {
            /// <summary>
            /// We will run this instances <see cref="IExceptionContext.Process{TException}(TException)"/> first.
            /// This can be null.
            /// </summary>
            public readonly IExceptionContext Inner;
 
            /// <summary>
            /// Exceptions will be marked with this. If <see cref="Inner"/> happens to mark it with a sensitivity
            /// flag, then the result will not only be this value, but the bitwise or of this with the existing
            /// value.
            /// </summary>
            public readonly MessageSensitivity ToMark;
 
            public string ContextDescription => Inner?.ContextDescription ?? "";
 
            public SensitiveExceptionContext(IExceptionContext inner, MessageSensitivity toMark)
            {
                AssertValueOrNull(inner);
                Inner = inner;
                ToMark = toMark;
            }
 
            public TException Process<TException>(TException ex) where TException : Exception
            {
                CheckValue(ex, nameof(ex));
                ex = Inner?.Process(ex) ?? ex;
                return ex.MarkSensitive(ToMark);
            }
        }
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.None"/>.
        /// </summary>
        public static IExceptionContext NotSensitive() => NotSensitive(null);
 
        // REVIEW: The above could be a property, but then it would look unlike the
        // extension method, and extension properties are still not a thing as of C# 7.2. :(
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.None"/>.
        /// </summary>
        public static IExceptionContext NotSensitive(this IExceptionContext ctx)
            => new SensitiveExceptionContext(ctx, MessageSensitivity.None);
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.UserData"/>.
        /// </summary>
        public static IExceptionContext UserSensitive() => UserSensitive(null);
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.UserData"/>.
        /// </summary>
        public static IExceptionContext UserSensitive(this IExceptionContext ctx)
            => new SensitiveExceptionContext(ctx, MessageSensitivity.UserData);
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.Schema"/>.
        /// </summary>
        public static IExceptionContext SchemaSensitive() => SchemaSensitive(null);
 
        /// <summary>
        /// A convenience context for marking exceptions from checks and excepts with <see cref="MessageSensitivity.Schema"/>.
        /// </summary>
        public static IExceptionContext SchemaSensitive(this IExceptionContext ctx)
            => new SensitiveExceptionContext(ctx, MessageSensitivity.Schema);
#endif
 
        /// <summary>
        /// Sets the assert handler to the given function, returning the previous handler.
        /// </summary>
        public static Action<string, IExceptionContext> SetAssertHandler(Action<string, IExceptionContext> handler)
        {
            return Interlocked.Exchange(ref _handler, handler);
        }
 
        // Standard Exception generation. Note that these do NOT throw the exception,
        // merely construct (and log) it.
        // NOTE: The ordering of arguments to these is standardized to be (when they exist):
        // * inner exception
        // * parameter value of type T
        // * parameter name
        // * message composition - either a single string or format followed by params array
 
        // Default exception type (currently InvalidOperationException)
 
        /// <summary>
        /// Default exception type (currently InvalidOperationException)
        /// </summary>
        public static Exception Except()
            => Process(new InvalidOperationException());
        public static Exception Except(this IExceptionContext ctx)
            => Process(new InvalidOperationException(), ctx);
        public static Exception Except(string msg)
            => Process(new InvalidOperationException(msg));
        public static Exception Except(this IExceptionContext ctx, string msg)
            => Process(new InvalidOperationException(msg), ctx);
        public static Exception Except(string msg, params object[] args)
            => Process(new InvalidOperationException(GetMsg(msg, args)));
        public static Exception Except(this IExceptionContext ctx, string msg, params object[] args)
            => Process(new InvalidOperationException(GetMsg(msg, args)), ctx);
        public static Exception Except(Exception inner, string msg)
            => Process(new InvalidOperationException(msg, inner));
        public static Exception Except(this IExceptionContext ctx, Exception inner, string msg)
            => Process(new InvalidOperationException(msg, inner), ctx);
        public static Exception Except(Exception inner, string msg, params object[] args)
            => Process(new InvalidOperationException(GetMsg(msg, args), inner));
        public static Exception Except(this IExceptionContext ctx, Exception inner, string msg, params object[] args)
            => Process(new InvalidOperationException(GetMsg(msg, args), inner), ctx);
 
        // REVIEW: Change ExceptUser*** to use a custom exception type.
 
        /// <summary>
        /// For signaling bad user input.
        /// </summary>
        public static Exception ExceptUserArg(string name)
            => Process(new ArgumentOutOfRangeException(name));
        public static Exception ExceptUserArg(this IExceptionContext ctx, string name)
            => Process(new ArgumentOutOfRangeException(name), ctx);
        public static Exception ExceptUserArg(string name, string msg)
            => Process(new ArgumentOutOfRangeException(name, msg));
        public static Exception ExceptUserArg(this IExceptionContext ctx, string name, string msg)
            => Process(new ArgumentOutOfRangeException(name, msg), ctx);
        public static Exception ExceptUserArg(string name, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(name, GetMsg(msg, args)));
        public static Exception ExceptUserArg(this IExceptionContext ctx, string name, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(name, GetMsg(msg, args)), ctx);
 
        /// <summary>
        /// For signaling bad function parameters.
        /// </summary>
        public static Exception ExceptParam(string paramName)
            => Process(new ArgumentOutOfRangeException(paramName));
        public static Exception ExceptParam(this IExceptionContext ctx, string paramName)
            => Process(new ArgumentOutOfRangeException(paramName), ctx);
        public static Exception ExceptParam(string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg));
        public static Exception ExceptParam(this IExceptionContext ctx, string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg), ctx);
        public static Exception ExceptParam(string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, GetMsg(msg, args)));
        public static Exception ExceptParam(this IExceptionContext ctx, string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, GetMsg(msg, args)), ctx);
        public static Exception ExceptParamValue<T>(T value, string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(msg, value, paramName));
        public static Exception ExceptParamValue<T>(this IExceptionContext ctx, T value, string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(msg, value, paramName), ctx);
        public static Exception ExceptParamValue<T>(T value, string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, value, GetMsg(msg, args)));
        public static Exception ExceptParamValue<T>(this IExceptionContext ctx, T value, string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, value, GetMsg(msg, args)), ctx);
 
        /// <summary>
        /// For signaling null function parameters.
        /// </summary>
        public static Exception ExceptValue(string paramName)
            => Process(new ArgumentNullException(paramName));
        public static Exception ExceptValue(this IExceptionContext ctx, string paramName)
            => Process(new ArgumentNullException(paramName), ctx);
        public static Exception ExceptValue(string paramName, string msg)
            => Process(new ArgumentNullException(paramName, msg));
        public static Exception ExceptValue(this IExceptionContext ctx, string paramName, string msg)
            => Process(new ArgumentNullException(paramName, msg), ctx);
        public static Exception ExceptValue(string paramName, string msg, params object[] args)
            => Process(new ArgumentNullException(paramName, GetMsg(msg, args)));
        public static Exception ExceptValue(this IExceptionContext ctx, string paramName, string msg, params object[] args)
            => Process(new ArgumentNullException(paramName, GetMsg(msg, args)), ctx);
 
        // For signaling null or empty function parameters (strings, arrays, collections, etc).
 
        /// <summary>
        /// For signaling null or empty function parameters (strings, arrays, collections, etc).
        /// </summary>
        public static Exception ExceptEmpty(string paramName)
            => Process(new ArgumentOutOfRangeException(paramName, string.Format("{0} cannot be null or empty", paramName)));
        public static Exception ExceptEmpty(this IExceptionContext ctx, string paramName)
            => Process(new ArgumentOutOfRangeException(paramName, string.Format("{0} cannot be null or empty", paramName)), ctx);
        public static Exception ExceptEmpty(string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg));
        public static Exception ExceptEmpty(this IExceptionContext ctx, string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg), ctx);
        public static Exception ExceptEmpty(string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, GetMsg(msg, args)));
        public static Exception ExceptEmpty(this IExceptionContext ctx, string paramName, string msg, params object[] args)
            => Process(new ArgumentOutOfRangeException(paramName, GetMsg(msg, args)), ctx);
 
        /// <summary>
        /// For signaling null, empty or white-space function parameters (strings, arrays, collections, etc).
        /// </summary>
        public static Exception ExceptWhiteSpace(string paramName)
            => Process(new ArgumentOutOfRangeException(paramName, string.Format("{0} cannot be null or white space", paramName)));
        public static Exception ExceptWhiteSpace(this IExceptionContext ctx, string paramName)
            => Process(new ArgumentOutOfRangeException(paramName, string.Format("{0} cannot be null or white space", paramName)), ctx);
        public static Exception ExceptWhiteSpace(string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg));
        public static Exception ExceptWhiteSpace(this IExceptionContext ctx, string paramName, string msg)
            => Process(new ArgumentOutOfRangeException(paramName, msg), ctx);
 
        /// <summary>
        /// For signaling errors in decoding information, whether while reading from a file,
        /// parsing user input, etc.
        /// </summary>
        /// <returns></returns>
        public static Exception ExceptDecode()
            => Process(new FormatException());
        public static Exception ExceptDecode(this IExceptionContext ctx)
            => Process(new FormatException(), ctx);
        public static Exception ExceptDecode(string msg)
            => Process(new FormatException(msg));
        public static Exception ExceptDecode(this IExceptionContext ctx, string msg)
            => Process(new FormatException(msg), ctx);
        public static Exception ExceptDecode(string msg, params object[] args)
            => Process(new FormatException(GetMsg(msg, args)));
        public static Exception ExceptDecode(this IExceptionContext ctx, string msg, params object[] args)
            => Process(new FormatException(GetMsg(msg, args)), ctx);
        public static Exception ExceptDecode(Exception inner, string msg)
            => Process(new FormatException(msg, inner));
        public static Exception ExceptDecode(this IExceptionContext ctx, Exception inner, string msg)
            => Process(new FormatException(msg, inner), ctx);
        public static Exception ExceptDecode(Exception inner, string msg, params object[] args)
            => Process(new FormatException(GetMsg(msg, args), inner));
        public static Exception ExceptDecode(this IExceptionContext ctx, Exception inner, string msg, params object[] args)
            => Process(new FormatException(GetMsg(msg, args), inner), ctx);
 
        /// <summary>
        /// For signaling IO failures.
        /// </summary>
        public static Exception ExceptIO()
            => Process(new IOException());
        public static Exception ExceptIO(this IExceptionContext ctx)
            => Process(new IOException(), ctx);
        public static Exception ExceptIO(string msg)
            => Process(new IOException(msg));
        public static Exception ExceptIO(this IExceptionContext ctx, string msg)
            => Process(new IOException(msg), ctx);
        public static Exception ExceptIO(string msg, params object[] args)
            => Process(new IOException(GetMsg(msg, args)));
        public static Exception ExceptIO(this IExceptionContext ctx, string msg, params object[] args)
            => Process(new IOException(GetMsg(msg, args)), ctx);
        public static Exception ExceptIO(Exception inner, string msg)
            => Process(new IOException(msg, inner));
        public static Exception ExceptIO(this IExceptionContext ctx, Exception inner, string msg)
            => Process(new IOException(msg, inner), ctx);
        public static Exception ExceptIO(Exception inner, string msg, params object[] args)
            => Process(new IOException(GetMsg(msg, args), inner));
        public static Exception ExceptIO(this IExceptionContext ctx, Exception inner, string msg, params object[] args)
            => Process(new IOException(GetMsg(msg, args), inner), ctx);
 
        /// <summary>
        /// For signaling functionality that is not YET implemented.
        /// </summary>
        public static Exception ExceptNotImpl()
            => Process(new NotImplementedException());
        public static Exception ExceptNotImpl(this IExceptionContext ctx)
            => Process(new NotImplementedException(), ctx);
        public static Exception ExceptNotImpl(string msg)
            => Process(new NotImplementedException(msg));
        public static Exception ExceptNotImpl(this IExceptionContext ctx, string msg)
            => Process(new NotImplementedException(msg), ctx);
        public static Exception ExceptNotImpl(string msg, params object[] args)
            => Process(new NotImplementedException(GetMsg(msg, args)));
        public static Exception ExceptNotImpl(this IExceptionContext ctx, string msg, params object[] args)
            => Process(new NotImplementedException(GetMsg(msg, args)), ctx);
 
        /// <summary>
        /// For signaling functionality that is not implemented by design.
        /// </summary>
        public static Exception ExceptNotSupp()
            => Process(new NotSupportedException());
        public static Exception ExceptNotSupp(this IExceptionContext ctx)
            => Process(new NotSupportedException(), ctx);
        public static Exception ExceptNotSupp(string msg)
            => Process(new NotSupportedException(msg));
        public static Exception ExceptNotSupp(this IExceptionContext ctx, string msg)
            => Process(new NotSupportedException(msg), ctx);
        public static Exception ExceptNotSupp(string msg, params object[] args)
            => Process(new NotSupportedException(GetMsg(msg, args)));
        public static Exception ExceptNotSupp(this IExceptionContext ctx, string msg, params object[] args)
            => Process(new NotSupportedException(GetMsg(msg, args)), ctx);
 
        /// <summary>
        /// For signaling schema validation issues.
        /// </summary>
        public static Exception ExceptSchemaMismatch(string paramName, string columnRole, string columnName)
            => Process(new ArgumentOutOfRangeException(paramName, MakeSchemaMismatchMsg(columnRole, columnName)));
        public static Exception ExceptSchemaMismatch(this IExceptionContext ctx, string paramName, string columnRole, string columnName)
            => Process(new ArgumentOutOfRangeException(paramName, MakeSchemaMismatchMsg(columnRole, columnName)), ctx);
        public static Exception ExceptSchemaMismatch(string paramName, string columnRole, string columnName, string expectedType, string actualType)
            => Process(new ArgumentOutOfRangeException(paramName, MakeSchemaMismatchMsg(columnRole, columnName, expectedType, actualType)));
        public static Exception ExceptSchemaMismatch(this IExceptionContext ctx, string paramName, string columnRole, string columnName, string expectedType, string actualType)
            => Process(new ArgumentOutOfRangeException(paramName, MakeSchemaMismatchMsg(columnRole, columnName, expectedType, actualType)), ctx);
 
        private static string MakeSchemaMismatchMsg(string columnRole, string columnName, string expectedType = null, string actualType = null)
        {
            if (actualType == null)
                return $"Could not find {columnRole} column '{columnName}'";
            return $"Schema mismatch for {columnRole} column '{columnName}': expected {expectedType}, got {actualType}";
        }
 
        // Check - these check a condition and if it fails, throw the corresponding exception.
        // NOTE: The ordering of arguments to these is standardized to be:
        // * boolean condition
        // * parameter name
        // * parameter value
        // * message string
        //
        // Note that these do NOT support a params array of arguments since that would
        // involve memory allocation whenever the condition is checked. When message string
        // args are need, the condition test should be inlined, eg:
        //   if (!condition)
        //       throw Contracts.ExceptXxx(fmt, arg1, arg2);
 
        public static void Check(bool f)
        {
            if (!f)
                throw Except();
        }
        public static void Check(this IExceptionContext ctx, bool f)
        {
            if (!f)
                throw Except(ctx);
        }
        public static void Check(bool f, string msg)
        {
            if (!f)
                throw Except(msg);
        }
        public static void Check(this IExceptionContext ctx, bool f, string msg)
        {
            if (!f)
                throw Except(ctx, msg);
        }
 
        /// <summary>
        /// CheckUserArg / ExceptUserArg should be used when the validation of user-provided arguments failed.
        /// Typically, this is shortly after the arguments are parsed using CmdParser.
        /// </summary>
        public static void CheckUserArg(bool f, string name)
        {
            if (!f)
                throw ExceptUserArg(name);
        }
        public static void CheckUserArg(this IExceptionContext ctx, bool f, string name)
        {
            if (!f)
                throw ExceptUserArg(ctx, name);
        }
        public static void CheckUserArg(bool f, string name, string msg)
        {
            if (!f)
                throw ExceptUserArg(name, msg);
        }
        public static void CheckUserArg(this IExceptionContext ctx, bool f, string name, string msg)
        {
            if (!f)
                throw ExceptUserArg(ctx, name, msg);
        }
 
        public static void CheckParam(bool f, string paramName)
        {
            if (!f)
                throw ExceptParam(paramName);
        }
        public static void CheckParam(this IExceptionContext ctx, bool f, string paramName)
        {
            if (!f)
                throw ExceptParam(ctx, paramName);
        }
        public static void CheckParam(bool f, string paramName, string msg)
        {
            if (!f)
                throw ExceptParam(paramName, msg);
        }
        public static void CheckParam(this IExceptionContext ctx, bool f, string paramName, string msg)
        {
            if (!f)
                throw ExceptParam(ctx, paramName, msg);
        }
        public static void CheckParamValue<T>(bool f, T value, string paramName, string msg)
        {
            if (!f)
                throw ExceptParamValue(value, paramName, msg);
        }
        public static void CheckParamValue<T>(this IExceptionContext ctx, bool f, T value, string paramName, string msg)
        {
            if (!f)
                throw ExceptParamValue(ctx, value, paramName, msg);
        }
 
        public static T CheckRef<T>(T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(paramName);
            return val;
        }
        public static T CheckRef<T>(this IExceptionContext ctx, T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(ctx, paramName);
            return val;
        }
 
        public static T CheckRef<T>(this IExceptionContext ctx, T val, string paramName, string msg) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(ctx, paramName, msg);
            return val;
        }
 
        public static void CheckValue<T>(T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(paramName);
        }
        public static void CheckValue<T>(this IExceptionContext ctx, T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(ctx, paramName);
        }
        public static T CheckValue<T>(T val, string paramName, string msg) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(paramName, msg);
            return val;
        }
        public static T CheckValue<T>(this IExceptionContext ctx, T val, string paramName, string msg) where T : class
        {
            if (object.ReferenceEquals(val, null))
                throw ExceptValue(ctx, paramName, msg);
            return val;
        }
 
        public static string CheckNonEmpty(string s, string paramName)
        {
            if (string.IsNullOrEmpty(s))
                throw ExceptEmpty(paramName);
            return s;
        }
        public static string CheckNonEmpty(this IExceptionContext ctx, string s, string paramName)
        {
            if (string.IsNullOrEmpty(s))
                throw ExceptEmpty(ctx, paramName);
            return s;
        }
 
        public static string CheckNonWhiteSpace(string s, string paramName)
        {
            if (string.IsNullOrWhiteSpace(s))
                throw ExceptWhiteSpace(paramName);
            return s;
        }
        public static string CheckNonWhiteSpace(this IExceptionContext ctx, string s, string paramName)
        {
            if (string.IsNullOrWhiteSpace(s))
                throw ExceptWhiteSpace(ctx, paramName);
            return s;
        }
 
        public static string CheckNonEmpty(string s, string paramName, string msg)
        {
            if (string.IsNullOrEmpty(s))
                throw ExceptEmpty(paramName, msg);
            return s;
        }
        public static string CheckNonEmpty(this IExceptionContext ctx, string s, string paramName, string msg)
        {
            if (string.IsNullOrEmpty(s))
                throw ExceptEmpty(ctx, paramName, msg);
            return s;
        }
 
        public static string CheckNonWhiteSpace(string s, string paramName, string msg)
        {
            if (string.IsNullOrWhiteSpace(s))
                throw ExceptWhiteSpace(paramName, msg);
            return s;
        }
        public static string CheckNonWhiteSpace(this IExceptionContext ctx, string s, string paramName, string msg)
        {
            if (string.IsNullOrWhiteSpace(s))
                throw ExceptWhiteSpace(ctx, paramName, msg);
            return s;
        }
 
        public static T[] CheckNonEmpty<T>(T[] args, string paramName)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(paramName);
            return args;
        }
        public static T[] CheckNonEmpty<T>(this IExceptionContext ctx, T[] args, string paramName)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(ctx, paramName);
            return args;
        }
        public static T[] CheckNonEmpty<T>(T[] args, string paramName, string msg)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(paramName, msg);
            return args;
        }
        public static T[] CheckNonEmpty<T>(this IExceptionContext ctx, T[] args, string paramName, string msg)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(ctx, paramName, msg);
            return args;
        }
        public static ICollection<T> CheckNonEmpty<T>(ICollection<T> args, string paramName)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(paramName);
            return args;
        }
        public static ICollection<T> CheckNonEmpty<T>(this IExceptionContext ctx, ICollection<T> args, string paramName)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(ctx, paramName);
            return args;
        }
        public static ICollection<T> CheckNonEmpty<T>(ICollection<T> args, string paramName, string msg)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(paramName, msg);
            return args;
        }
        public static ICollection<T> CheckNonEmpty<T>(this IExceptionContext ctx, ICollection<T> args, string paramName, string msg)
        {
            if (Size(args) == 0)
                throw ExceptEmpty(ctx, paramName, msg);
            return args;
        }
 
        public static void CheckDecode(bool f)
        {
            if (!f)
                throw ExceptDecode();
        }
        public static void CheckDecode(this IExceptionContext ctx, bool f)
        {
            if (!f)
                throw ExceptDecode(ctx);
        }
        public static void CheckDecode(bool f, string msg)
        {
            if (!f)
                throw ExceptDecode(msg);
        }
        public static void CheckDecode(this IExceptionContext ctx, bool f, string msg)
        {
            if (!f)
                throw ExceptDecode(ctx, msg);
        }
 
        public static void CheckIO(bool f)
        {
            if (!f)
                throw ExceptIO();
        }
        public static void CheckIO(this IExceptionContext ctx, bool f)
        {
            if (!f)
                throw ExceptIO(ctx);
        }
        public static void CheckIO(bool f, string msg)
        {
            if (!f)
                throw ExceptIO(msg);
        }
        public static void CheckIO(this IExceptionContext ctx, bool f, string msg)
        {
            if (!f)
                throw ExceptIO(ctx, msg);
        }
 
        public static void CheckIO(this IExceptionContext ctx, bool f, string msg, params object[] args)
        {
            if (!f)
                throw ExceptIO(ctx, msg, args);
        }
#if !CPUMATH_INFRASTRUCTURE
        /// <summary>
        /// Check state of the host and throw exception if host marked to stop all execution.
        /// </summary>
        public static void CheckAlive(this IHostEnvironment env)
        {
            if (env is ICancelable cancelableEnv && cancelableEnv.IsCanceled)
                throw Process(new OperationCanceledException("Operation was canceled."), env);
        }
#endif
        /// <summary>
        /// This documents that the parameter can legally be null.
        /// </summary>
        [Conditional("INVARIANT_CHECKS")]
        public static void CheckValueOrNull<T>(T val) where T : class
        {
        }
 
        /// <summary>
        /// This documents that the parameter can legally be null.
        /// </summary>
        [Conditional("INVARIANT_CHECKS")]
        public static void CheckValueOrNull<T>(this IExceptionContext ctx, T val) where T : class
        {
        }
 
        // Assert
 
        #region Private assert handling
 
        private static void DbgFailCore(string msg, IExceptionContext ctx = null)
        {
            var handler = _handler;
 
            if (handler != null)
                handler(msg, ctx);
            else if (ctx != null)
                Debug.Fail(msg, ctx.ContextDescription);
            else
                Debug.Fail(msg);
        }
 
        private static void DbgFail(IExceptionContext ctx = null)
        {
            DbgFailCore("Assertion Failed", ctx);
        }
        private static void DbgFail(string msg)
        {
            DbgFailCore(msg);
        }
        private static void DbgFail(IExceptionContext ctx, string msg)
        {
            DbgFailCore(msg, ctx);
        }
        private static void DbgFailValue(IExceptionContext ctx = null)
        {
            DbgFailCore("Non-null assertion failure", ctx);
        }
        private static void DbgFailValue(string paramName)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-null assertion failure: {0}", paramName));
        }
        private static void DbgFailValue(IExceptionContext ctx, string paramName)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-null assertion failure: {0}", paramName), ctx);
        }
        private static void DbgFailValue(string paramName, string msg)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-null assertion failure: {0}: {1}", paramName, msg));
        }
        private static void DbgFailValue(IExceptionContext ctx, string paramName, string msg)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-null assertion failure: {0}: {1}", paramName, msg), ctx);
        }
        private static void DbgFailEmpty(IExceptionContext ctx = null)
        {
            DbgFailCore("Non-empty assertion failure", ctx);
        }
        private static void DbgFailEmpty(string msg)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-empty assertion failure: {0}", msg));
        }
        private static void DbgFailEmpty(IExceptionContext ctx, string msg)
        {
            DbgFailCore(string.Format(CultureInfo.InvariantCulture, "Non-empty assertion failure: {0}", msg), ctx);
        }
 
        #endregion Private assert handling
 
        [Conditional("DEBUG")]
        public static void Assert(bool f)
        {
            if (!f)
                DbgFail();
        }
        [Conditional("DEBUG")]
        public static void Assert(this IExceptionContext ctx, bool f)
        {
            if (!f)
                DbgFail(ctx);
        }
 
        [Conditional("DEBUG")]
        public static void Assert(bool f, string msg)
        {
            if (!f)
                DbgFail(msg);
        }
        [Conditional("DEBUG")]
        public static void Assert(this IExceptionContext ctx, bool f, string msg)
        {
            if (!f)
                DbgFail(ctx, msg);
        }
 
        [Conditional("DEBUG")]
        public static void AssertValue<T>(T val) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue();
        }
        [Conditional("DEBUG")]
        public static void AssertValue<T>(this IExceptionContext ctx, T val) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue(ctx);
        }
 
        [Conditional("DEBUG")]
        public static void AssertValue<T>(T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue(paramName);
        }
 
        [Conditional("DEBUG")]
        public static void AssertValue<T>(this IExceptionContext ctx, T val, string paramName) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue(ctx, paramName);
        }
 
        [Conditional("DEBUG")]
        public static void AssertValue<T>(T val, string name, string msg) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue(name, msg);
        }
        [Conditional("DEBUG")]
        public static void AssertValue<T>(this IExceptionContext ctx, T val, string name, string msg) where T : class
        {
            if (object.ReferenceEquals(val, null))
                DbgFailValue(ctx, name, msg);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonEmpty(string s)
        {
            if (string.IsNullOrEmpty(s))
                DbgFailEmpty();
        }
        [Conditional("DEBUG")]
        public static void AssertNonEmpty(this IExceptionContext ctx, string s)
        {
            if (string.IsNullOrEmpty(s))
                DbgFailEmpty(ctx);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonWhiteSpace(string s)
        {
            if (string.IsNullOrWhiteSpace(s))
                DbgFailEmpty();
        }
        [Conditional("DEBUG")]
        public static void AssertNonWhiteSpace(this IExceptionContext ctx, string s)
        {
            if (string.IsNullOrWhiteSpace(s))
                DbgFailEmpty(ctx);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonEmpty(string s, string msg)
        {
            if (string.IsNullOrEmpty(s))
                DbgFailEmpty(msg);
        }
        [Conditional("DEBUG")]
        public static void AssertNonEmpty(this IExceptionContext ctx, string s, string msg)
        {
            if (string.IsNullOrEmpty(s))
                DbgFailEmpty(ctx, msg);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonWhiteSpace(string s, string msg)
        {
            if (string.IsNullOrWhiteSpace(s))
                DbgFailEmpty(msg);
        }
        [Conditional("DEBUG")]
        public static void AssertNonWhiteSpace(this IExceptionContext ctx, string s, string msg)
        {
            if (string.IsNullOrWhiteSpace(s))
                DbgFailEmpty(ctx, msg);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(ReadOnlySpan<T> args)
        {
            if (args.IsEmpty)
                DbgFail();
        }
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(Span<T> args)
        {
            if (args.IsEmpty)
                DbgFail();
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(ICollection<T> args)
        {
            if (Size(args) == 0)
                DbgFail();
        }
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(this IExceptionContext ctx, ICollection<T> args)
        {
            if (Size(args) == 0)
                DbgFail(ctx);
        }
 
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(ICollection<T> args, string msg)
        {
            if (Size(args) == 0)
                DbgFail(msg);
        }
        [Conditional("DEBUG")]
        public static void AssertNonEmpty<T>(this IExceptionContext ctx, ICollection<T> args, string msg)
        {
            if (Size(args) == 0)
                DbgFail(ctx, msg);
        }
 
        /// <summary>
        /// This documents that the parameter can legally be null.
        /// </summary>
        [Conditional("INVARIANT_CHECKS")]
        public static void AssertValueOrNull<T>(T val) where T : class
        {
        }
        [Conditional("INVARIANT_CHECKS")]
        public static void AssertValueOrNull<T>(this IExceptionContext ctx, T val) where T : class
        {
        }
    }
}