File: src\runtime\src\libraries\System.Private.CoreLib\src\System\AppContext.cs
Web Access
Project: System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Versioning;
using System.Threading;

namespace System
{
    public static partial class AppContext
    {
        private static Dictionary<string, object?>? s_dataStore
#if NATIVEAOT
            = InitializeDataStore()
#endif
            ;
        private static Dictionary<string, bool>? s_switches;
        private static string? s_defaultBaseDirectory;

        public static string BaseDirectory =>
            // The value of APP_CONTEXT_BASE_DIRECTORY key has to be a string and it is not allowed to be any other type.
            // Otherwise the caller will get invalid cast exception
            GetData("APP_CONTEXT_BASE_DIRECTORY") as string ??
            (s_defaultBaseDirectory ??= GetBaseDirectoryCore());

        public static string? TargetFrameworkName =>
            // The Target framework is not the framework that the process is actually running on.
            // It is the value read from the TargetFrameworkAttribute on the .exe that started the process.
            Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;

        public static object? GetData(string name)
        {
            ArgumentNullException.ThrowIfNull(name);

            if (s_dataStore == null)
                return null;

            lock (s_dataStore)
            {
                if (s_dataStore.TryGetValue(name, out object? data))
                    return data;
            }

#if !MONO && !NATIVEAOT
            if (IsKnownHostProperty(name))
            {
                string? value = null;
                if (TryGetHostPropertyValue(name, new StringHandleOnStack(ref value)))
                {
                    lock (s_dataStore)
                    {
                        if (s_dataStore.TryGetValue(name, out object? existing))
                        {
                            Debug.Assert(existing is string existingValue && existingValue == value);
                            return existing;
                        }

                        s_dataStore[name] = value;
                        return value;
                    }
                }
            }
#endif

            return null;
        }

        /// <summary>
        /// Sets the value of the named data element assigned to the current application domain.
        /// </summary>
        /// <param name="name">The name of the data element</param>
        /// <param name="data">The value of <paramref name="name"/></param>
        /// <exception cref="ArgumentNullException">If <paramref name="name"/> is <see langword="null"/></exception>
        public static void SetData(string name, object? data)
        {
            ArgumentNullException.ThrowIfNull(name);

            if (s_dataStore == null)
            {
                Interlocked.CompareExchange(ref s_dataStore, new Dictionary<string, object?>(), null);
            }

            lock (s_dataStore)
            {
                s_dataStore[name] = data;
            }
        }

#pragma warning disable CS0067 // events raised by the VM
#if MONO
        [field: DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(UnhandledExceptionEventArgs))]
#endif
        internal static event UnhandledExceptionEventHandler? UnhandledException;

#if MONO
        [field: DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(FirstChanceExceptionEventArgs))]
#endif
        internal static event EventHandler<FirstChanceExceptionEventArgs>? FirstChanceException;
#pragma warning restore CS0067

#if NATIVEAOT
        [System.Runtime.RuntimeExport("OnUnhandledException")]
