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