Skip to content

AppContext and *CompatibilityPreferences infrastructure #787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// 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.

// There are cases where we have multiple assemblies that are going to import this file and
// if they are going to also have InternalsVisibleTo between them, there will be a compiler warning
// that the type is found both in the source and in a referenced assembly. The compiler will prefer
// the version of the type defined in the source
//
// In order to disable the warning for this type we are disabling this warning for this entire file.
#pragma warning disable 436

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)
{
// AppDomain.CurrentDomain.SetupInformation is not available on .NET Core prior to 3.0
// Use Reflection to obtain this value if available.
string targetFrameworkMoniker = GetTargetFrameworkMoniker();

// This is our default
// When TFM cannot be found, it probably means we are running
// on .NET Core 2.2 or lower, in which case we will default to .NET Core 3.0
if (targetFrameworkMoniker == null)
{
targetFrameworkMoniker = ".NETCoreApp,Version=v3.0";
}

// If we don't have a TFM then we should default to the .NET Framework 4.8+ behavior where all quirks are turned on.
// For .NET Core 3.0, the result would be something like this:
// identifier = ".NETCore";
// version = 30000;
// profile = string.Empty;
if (!TryParseFrameworkName(targetFrameworkMoniker, out identifier, out version, out profile))
{
// 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
//
// In practices, this may not work reliably due to the lack of targetFrameworkMoniker information.
identifier = string.Empty;
}
}

/// <summary>
/// This is equivalent to calling <code>AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName</code>
/// </summary>
/// <remarks>
/// <code>AppDomain.CurrentDomain.SetupInformation</code> is not available until .NET Core 3.0, but we
/// have a need to run this code in .NET Core 2.2, we attempt to obtain this information via Reflection.
/// </remarks>
/// <returns>TargetFrameworkMoniker on .NET Framework and .NET Core 3.0+; null on .NET Core 2.2 or older runtimes</returns>
private static string GetTargetFrameworkMoniker()
{
try
{
var pSetupInformation = typeof(AppDomain).GetProperty("SetupInformation");
object appDomainSetup = pSetupInformation?.GetValue(AppDomain.CurrentDomain);
Type tAppDomainSetup = Type.GetType("System.AppDomainSetup");
var pTargetFrameworkName = tAppDomainSetup?.GetProperty("TargetFrameworkName");

return
appDomainSetup != null ?
pTargetFrameworkName?.GetValue(appDomainSetup) as string :
null;
}
catch (Exception)
{
return null;
}
}

// 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;

// Identifier 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);
}
}

#pragma warning restore 436
115 changes: 98 additions & 17 deletions src/Microsoft.DotNet.Wpf/src/Common/src/System/LocalAppContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,94 @@

using System.Runtime.CompilerServices;
using System.Threading;
using System.Collections.Generic;
using System.Reflection;