#endif
        internal static void OnUnhandledException(object e)
        {
#if NATIVEAOT
            RuntimeExceptionHelpers.SerializeCrashInfo(System.Runtime.RhFailFastReason.UnhandledException, (e as Exception)?.Message, e as Exception);
#endif
            if (UnhandledException is UnhandledExceptionEventHandler handlers)
            {
                UnhandledExceptionEventArgs args = new(e, isTerminating: true);
                foreach (UnhandledExceptionEventHandler handler in Delegate.EnumerateInvocationList(handlers))
                {
                    try
                    {
                        handler(/* AppDomain */ null!, args);
                    }
                    catch
                    {
                    }
                }
            }
        }

        [ThreadStatic]
        private static bool t_deliveringFirstChanceNotification;

        private static void OnFirstChanceException(Exception e, object? sender)
        {
            if (FirstChanceException is EventHandler<FirstChanceExceptionEventArgs> handlers)
            {
                // Guard against reentrancy. Allocating the event args below or running a
                // handler may itself throw (e.g. OutOfMemoryException in a low-memory
                // situation). That exception would trigger another first-chance
                // notification on this same thread, allocate again, throw again, and
                // recurse until the stack overflows. Skip nested notifications to break
                // the recursion.
                if (t_deliveringFirstChanceNotification)
                {
                    return;
                }

                t_deliveringFirstChanceNotification = true;
                try
                {
                    FirstChanceExceptionEventArgs args = new(e);

                    foreach (EventHandler<FirstChanceExceptionEventArgs> handler in Delegate.EnumerateInvocationList(handlers))
                    {
                        try
                        {
                            handler(sender, args);
                        }
                        catch
                        {
                        }
                    }
                }
                finally
                {
                    t_deliveringFirstChanceNotification = false;
                }
            }
        }

        internal static void OnProcessExit()
        {
            AssemblyLoadContext.OnProcessExit();
            if (EventSource.IsSupported)
            {
                EventListener.DisposeOnShutdown();
            }
            AppDomain.OnProcessExit();
        }

        /// <summary>
        /// Try to get the value of the switch.
        /// </summary>
        /// <param name="switchName">The name of the switch</param>
        /// <param name="isEnabled">A variable where to place the value of the switch</param>
        /// <returns>A return value of true represents that the switch was set and <paramref name="isEnabled"/> contains the value of the switch</returns>
        public static bool TryGetSwitch(string switchName, out bool isEnabled)
        {
            ArgumentException.ThrowIfNullOrEmpty(switchName);

            if (s_switches != null)
            {
                lock (s_switches)
                {
                    if (s_switches.TryGetValue(switchName, out isEnabled))
                        return true;
                }
            }

            if (GetData(switchName) is string value && bool.TryParse(value, out isEnabled))
            {
                return true;
            }

            isEnabled = false;
            return false;
        }

        /// <summary>
        /// Assign a switch a value
        /// </summary>
        /// <param name="switchName">The name of the switch</param>
        /// <param name="isEnabled">The value to assign</param>
        public static void SetSwitch(string switchName, bool isEnabled)
        {
            ArgumentException.ThrowIfNullOrEmpty(switchName);

            if (s_switches == null)
            {
                // Compatibility switches are rarely used. Initialize the Dictionary lazily
                Interlocked.CompareExchange(ref s_switches, new Dictionary<string, bool>(), null);
            }

            lock (s_switches)
            {
                s_switches[switchName] = isEnabled;
            }
        }

#if MONO
        internal static unsafe void Setup(char** pNames, uint* pNameLengths, char** pValues, uint* pValueLengths, int count)
        {
            Debug.Assert(s_dataStore == null, "s_dataStore is not expected to be inited before Setup is called");
            s_dataStore = new Dictionary<string, object?>(count);
            for (int i = 0; i < count; i++)
            {
                s_dataStore.Add(new string(pNames[i], 0, (int)pNameLengths[i]), new string(pValues[i], 0, (int)pValueLengths[i]));
            }
        }
#elif !NATIVEAOT
        [UnmanagedCallersOnly]
        internal static unsafe void Setup(char** pNames, char** pValues, int count, Exception* pException)
        {
            try
            {
                Debug.Assert(s_dataStore == null, "s_dataStore is not expected to be inited before Setup is called");
                s_dataStore = new Dictionary<string, object?>(count);
                for (int i = 0; i < count; i++)
                {
                    string name = new string(pNames[i]);

                    // Avoid retaining a managed string copy of known host properties.
                    // They will be retrieved if explicitly requested.
                    if (IsKnownHostProperty(name))
                        continue;

                    s_dataStore.Add(name, new string(pValues[i]));
                }
            }
            catch (Exception ex)
            {
                *pException = ex;
            }
        }
#endif
    }
}