1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using System.Runtime;
10 using System.Runtime.ExceptionServices;
11 using System.Runtime.InteropServices;
12 using System.Runtime.Versioning;
13 using System.Text;
14 
15 namespace System
16 {
17     public static partial class AppContext
18     {
19         [Flags]
20         private enum SwitchValueState
21         {
22             HasFalseValue = 0x1,
23             HasTrueValue = 0x2,
24             HasLookedForOverride = 0x4,
25             UnknownValue = 0x8 // Has no default and could not find an override
26         }
27         private static readonly Dictionary<string, SwitchValueState> s_switchMap = new Dictionary<string, SwitchValueState>();
28         private static Dictionary<String, Object> s_localStore = new Dictionary<String, Object>();
29         private static string s_defaultBaseDirectory;
30         // AppDomain lives in CoreFX, but some of this class's events need to pass in AppDomains, so people registering those
31         // events need to first pass in an AppDomain that we stash here to pass back in the events.
32         private static object s_appDomain;
33 
AppContext()34         static AppContext()
35         {
36             // populate the AppContext with the default set of values
37             AppContextDefaultValues.PopulateDefaultValues();
38         }
39 
SetAppDomain(object appDomain)40         public static void SetAppDomain(object appDomain)
41         {
42             s_appDomain = appDomain;
43         }
44 
45         public static string TargetFrameworkName => Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
46 
47         public static string BaseDirectory
48         {
49             get
50             {
51                 // The value of APP_CONTEXT_BASE_DIRECTORY key has to be a string and it is not allowed to be any other type.
52                 // Otherwise the caller will get invalid cast exception
53                 return (string)GetData("APP_CONTEXT_BASE_DIRECTORY") ??
54                     (s_defaultBaseDirectory ?? (s_defaultBaseDirectory = GetBaseDirectoryCore()));
55             }
56         }
57 
GetData(string name)58         public static object GetData(string name)
59         {
60             if (name == null)
61                 throw new ArgumentNullException(nameof(name));
62 
63             object data;
64             lock (((ICollection)s_localStore).SyncRoot)
65             {
66                 s_localStore.TryGetValue(name, out data);
67             }
68 
69             return data;
70         }
71 
SetData(string name, object data)72         public static void SetData(string name, object data)
73         {
74             if (name == null)
75                 throw new ArgumentNullException(nameof(name));
76 
77             lock (((ICollection)s_localStore).SyncRoot)
78             {
79                 s_localStore[name] = data;
80             }
81         }
82 
83         public static event UnhandledExceptionEventHandler UnhandledException;
84 
85         public static event System.EventHandler<FirstChanceExceptionEventArgs> FirstChanceException;
86 
87         public static event System.EventHandler ProcessExit;
88         internal static event System.EventHandler Unloading;
89 
OnUnhandledException(object sender, UnhandledExceptionEventArgs e)90         private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
91         {
92             var unhandledException = UnhandledException;
93             if (unhandledException != null)
94             {
95                 unhandledException(sender, e);
96             }
97         }
98 
OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)99         internal static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
100         {
101             var firstChanceException = FirstChanceException;
102             if (firstChanceException != null)
103             {
104                 firstChanceException(sender, e);
105             }
106         }
107 
108         [RuntimeExport("OnFirstChanceException")]
OnFirstChanceException(object e)109         internal static void OnFirstChanceException(object e)
110         {
111             OnFirstChanceException(s_appDomain, new FirstChanceExceptionEventArgs((Exception)e));
112         }
113 
OnProcessExit(object sender, EventArgs e)114         private static void OnProcessExit(object sender, EventArgs e)
115         {
116             var processExit = ProcessExit;
117             if (processExit != null)
118             {
119                 processExit(sender, e);
120             }
121         }
122 
OnUnloading(object sender, EventArgs e)123         private static void OnUnloading(object sender, EventArgs e)
124         {
125             var unloading = Unloading;
126             if (unloading != null)
127             {
128                 unloading(sender, e);
129             }
130         }
131 
132         #region Switch APIs
133         /// <summary>
134         /// Try to get the value of the switch.
135         /// </summary>
136         /// <param name="switchName">The name of the switch</param>
137         /// <param name="isEnabled">A variable where to place the value of the switch</param>
138         /// <returns>A return value of true represents that the switch was set and <paramref name="isEnabled"/> contains the value of the switch</returns>
TryGetSwitch(string switchName, out bool isEnabled)139         public static bool TryGetSwitch(string switchName, out bool isEnabled)
140         {
141             if (switchName == null)
142                 throw new ArgumentNullException(nameof(switchName));
143             if (switchName.Length == 0)
144                 throw new ArgumentException(SR.Argument_EmptyName, nameof(switchName));
145 
146             // By default, the switch is not enabled.
147             isEnabled = false;
148 
149             SwitchValueState switchValue;
150             lock (s_switchMap)
151             {
152                 if (s_switchMap.TryGetValue(switchName, out switchValue))
153                 {
154                     // The value is in the dictionary.
155                     // There are 3 cases here:
156                     // 1. The value of the switch is 'unknown'. This means that the switch name is not known to the system (either via defaults or checking overrides).
157                     //    Example: This is the case when, during a servicing event, a switch is added to System.Xml which ships before mscorlib. The value of the switch
158                     //             Will be unknown to mscorlib.dll and we want to prevent checking the overrides every time we check this switch
159                     // 2. The switch has a valid value AND we have read the overrides for it
160                     //    Example: TryGetSwitch is called for a switch set via SetSwitch
161                     // 3. The switch has the default value and we need to check for overrides
162                     //    Example: TryGetSwitch is called for the first time for a switch that has a default value
163 
164                     // 1. The value is unknown
165                     if (switchValue == SwitchValueState.UnknownValue)
166                     {
167                         isEnabled = false;
168                         return false;
169                     }
170 
171                     // We get the value of isEnabled from the value that we stored in the dictionary
172                     isEnabled = (switchValue & SwitchValueState.HasTrueValue) == SwitchValueState.HasTrueValue;
173 
174                     // 2. The switch has a valid value AND we have checked for overrides
175                     if ((switchValue & SwitchValueState.HasLookedForOverride) == SwitchValueState.HasLookedForOverride)
176                     {
177                         return true;
178                     }
179 
180                     // Update the switch in the dictionary to mark it as 'checked for override'
181                     s_switchMap[switchName] = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue)
182                                                 | SwitchValueState.HasLookedForOverride;
183 
184                     return true;
185                 }
186                 else
187                 {
188                     // The value is NOT in the dictionary
189                     // In this case we need to see if we have an override defined for the value.
190                     // There are 2 cases:
191                     // 1. The value has an override specified. In this case we need to add the value to the dictionary
192                     //    and mark it as checked for overrides
193                     //    Example: In a servicing event, System.Xml introduces a switch and an override is specified.
194                     //             The value is not found in mscorlib (as System.Xml ships independent of mscorlib)
195                     // 2. The value does not have an override specified
196                     //    In this case, we want to capture the fact that we looked for a value and found nothing by adding
197                     //    an entry in the dictionary with the 'sentinel' value of 'SwitchValueState.UnknownValue'.
198                     //    Example: This will prevent us from trying to find overrides for values that we don't have in the dictionary
199 
200                     // 1. The value has an override specified.
201                     bool overrideValue;
202                     if (AppContextDefaultValues.TryGetSwitchOverride(switchName, out overrideValue))
203                     {
204                         isEnabled = overrideValue;
205 
206                         // Update the switch in the dictionary to mark it as 'checked for override'
207                         s_switchMap[switchName] = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue)
208                                                     | SwitchValueState.HasLookedForOverride;
209 
210                         return true;
211                     }
212 
213                     // 2. The value does not have an override.
214                     s_switchMap[switchName] = SwitchValueState.UnknownValue;
215                 }
216             }
217             return false; // we did not find a value for the switch
218         }
219 
220         /// <summary>
221         /// Assign a switch a value
222         /// </summary>
223         /// <param name="switchName">The name of the switch</param>
224         /// <param name="isEnabled">The value to assign</param>
SetSwitch(string switchName, bool isEnabled)225         public static void SetSwitch(string switchName, bool isEnabled)
226         {
227             if (switchName == null)
228                 throw new ArgumentNullException(nameof(switchName));
229             if (switchName.Length == 0)
230                 throw new ArgumentException(SR.Argument_EmptyName, nameof(switchName));
231 
232             SwitchValueState switchValue = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue)
233                                             | SwitchValueState.HasLookedForOverride;
234 
235             lock (s_switchMap)
236             {
237                 // Store the new value and the fact that we checked in the dictionary
238                 s_switchMap[switchName] = switchValue;
239             }
240         }
241 
242         /// <summary>
243         /// This method is going to be called from the AppContextDefaultValues class when setting up the
244         /// default values for the switches. !!!! This method is called during the static constructor so it does not
245         /// take a lock !!!! If you are planning to use this outside of that, please ensure proper locking.
246         /// </summary>
DefineSwitchDefault(string switchName, bool isEnabled)247         internal static void DefineSwitchDefault(string switchName, bool isEnabled)
248         {
249             s_switchMap[switchName] = isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue;
250         }
251         #endregion
252     }
253 }
254