namespace System
{
// error CS0436: When building PresentationFramework, the type 'LocalAppContext'
// conflicts with the imported type 'LocalAppContext' in 'PresentationCore
#pragma warning disable 436
internal partial class LocalAppContext
{
private static bool s_isDisableCachingInitialized;
private static bool s_disableCaching;
private static object s_syncObject;
private delegate bool TryGetSwitchDelegate(string switchName, out bool value);

private static TryGetSwitchDelegate TryGetSwitchFromCentralAppContext;
private static bool s_canForwardCalls;

private static Dictionary<string, bool> s_switchMap = new Dictionary<string, bool>();
private static readonly object s_syncLock = new object();

private static bool DisableCaching { get; set; }

static LocalAppContext()
{
// Try to setup the callback into the central AppContext
s_canForwardCalls = SetupDelegate();

// Populate the default values of the local app context
AppContextDefaultValues.PopulateDefaultValues();

// Cache the value of the switch that help with testing
DisableCaching = IsSwitchEnabled(@"TestSwitch.LocalAppContext.DisableCaching");
}

public static bool IsSwitchEnabled(string switchName)
{
if (s_canForwardCalls)
{
bool isEnabledCentrally;
if (TryGetSwitchFromCentralAppContext(switchName, out isEnabledCentrally))
{
// we found the switch, so return whatever value it has
return isEnabledCentrally;
}
// if we could not get the value from the central authority, try the local storage.
}

return IsSwitchEnabledLocal(switchName);
}

private static bool IsSwitchEnabledLocal(string switchName)
{
// read the value from the set of local defaults
bool isEnabled, isPresent;
lock (s_switchMap)
{
isPresent = s_switchMap.TryGetValue(switchName, out isEnabled);
}

// If the value is in the set of local switches, return the value
if (isPresent)
{
return isEnabled;
}

// if we could not find the switch name, we should return 'false'
// This will preserve the concept of switches been 'off' unless explicitly set to 'on'
return false;
}

private static bool SetupDelegate()
{
Type appContextType = typeof(object).Assembly.GetType("System.AppContext");
if (appContextType == null)
return false;

MethodInfo method = appContextType.GetMethod(
"TryGetSwitch", // the method name
BindingFlags.Static | BindingFlags.Public, // binding flags
null, // use the default binder
new Type[] { typeof(string), typeof(bool).MakeByRefType() },
null); // parameterModifiers - this is ignored by the default binder
if (method == null)
return false;

// Create delegate if we found the method.
TryGetSwitchFromCentralAppContext = (TryGetSwitchDelegate)Delegate.CreateDelegate(typeof(TryGetSwitchDelegate), method);

return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetCachedSwitchValue(string switchName, ref int switchValue)
Expand All @@ -24,24 +104,25 @@ internal static bool GetCachedSwitchValue(string switchName, ref int switchValue

private static bool GetCachedSwitchValueInternal(string switchName, ref int switchValue)
{
bool isSwitchEnabled;
AppContext.TryGetSwitch(switchName, out isSwitchEnabled);

if (DisableCaching)
if (LocalAppContext.DisableCaching)
{
return isSwitchEnabled;
return LocalAppContext.IsSwitchEnabled(switchName);
}

switchValue = isSwitchEnabled ? 1 /*true*/ : -1 /*false*/;
return isSwitchEnabled;
bool isEnabled = LocalAppContext.IsSwitchEnabled(switchName);
switchValue = isEnabled ? 1 /*true*/ : -1 /*false*/;
return isEnabled;
}

private static bool DisableCaching =>
LazyInitializer.EnsureInitialized(ref s_disableCaching, ref s_isDisableCachingInitialized, ref s_syncObject, () =>
{
bool isEnabled;
AppContext.TryGetSwitch(@"TestSwitch.LocalAppContext.DisableCaching", out isEnabled);
return isEnabled;
});
/// <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 initialValue)
{
s_switchMap[switchName] = initialValue;
}
}
#pragma warning restore 436
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.Runtime.CompilerServices;

namespace MS.Internal
{
// WPF's builds are seeing warnings as a result of using LocalAppContext in mutliple assemblies.
// that have internalsVisibleTo attribute set between them - which results in the warning.
// We don't have a way of suppressing this warning effectively until the shared copies of LocalAppContext and
// AppContextDefaultValues have pragmas added to suppress warning 436
#pragma warning disable 436
internal static class BuildTasksAppContextSwitches
{
#region DoNotUseSha256ForMarkupCompilerChecksumAlgorithm

internal const string DoNotUseSha256ForMarkupCompilerChecksumAlgorithmSwitchName = "Switch.System.Windows.Markup.DoNotUseSha256ForMarkupCompilerChecksumAlgorithm";
private static int _doNotUseSha256ForMarkupCompilerChecksumAlgorithm;

public static bool DoNotUseSha256ForMarkupCompilerChecksumAlgorithm
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return LocalAppContext.GetCachedSwitchValue(DoNotUseSha256ForMarkupCompilerChecksumAlgorithmSwitchName, ref _doNotUseSha256ForMarkupCompilerChecksumAlgorithm);
}
}

#endregion
}
#pragma warning restore 436
}
Loading