diff options
Diffstat (limited to 'src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs')
-rw-r--r-- | src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs b/src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs new file mode 100644 index 0000000000..99e30b5488 --- /dev/null +++ b/src/mscorlib/src/System/Runtime/Versioning/BinaryCompatibility.cs @@ -0,0 +1,485 @@ +// 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. + +/*============================================================ +** +** +** +** +** Purpose: This class is used to determine which binary compatibility +** behaviors are enabled at runtime. A type for +** tracking which target Framework an app was built against, or an +** appdomain-wide setting from the host telling us which .NET +** Framework version we should emulate. +** +** +===========================================================*/ +using System; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace System.Runtime.Versioning +{ + // Provides a simple way to test whether an application was built against specific .NET Framework + // flavors and versions, with the intent of allowing Framework developers to mimic behavior of older + // Framework releases. This allows us to make behavioral breaking changes in a binary compatible way, + // for an application. This works at the per-AppDomain level, not process nor per-Assembly. + // + // To opt into newer behavior, applications must specify a TargetFrameworkAttribute on their assembly + // saying what version they targeted, or a host must set this when creating an AppDomain. Note + // that command line apps don't have this attribute! + // + // To use this class: + // Developers need to figure out whether they're working on the phone, desktop, or Silverlight, and + // what version they are introducing a breaking change in. Pick one predicate below, and use that + // to decide whether to run the new or old behavior. Example: + // + // if (BinaryCompatibility.TargetsAtLeast_Phone_V7_1) { + // // new behavior for phone 7.1 and other releases where we will integrate this change, like .NET Framework 4.5 + // } + // else { + // // Legacy behavior + // } + // + // If you are making a breaking change in one specific branch that won't be integrated normally to + // all other branches (ie, say you're making breaking changes to Windows Phone 8 after .NET Framework v4.5 + // has locked down for release), then add in specific predicates for each relevant platform. + // + // Maintainers of this class: + // Revisit the table once per release, perhaps at the end of the last coding milestone, to verify a + // default policy saying whether all quirks from a particular flavor & release should be enabled in + // other releases (ie, should all Windows Phone 8.0 quirks be enabled in .NET Framework v5)? + // + // History: + // Here is the order in which releases were made along with some basic integration information. The idea + // is to track what set of compatibility features are present in each other. + // While we cannot guarantee this list is perfectly linear (ie, a feature could be implemented in the last + // few weeks before shipping and make it into only one of three concommittent releases due to triaging), + // this is a good high level summary of code flow. + // + // Desktop Silverlight Windows Phone + // .NET Framework 3.0 -> Silverlight 2 + // .NET Framework 3.5 + // Silverlight 3 + // Silverlight 4 + // .NET Framework 4 Phone 8.0 + // .NET Framework 4.5 Phone 8.1 + // .NET Framework 4.5.1 Phone 8.1 + // + // (Note: Windows Phone 7.0 was built using the .NET Compact Framework, which forked around v1 or v1.1) + // + // Compatibility Policy decisions: + // If we cannot determine that an app was built for a newer .NET Framework (ie, the app has no + // TargetFrameworkAttribute), then quirks will be enabled to emulate older behavior. + // As such, your test code should define the TargetFrameworkAttribute (which VS does for you) + // if you want to see the new behavior! + [FriendAccessAllowed] + internal static class BinaryCompatibility + { + // Use this for new behavior introduced in the phone branch. It will do the right thing for desktop & SL. + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Phone_V7_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V7_1; } } + + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Phone_V8_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V8_0; } } + + // Use this for new behavior introduced in the Desktop branch. It will do the right thing for Phone & SL. + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V4_5 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V4_5_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_1; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V4_5_2 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_2; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V4_5_3 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_3; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V4_5_4 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_4; } } + + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Desktop_V5_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V5_0; } } + + // Use this for new behavior introduced in the Silverlight branch. It will do the right thing for desktop & Phone. + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Silverlight_V4 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V4; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Silverlight_V5 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V5; } } + [FriendAccessAllowed] + internal static bool TargetsAtLeast_Silverlight_V6 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V6; } } + + [FriendAccessAllowed] + internal static TargetFrameworkId AppWasBuiltForFramework { + [FriendAccessAllowed] + get { + Contract.Ensures(Contract.Result<TargetFrameworkId>() > TargetFrameworkId.NotYetChecked); + + if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked) + ReadTargetFrameworkId(); + + return s_AppWasBuiltForFramework; + } + } + + // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501). + [FriendAccessAllowed] + internal static int AppWasBuiltForVersion { + [FriendAccessAllowed] + get { + Contract.Ensures(Contract.Result<int>() > 0 || s_AppWasBuiltForFramework == TargetFrameworkId.Unspecified); + + if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked) + ReadTargetFrameworkId(); + + Contract.Assert(s_AppWasBuiltForFramework != TargetFrameworkId.Unrecognized); + + return s_AppWasBuiltForVersion; + } + } + + #region private + private static TargetFrameworkId s_AppWasBuiltForFramework; + // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501). + private static int s_AppWasBuiltForVersion; + + readonly static BinaryCompatibilityMap s_map = new BinaryCompatibilityMap(); + + // For parsing a target Framework moniker, from the FrameworkName class + private const char c_componentSeparator = ','; + private const char c_keyValueSeparator = '='; + private const char c_versionValuePrefix = 'v'; + private const String c_versionKey = "Version"; + private const String c_profileKey = "Profile"; + + /// <summary> + /// BinaryCompatibilityMap is basically a bitvector. There is a boolean field for each of the + /// properties in BinaryCompatibility + /// </summary> + private sealed class BinaryCompatibilityMap + { + // A bit for each property + internal bool TargetsAtLeast_Phone_V7_1; + internal bool TargetsAtLeast_Phone_V8_0; + internal bool TargetsAtLeast_Phone_V8_1; + internal bool TargetsAtLeast_Desktop_V4_5; + internal bool TargetsAtLeast_Desktop_V4_5_1; + internal bool TargetsAtLeast_Desktop_V4_5_2; + internal bool TargetsAtLeast_Desktop_V4_5_3; + internal bool TargetsAtLeast_Desktop_V4_5_4; + internal bool TargetsAtLeast_Desktop_V5_0; + internal bool TargetsAtLeast_Silverlight_V4; + internal bool TargetsAtLeast_Silverlight_V5; + internal bool TargetsAtLeast_Silverlight_V6; + + internal BinaryCompatibilityMap() + { + AddQuirksForFramework(AppWasBuiltForFramework, AppWasBuiltForVersion); + } + + // The purpose of this method is to capture information about integrations & behavioral compatibility + // between our multiple different release vehicles. IE, if a behavior shows up in Silverlight version 5, + // does it show up in the .NET Framework version 4.5 and Windows Phone 8? + // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501). + private void AddQuirksForFramework(TargetFrameworkId builtAgainstFramework, int buildAgainstVersion) + { + Contract.Requires(buildAgainstVersion > 0 || builtAgainstFramework == TargetFrameworkId.Unspecified); + + switch (builtAgainstFramework) + { + case TargetFrameworkId.NetFramework: + case TargetFrameworkId.NetCore: // Treat Windows 8 tailored apps as normal desktop apps - same product + if (buildAgainstVersion >= 50000) + TargetsAtLeast_Desktop_V5_0 = true; + + // Potential 4.5 servicing releases + if (buildAgainstVersion >= 40504) + TargetsAtLeast_Desktop_V4_5_4 = true; + if (buildAgainstVersion >= 40503) + TargetsAtLeast_Desktop_V4_5_3 = true; + if (buildAgainstVersion >= 40502) + TargetsAtLeast_Desktop_V4_5_2 = true; + if (buildAgainstVersion >= 40501) + TargetsAtLeast_Desktop_V4_5_1 = true; + + if (buildAgainstVersion >= 40500) + { + TargetsAtLeast_Desktop_V4_5 = true; + // On XX/XX/XX we integrated all changes from the phone V7_1 into the branch from which contains Desktop V4_5, thus + // Any application built for V4_5 (or above) should have all the quirks for Phone V7_1 turned on. + AddQuirksForFramework(TargetFrameworkId.Phone, 70100); + // All Silverlight 5 behavior should be in the .NET Framework version 4.5 + AddQuirksForFramework(TargetFrameworkId.Silverlight, 50000); + } + break; + + case TargetFrameworkId.Phone: + if (buildAgainstVersion >= 80000) + { + // This is for Apollo apps. For Apollo apps we don't want to enable any of the 4.5 or 4.5.1 quirks + TargetsAtLeast_Phone_V8_0 = true; + //TargetsAtLeast_Desktop_V4_5 = true; + } + if (buildAgainstVersion >= 80100) + { + // For WindowsPhone 8.1 and SL 8.1 scenarios we want to enable both 4.5 and 4.5.1 quirks. + TargetsAtLeast_Desktop_V4_5 = true; + TargetsAtLeast_Desktop_V4_5_1 = true; + } + + if (buildAgainstVersion >= 710) + TargetsAtLeast_Phone_V7_1 = true; + break; + + case TargetFrameworkId.Silverlight: + if (buildAgainstVersion >= 40000) + TargetsAtLeast_Silverlight_V4 = true; + + if (buildAgainstVersion >= 50000) + TargetsAtLeast_Silverlight_V5 = true; + + if (buildAgainstVersion >= 60000) + { + TargetsAtLeast_Silverlight_V6 = true; + } + break; + + case TargetFrameworkId.Unspecified: + break; + + case TargetFrameworkId.NotYetChecked: + case TargetFrameworkId.Unrecognized: + Contract.Assert(false, "Bad framework kind"); + break; + default: + Contract.Assert(false, "Error: we introduced a new Target Framework but did not update our binary compatibility map"); + break; + } + } + } + + #region String Parsing + + // If this doesn't work, perhaps we could fall back to parsing the metadata version number. + private static bool ParseTargetFrameworkMonikerIntoEnum(String targetFrameworkMoniker, out TargetFrameworkId targetFramework, out int targetFrameworkVersion) + { + Contract.Requires(!String.IsNullOrEmpty(targetFrameworkMoniker)); + + targetFramework = TargetFrameworkId.NotYetChecked; + targetFrameworkVersion = 0; + + String identifier = null; + String profile = null; + ParseFrameworkName(targetFrameworkMoniker, out identifier, out targetFrameworkVersion, out profile); + + switch (identifier) + { + case ".NETFramework": + targetFramework = TargetFrameworkId.NetFramework; + break; + + case ".NETPortable": + targetFramework = TargetFrameworkId.Portable; + break; + + case ".NETCore": + targetFramework = TargetFrameworkId.NetCore; + break; + + case "WindowsPhone": + if (targetFrameworkVersion >= 80100) + { + // A TFM of the form WindowsPhone,Version=v8.1 corresponds to SL 8.1 scenario + // and gets the same quirks as WindowsPhoneApp\v8.1 store apps. + targetFramework = TargetFrameworkId.Phone; + } + else + { + // There is no TFM for Apollo or below and hence we assign the targetFramework to Unspecified. + targetFramework = TargetFrameworkId.Unspecified; + } + break; + + case "WindowsPhoneApp": + targetFramework = TargetFrameworkId.Phone; + break; + + case "Silverlight": + targetFramework = TargetFrameworkId.Silverlight; + // Windows Phone 7 is Silverlight,Version=v4.0,Profile=WindowsPhone + // Windows Phone 7.1 is Silverlight,Version=v4.0,Profile=WindowsPhone71 + if (!String.IsNullOrEmpty(profile)) + { + if (profile == "WindowsPhone") + { + targetFramework = TargetFrameworkId.Phone; + targetFrameworkVersion = 70000; + } + else if (profile == "WindowsPhone71") + { + targetFramework = TargetFrameworkId.Phone; + targetFrameworkVersion = 70100; + } + else if (profile == "WindowsPhone8") + { + targetFramework = TargetFrameworkId.Phone; + targetFrameworkVersion = 80000; + } + else if (profile.StartsWith("WindowsPhone", StringComparison.Ordinal)) + { + Contract.Assert(false, "This is a phone app, but we can't tell what version this is!"); + targetFramework = TargetFrameworkId.Unrecognized; + targetFrameworkVersion = 70100; + } + else + { + Contract.Assert(false, String.Format(CultureInfo.InvariantCulture, "Unrecognized Silverlight profile \"{0}\". What is this, an XBox app?", profile)); + targetFramework = TargetFrameworkId.Unrecognized; + } + } + break; + + default: + Contract.Assert(false, String.Format(CultureInfo.InvariantCulture, "Unrecognized Target Framework Moniker in our Binary Compatibility class. Framework name: \"{0}\"", targetFrameworkMoniker)); + targetFramework = TargetFrameworkId.Unrecognized; + break; + } + + return true; + } + + // 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 void ParseFrameworkName(String frameworkName, out String identifier, out int version, out String profile) + { + if (frameworkName == null) + { + throw new ArgumentNullException("frameworkName"); + } + if (frameworkName.Length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_StringZeroLength"), "frameworkName"); + } + Contract.EndContractBlock(); + + String[] components = frameworkName.Split(c_componentSeparator); + version = 0; + + // Identifer and Version are required, Profile is optional. + if (components.Length < 2 || components.Length > 3) + { + throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameTooShort"), "frameworkName"); + } + + // + // 1) Parse the "Identifier", which must come first. Trim any whitespace + // + identifier = components[0].Trim(); + + if (identifier.Length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName"); + } + + 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) + { + throw new ArgumentException(Environment.GetResourceString("SR.Argument_FrameworkNameInvalid"), "frameworkName"); + } + + // 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 + { + throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName"); + } + } + + if (!versionFound) + { + throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameMissingVersion"), "frameworkName"); + } + } + + [System.Security.SecuritySafeCritical] + private static void ReadTargetFrameworkId() + { + String targetFrameworkName = AppDomain.CurrentDomain.GetTargetFrameworkName(); + + var overrideValue = System.Runtime.Versioning.CompatibilitySwitch.GetValueInternal("TargetFrameworkMoniker"); + if (!string.IsNullOrEmpty(overrideValue)) + { + targetFrameworkName = overrideValue; + } + + // Write to a local then to _targetFramework, after writing the version number. + TargetFrameworkId fxId; + int fxVersion = 0; + if (targetFrameworkName == null) + { +#if FEATURE_CORECLR + // if we don't have a value for targetFrameworkName we need to figure out if we should give the newest behavior or not. + if (CompatibilitySwitches.UseLatestBehaviorWhenTFMNotSpecified) + { + fxId = TargetFrameworkId.NetFramework; + fxVersion = 50000; // We are going to default to the latest value for version that we have in our code. + } + else +#endif + fxId = TargetFrameworkId.Unspecified; + } + else if (!ParseTargetFrameworkMonikerIntoEnum(targetFrameworkName, out fxId, out fxVersion)) + fxId = TargetFrameworkId.Unrecognized; + + s_AppWasBuiltForFramework = fxId; + s_AppWasBuiltForVersion = fxVersion; + } + #endregion String Parsing + + #endregion private + } +} |