diff options
Diffstat (limited to 'src/mscorlib/src/System/AppContext')
6 files changed, 588 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/AppContext/AppContext.cs b/src/mscorlib/src/System/AppContext/AppContext.cs new file mode 100644 index 0000000000..0b0643d7b4 --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContext.cs @@ -0,0 +1,189 @@ +// 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. + + +using System.Collections.Generic; + +namespace System +{ + public static class AppContext + { + [Flags] + private enum SwitchValueState + { + HasFalseValue = 0x1, + HasTrueValue = 0x2, + HasLookedForOverride = 0x4, + UnknownValue = 0x8 // Has no default and could not find an override + } + private static readonly Dictionary<string, SwitchValueState> s_switchMap = new Dictionary<string, SwitchValueState>(); + + public static string BaseDirectory + { +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] +#endif + get + { + // 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 + return (string) AppDomain.CurrentDomain.GetData("APP_CONTEXT_BASE_DIRECTORY") ?? AppDomain.CurrentDomain.BaseDirectory; + } + } + + public static string TargetFrameworkName + { + get + { + // Forward the value that is set on the current domain. + return AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName; + } + } + +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] +#endif + public static object GetData(string name) + { + return AppDomain.CurrentDomain.GetData(name); + } + + #region Switch APIs + static AppContext() + { + // populate the AppContext with the default set of values + AppContextDefaultValues.PopulateDefaultValues(); + } + + /// <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) + { + if (switchName == null) + throw new ArgumentNullException("switchName"); + if (switchName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "switchName"); + + // By default, the switch is not enabled. + isEnabled = false; + + SwitchValueState switchValue; + lock (s_switchMap) + { + if (s_switchMap.TryGetValue(switchName, out switchValue)) + { + // The value is in the dictionary. + // There are 3 cases here: + // 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). + // 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 + // Will be unknown to mscorlib.dll and we want to prevent checking the overrides every time we check this switch + // 2. The switch has a valid value AND we have read the overrides for it + // Example: TryGetSwitch is called for a switch set via SetSwitch + // 3. The switch has the default value and we need to check for overrides + // Example: TryGetSwitch is called for the first time for a switch that has a default value + + // 1. The value is unknown + if (switchValue == SwitchValueState.UnknownValue) + { + isEnabled = false; + return false; + } + + // We get the value of isEnabled from the value that we stored in the dictionary + isEnabled = (switchValue & SwitchValueState.HasTrueValue) == SwitchValueState.HasTrueValue; + + // 2. The switch has a valid value AND we have checked for overrides + if ((switchValue & SwitchValueState.HasLookedForOverride) == SwitchValueState.HasLookedForOverride) + { + return true; + } + + // 3. The switch has a valid value, but we need to check for overrides. + // Regardless of whether or not the switch has an override, we need to update the value to reflect + // the fact that we checked for overrides. + bool overrideValue; + if (AppContextDefaultValues.TryGetSwitchOverride(switchName, out overrideValue)) + { + // we found an override! + isEnabled = overrideValue; + } + + // Update the switch in the dictionary to mark it as 'checked for override' + s_switchMap[switchName] = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue) + | SwitchValueState.HasLookedForOverride; + + return true; + } + else + { + // The value is NOT in the dictionary + // In this case we need to see if we have an override defined for the value. + // There are 2 cases: + // 1. The value has an override specified. In this case we need to add the value to the dictionary + // and mark it as checked for overrides + // Example: In a servicing event, System.Xml introduces a switch and an override is specified. + // The value is not found in mscorlib (as System.Xml ships independent of mscorlib) + // 2. The value does not have an override specified + // In this case, we want to capture the fact that we looked for a value and found nothing by adding + // an entry in the dictionary with the 'sentinel' value of 'SwitchValueState.UnknownValue'. + // Example: This will prevent us from trying to find overrides for values that we don't have in the dictionary + + // 1. The value has an override specified. + bool overrideValue; + if (AppContextDefaultValues.TryGetSwitchOverride(switchName, out overrideValue)) + { + isEnabled = overrideValue; + + // Update the switch in the dictionary to mark it as 'checked for override' + s_switchMap[switchName] = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue) + | SwitchValueState.HasLookedForOverride; + + return true; + } + + // 2. The value does not have an override. + s_switchMap[switchName] = SwitchValueState.UnknownValue; + } + } + return false; // we did not find a value for the switch + } + + /// <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) + { + if (switchName == null) + throw new ArgumentNullException("switchName"); + if (switchName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "switchName"); + + SwitchValueState switchValue = (isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue) + | SwitchValueState.HasLookedForOverride; + + lock (s_switchMap) + { + // Store the new value and the fact that we checked in the dictionary + s_switchMap[switchName] = switchValue; + } + } + + /// <summary> + /// This method is going to be called from the AppContextDefaultValues class when setting up the + /// default values for the switches. !!!! This method is called during the static constructor so it does not + /// take a lock !!!! If you are planning to use this outside of that, please ensure proper locking. + /// </summary> + internal static void DefineSwitchDefault(string switchName, bool isEnabled) + { + s_switchMap[switchName] = isEnabled ? SwitchValueState.HasTrueValue : SwitchValueState.HasFalseValue; + } + #endregion + } +} diff --git a/src/mscorlib/src/System/AppContext/AppContextDefaultValues.CoreClrOverrides.cs b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.CoreClrOverrides.cs new file mode 100644 index 0000000000..89893c6bee --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.CoreClrOverrides.cs @@ -0,0 +1,21 @@ +// 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. + +namespace System +{ + internal static partial class AppContextDefaultValues + { + static partial void TryGetSwitchOverridePartial(string switchName, ref bool overrideFound, ref bool overrideValue) + { + overrideFound = false; + overrideValue = false; + + string value = AppContext.GetData(switchName) as string; + if (value != null) + { + overrideFound = bool.TryParse(value, out overrideValue); + } + } + } +} diff --git a/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.Central.cs b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.Central.cs new file mode 100644 index 0000000000..92c9917113 --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.Central.cs @@ -0,0 +1,33 @@ +// 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. + + +// +// This file is used to provide an implementation for defining a default value +// This should be compiled only in mscorlib where the AppContext class is available +// + +namespace System +{ + internal static partial class AppContextDefaultValues + { + /// <summary> + /// This method allows reading the override for a switch. + /// The implementation is platform specific + /// </summary> + public static bool TryGetSwitchOverride(string switchName, out bool overrideValue) + { + // The default value for a switch is 'false' + overrideValue = false; + + // Read the override value + bool overrideFound = false; + + // This partial method will be removed if there are no implementations of it. + TryGetSwitchOverridePartial(switchName, ref overrideFound, ref overrideValue); + + return overrideFound; + } + } +} diff --git a/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.cs b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.cs new file mode 100644 index 0000000000..c80913e3a6 --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.cs @@ -0,0 +1,71 @@ +// 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. + +using System; + +namespace System +{ + internal static partial class AppContextDefaultValues + { + + internal static readonly string SwitchNoAsyncCurrentCulture = "Switch.System.Globalization.NoAsyncCurrentCulture"; + internal static readonly string SwitchThrowExceptionIfDisposedCancellationTokenSource = "Switch.System.Threading.ThrowExceptionIfDisposedCancellationTokenSource"; + internal static readonly string SwitchPreserveEventListnerObjectIdentity = "Switch.System.Diagnostics.EventSource.PreserveEventListnerObjectIdentity"; +#if FEATURE_PATHCOMPAT + internal static readonly string SwitchUseLegacyPathHandling = "Switch.System.IO.UseLegacyPathHandling"; + internal static readonly string SwitchBlockLongPaths = "Switch.System.IO.BlockLongPaths"; +#endif + + // This is a partial method. Platforms can provide an implementation of it that will set override values + // from whatever mechanism is available on that platform. If no implementation is provided, the compiler is going to remove the calls + // to it from the code + // We are going to have an implementation of this method for the Desktop platform that will read the overrides from app.config, registry and + // the shim database. Additional implementation can be provided for other platforms. + static partial void PopulateOverrideValuesPartial(); + + static partial void PopulateDefaultValuesPartial(string platformIdentifier, string profile, int version) + { + // When defining a new switch you should add it to the last known version. + // For instance, if you are adding a switch in .NET 4.6 (the release after 4.5.2) you should defined your switch + // like this: + // if (version <= 40502) ... + // This ensures that all previous versions of that platform (up-to 4.5.2) will get the old behavior by default + // NOTE: When adding a default value for a switch please make sure that the default value is added to ALL of the existing platforms! + // NOTE: When adding a new if statement for the version please ensure that ALL previous switches are enabled (ie. don't use else if) + switch (platformIdentifier) + { + case ".NETCore": + case ".NETFramework": + { + if (version <= 40502) + { + AppContext.DefineSwitchDefault(SwitchNoAsyncCurrentCulture, true); + AppContext.DefineSwitchDefault(SwitchThrowExceptionIfDisposedCancellationTokenSource, true); + } +#if FEATURE_PATHCOMPAT + if (version <= 40601) + { + AppContext.DefineSwitchDefault(SwitchUseLegacyPathHandling, true); + AppContext.DefineSwitchDefault(SwitchBlockLongPaths, true); + } +#endif + break; + } + case "WindowsPhone": + case "WindowsPhoneApp": + { + if (version <= 80100) + { + AppContext.DefineSwitchDefault(SwitchNoAsyncCurrentCulture, true); + AppContext.DefineSwitchDefault(SwitchThrowExceptionIfDisposedCancellationTokenSource, true); + } + break; + } + } + + // At this point we should read the overrides if any are defined + PopulateOverrideValuesPartial(); + } + } +} diff --git a/src/mscorlib/src/System/AppContext/AppContextDefaultValues.cs b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.cs new file mode 100644 index 0000000000..7ab7ffbc04 --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContextDefaultValues.cs @@ -0,0 +1,164 @@ +// 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. + +using System; +using System.Collections.Generic; + +namespace System +{ + internal static partial class AppContextDefaultValues + { + public static void PopulateDefaultValues() + { + string platformIdentifier, profile; + int version; + + ParseTargetFrameworkName(out platformIdentifier, out profile, out version); + + // Call into each library to populate their default switches + PopulateDefaultValuesPartial(platformIdentifier, profile, version); + } + + /// <summary> + /// We have this separate method for getting the parsed elements out of the TargetFrameworkName so we can + /// more easily support this on other platforms. + /// </summary> + private static void ParseTargetFrameworkName(out string identifier, out string profile, out int version) + { + string targetFrameworkMoniker = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName; + + if (!TryParseFrameworkName(targetFrameworkMoniker, out identifier, out version, out profile)) + { +#if FEATURE_CORECLR + // If we can't parse the TFM or we don't have a TFM, default to latest behavior for all + // switches (ie. all of them false). + // If we want to use the latest behavior it is enough to set the value of the switch to string.Empty. + // When the get to the caller of this method (PopulateDefaultValuesPartial) we are going to use the + // identifier we just set to decide which switches to turn on. By having an empty string as the + // identifier we are simply saying -- don't turn on any switches, and we are going to get the latest + // behavior for all the switches + identifier = string.Empty; +#else + identifier = ".NETFramework"; + version = 40000; + profile = string.Empty; +#endif + } + } + + // This code was a constructor copied from the FrameworkName class, which is located in System.dll. + // Parses strings in the following format: "<identifier>, Version=[v|V]<version>, Profile=<profile>" + // - The identifier and version is required, profile is optional + // - Only three components are allowed. + // - The version string must be in the System.Version format; an optional "v" or "V" prefix is allowed + private static bool TryParseFrameworkName(String frameworkName, out String identifier, out int version, out String profile) + { + // For parsing a target Framework moniker, from the FrameworkName class + const char c_componentSeparator = ','; + const char c_keyValueSeparator = '='; + const char c_versionValuePrefix = 'v'; + const String c_versionKey = "Version"; + const String c_profileKey = "Profile"; + + identifier = profile = string.Empty; + version = 0; + + if (frameworkName == null || frameworkName.Length == 0) + { + return false; + } + + String[] components = frameworkName.Split(c_componentSeparator); + version = 0; + + // Identifer and Version are required, Profile is optional. + if (components.Length < 2 || components.Length > 3) + { + return false; + } + + // + // 1) Parse the "Identifier", which must come first. Trim any whitespace + // + identifier = components[0].Trim(); + + if (identifier.Length == 0) + { + return false; + } + + bool versionFound = false; + profile = null; + + // + // The required "Version" and optional "Profile" component can be in any order + // + for (int i = 1; i < components.Length; i++) + { + // Get the key/value pair separated by '=' + string[] keyValuePair = components[i].Split(c_keyValueSeparator); + + if (keyValuePair.Length != 2) + { + return false; + } + + // Get the key and value, trimming any whitespace + string key = keyValuePair[0].Trim(); + string value = keyValuePair[1].Trim(); + + // + // 2) Parse the required "Version" key value + // + if (key.Equals(c_versionKey, StringComparison.OrdinalIgnoreCase)) + { + versionFound = true; + + // Allow the version to include a 'v' or 'V' prefix... + if (value.Length > 0 && (value[0] == c_versionValuePrefix || value[0] == 'V')) + { + value = value.Substring(1); + } + Version realVersion = new Version(value); + // The version class will represent some unset values as -1 internally (instead of 0). + version = realVersion.Major * 10000; + if (realVersion.Minor > 0) + version += realVersion.Minor * 100; + if (realVersion.Build > 0) + version += realVersion.Build; + } + // + // 3) Parse the optional "Profile" key value + // + else if (key.Equals(c_profileKey, StringComparison.OrdinalIgnoreCase)) + { + if (!String.IsNullOrEmpty(value)) + { + profile = value; + } + } + else + { + return false; + } + } + + if (!versionFound) + { + return false; + } + + return true; + } + + // This is a partial method. Platforms (such as Desktop) can provide an implementation of it that will read override value + // from whatever mechanism is available on that platform. If no implementation is provided, the compiler is going to remove the calls + // to it from the code + static partial void TryGetSwitchOverridePartial(string switchName, ref bool overrideFound, ref bool overrideValue); + + /// This is a partial method. This method is responsible for populating the default values based on a TFM. + /// It is partial because each library should define this method in their code to contain their defaults. + static partial void PopulateDefaultValuesPartial(string platformIdentifier, string profile, int version); + } +} diff --git a/src/mscorlib/src/System/AppContext/AppContextSwitches.cs b/src/mscorlib/src/System/AppContext/AppContextSwitches.cs new file mode 100644 index 0000000000..3a96ec2159 --- /dev/null +++ b/src/mscorlib/src/System/AppContext/AppContextSwitches.cs @@ -0,0 +1,110 @@ +// 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. + +namespace System +{ + using System; + using System.Runtime.CompilerServices; + + internal static class AppContextSwitches + { + private static int _noAsyncCurrentCulture; + public static bool NoAsyncCurrentCulture + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetCachedSwitchValue(AppContextDefaultValues.SwitchNoAsyncCurrentCulture, ref _noAsyncCurrentCulture); + } + } + + private static int _throwExceptionIfDisposedCancellationTokenSource; + public static bool ThrowExceptionIfDisposedCancellationTokenSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetCachedSwitchValue(AppContextDefaultValues.SwitchThrowExceptionIfDisposedCancellationTokenSource, ref _throwExceptionIfDisposedCancellationTokenSource); + } + } + + private static int _preserveEventListnerObjectIdentity; + public static bool PreserveEventListnerObjectIdentity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetCachedSwitchValue(AppContextDefaultValues.SwitchPreserveEventListnerObjectIdentity, ref _preserveEventListnerObjectIdentity); + } + } + +#if FEATURE_PATHCOMPAT + private static int _useLegacyPathHandling; + + /// <summary> + /// Use legacy path normalization logic and blocking of extended syntax. + /// </summary> + public static bool UseLegacyPathHandling + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetCachedSwitchValue(AppContextDefaultValues.SwitchUseLegacyPathHandling, ref _useLegacyPathHandling); + } + } + + private static int _blockLongPaths; + + /// <summary> + /// Throw PathTooLongException for paths greater than MAX_PATH or directories greater than 248 (as per CreateDirectory Win32 limitations) + /// </summary> + public static bool BlockLongPaths + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetCachedSwitchValue(AppContextDefaultValues.SwitchBlockLongPaths, ref _blockLongPaths); + } + } +#endif // FEATURE_PATHCOMPAT + + // + // Implementation details + // + + private static bool DisableCaching { get; set; } + + static AppContextSwitches() + { + bool isEnabled; + if (AppContext.TryGetSwitch(@"TestSwitch.LocalAppContext.DisableCaching", out isEnabled)) + { + DisableCaching = isEnabled; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetCachedSwitchValue(string switchName, ref int switchValue) + { + if (switchValue < 0) return false; + if (switchValue > 0) return true; + + return GetCachedSwitchValueInternal(switchName, ref switchValue); + } + + private static bool GetCachedSwitchValueInternal(string switchName, ref int switchValue) + { + bool isSwitchEnabled; + AppContext.TryGetSwitch(switchName, out isSwitchEnabled); + + if (DisableCaching) + { + return isSwitchEnabled; + } + + switchValue = isSwitchEnabled ? 1 /*true*/ : -1 /*false*/; + return isSwitchEnabled; + } + } +} |