3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
8 ** Class: BinaryCompatibility
10 ** <OWNER>[....]</OWNER>
13 ** Purpose: This class is used to determine which binary compatibility
14 ** behaviors are enabled at runtime. A type for
15 ** tracking which target Framework an app was built against, or an
16 ** appdomain-wide setting from the host telling us which .NET
17 ** Framework version we should emulate.
20 ===========================================================*/
22 using System.Diagnostics.Contracts;
23 using System.Globalization;
24 using System.Runtime.CompilerServices;
26 namespace System.Runtime.Versioning
28 // Provides a simple way to test whether an application was built against specific .NET Framework
29 // flavors and versions, with the intent of allowing Framework developers to mimic behavior of older
30 // Framework releases. This allows us to make behavioral breaking changes in a binary compatible way,
31 // for an application. This works at the per-AppDomain level, not process nor per-Assembly.
33 // To opt into newer behavior, applications must specify a TargetFrameworkAttribute on their assembly
34 // saying what version they targeted, or a host must set this when creating an AppDomain. Note
35 // that command line apps don't have this attribute!
38 // Developers need to figure out whether they're working on the phone, desktop, or Silverlight, and
39 // what version they are introducing a breaking change in. Pick one predicate below, and use that
40 // to decide whether to run the new or old behavior. Example:
42 // if (BinaryCompatibility.TargetsAtLeast_Phone_V7_1) {
43 // // new behavior for phone 7.1 and other releases where we will integrate this change, like .NET Framework 4.5
49 // If you are making a breaking change in one specific branch that won't be integrated normally to
50 // all other branches (ie, say you're making breaking changes to Windows Phone 8 after .NET Framework v4.5
51 // has locked down for release), then add in specific predicates for each relevant platform.
53 // Maintainers of this class:
54 // Revisit the table once per release, perhaps at the end of the last coding milestone, to verify a
55 // default policy saying whether all quirks from a particular flavor & release should be enabled in
56 // other releases (ie, should all Windows Phone 8.0 quirks be enabled in .NET Framework v5)?
59 // Here is the order in which releases were made along with some basic integration information. The idea
60 // is to track what set of compatibility features are present in each other.
61 // While we cannot guarantee this list is perfectly linear (ie, a feature could be implemented in the last
62 // few weeks before shipping and make it into only one of three concommittent releases due to triaging),
63 // this is a good high level summary of code flow.
65 // Desktop Silverlight Windows Phone
66 // .NET Framework 3.0 -> Silverlight 2
70 // .NET Framework 4 Phone 8.0
71 // .NET Framework 4.5 Phone 8.1
72 // .NET Framework 4.5.1 Phone 8.1
74 // (Note: Windows Phone 7.0 was built using the .NET Compact Framework, which forked around v1 or v1.1)
76 // Compatibility Policy decisions:
77 // If we cannot determine that an app was built for a newer .NET Framework (ie, the app has no
78 // TargetFrameworkAttribute), then quirks will be enabled to emulate older behavior.
79 // As such, your test code should define the TargetFrameworkAttribute (which VS does for you)
80 // if you want to see the new behavior!
82 internal static class BinaryCompatibility
84 // Use this for new behavior introduced in the phone branch. It will do the right thing for desktop & SL.
86 internal static bool TargetsAtLeast_Phone_V7_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V7_1; } }
89 internal static bool TargetsAtLeast_Phone_V8_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V8_0; } }
91 // Use this for new behavior introduced in the Desktop branch. It will do the right thing for Phone & SL.
93 internal static bool TargetsAtLeast_Desktop_V4_5 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5; } }
95 internal static bool TargetsAtLeast_Desktop_V4_5_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_1; } }
97 internal static bool TargetsAtLeast_Desktop_V4_5_2 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_2; } }
99 internal static bool TargetsAtLeast_Desktop_V4_5_3 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_3; } }
100 [FriendAccessAllowed]
101 internal static bool TargetsAtLeast_Desktop_V4_5_4 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_4; } }
103 [FriendAccessAllowed]
104 internal static bool TargetsAtLeast_Desktop_V5_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V5_0; } }
106 // Use this for new behavior introduced in the Silverlight branch. It will do the right thing for desktop & Phone.
107 [FriendAccessAllowed]
108 internal static bool TargetsAtLeast_Silverlight_V4 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V4; } }
109 [FriendAccessAllowed]
110 internal static bool TargetsAtLeast_Silverlight_V5 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V5; } }
111 [FriendAccessAllowed]
112 internal static bool TargetsAtLeast_Silverlight_V6 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Silverlight_V6; } }
114 [FriendAccessAllowed]
115 internal static TargetFrameworkId AppWasBuiltForFramework {
116 [FriendAccessAllowed]
118 Contract.Ensures(Contract.Result<TargetFrameworkId>() > TargetFrameworkId.NotYetChecked);
120 if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked)
121 ReadTargetFrameworkId();
123 return s_AppWasBuiltForFramework;
127 // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501).
128 [FriendAccessAllowed]
129 internal static int AppWasBuiltForVersion {
130 [FriendAccessAllowed]
132 Contract.Ensures(Contract.Result<int>() > 0 || s_AppWasBuiltForFramework == TargetFrameworkId.Unspecified);
134 if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked)
135 ReadTargetFrameworkId();
137 Contract.Assert(s_AppWasBuiltForFramework != TargetFrameworkId.Unrecognized);
139 return s_AppWasBuiltForVersion;
144 private static TargetFrameworkId s_AppWasBuiltForFramework;
145 // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501).
146 private static int s_AppWasBuiltForVersion;
148 readonly static BinaryCompatibilityMap s_map = new BinaryCompatibilityMap();
150 // For parsing a target Framework moniker, from the FrameworkName class
151 private const char c_componentSeparator = ',';
152 private const char c_keyValueSeparator = '=';
153 private const char c_versionValuePrefix = 'v';
154 private const String c_versionKey = "Version";
155 private const String c_profileKey = "Profile";
158 /// BinaryCompatibilityMap is basically a bitvector. There is a boolean field for each of the
159 /// properties in BinaryCompatibility
161 private sealed class BinaryCompatibilityMap
163 // A bit for each property
164 internal bool TargetsAtLeast_Phone_V7_1;
165 internal bool TargetsAtLeast_Phone_V8_0;
166 internal bool TargetsAtLeast_Phone_V8_1;
167 internal bool TargetsAtLeast_Desktop_V4_5;
168 internal bool TargetsAtLeast_Desktop_V4_5_1;
169 internal bool TargetsAtLeast_Desktop_V4_5_2;
170 internal bool TargetsAtLeast_Desktop_V4_5_3;
171 internal bool TargetsAtLeast_Desktop_V4_5_4;
172 internal bool TargetsAtLeast_Desktop_V5_0;
173 internal bool TargetsAtLeast_Silverlight_V4;
174 internal bool TargetsAtLeast_Silverlight_V5;
175 internal bool TargetsAtLeast_Silverlight_V6;
177 internal BinaryCompatibilityMap()
179 AddQuirksForFramework(AppWasBuiltForFramework, AppWasBuiltForVersion);
182 // The purpose of this method is to capture information about integrations & behavioral compatibility
183 // between our multiple different release vehicles. IE, if a behavior shows up in Silverlight version 5,
184 // does it show up in the .NET Framework version 4.5 and Windows Phone 8?
185 // Version number is major * 10000 + minor * 100 + build (ie, 4.5.1.0 would be version 40501).
186 private void AddQuirksForFramework(TargetFrameworkId builtAgainstFramework, int buildAgainstVersion)
188 Contract.Requires(buildAgainstVersion > 0 || builtAgainstFramework == TargetFrameworkId.Unspecified);
190 switch (builtAgainstFramework)
192 case TargetFrameworkId.NetFramework:
193 case TargetFrameworkId.NetCore: // Treat Windows 8 tailored apps as normal desktop apps - same product
194 if (buildAgainstVersion >= 50000)
195 TargetsAtLeast_Desktop_V5_0 = true;
197 // Potential 4.5 servicing releases
198 if (buildAgainstVersion >= 40504)
199 TargetsAtLeast_Desktop_V4_5_4 = true;
200 if (buildAgainstVersion >= 40503)
201 TargetsAtLeast_Desktop_V4_5_3 = true;
202 if (buildAgainstVersion >= 40502)
203 TargetsAtLeast_Desktop_V4_5_2 = true;
204 if (buildAgainstVersion >= 40501)
205 TargetsAtLeast_Desktop_V4_5_1 = true;
207 if (buildAgainstVersion >= 40500)
209 TargetsAtLeast_Desktop_V4_5 = true;
210 // On XX/XX/XX we integrated all changes from the phone V7_1 into the branch from which contains Desktop V4_5, thus
211 // Any application built for V4_5 (or above) should have all the quirks for Phone V7_1 turned on.
212 AddQuirksForFramework(TargetFrameworkId.Phone, 70100);
213 // All Silverlight 5 behavior should be in the .NET Framework version 4.5
214 AddQuirksForFramework(TargetFrameworkId.Silverlight, 50000);
218 case TargetFrameworkId.Phone:
219 if (buildAgainstVersion >= 80000)
221 // This is for Apollo apps. For Apollo apps we don't want to enable any of the 4.5 or 4.5.1 quirks
222 TargetsAtLeast_Phone_V8_0 = true;
223 //TargetsAtLeast_Desktop_V4_5 = true;
225 if (buildAgainstVersion >= 80100)
227 // For WindowsPhone 8.1 and SL 8.1 scenarios we want to enable both 4.5 and 4.5.1 quirks.
228 TargetsAtLeast_Desktop_V4_5 = true;
229 TargetsAtLeast_Desktop_V4_5_1 = true;
232 if (buildAgainstVersion >= 710)
233 TargetsAtLeast_Phone_V7_1 = true;
236 case TargetFrameworkId.Silverlight:
237 if (buildAgainstVersion >= 40000)
238 TargetsAtLeast_Silverlight_V4 = true;
240 if (buildAgainstVersion >= 50000)
241 TargetsAtLeast_Silverlight_V5 = true;
243 if (buildAgainstVersion >= 60000)
245 TargetsAtLeast_Silverlight_V6 = true;
251 case TargetFrameworkId.Unspecified:
254 case TargetFrameworkId.NotYetChecked:
255 case TargetFrameworkId.Unrecognized:
256 Contract.Assert(false, "Bad framework kind");
259 Contract.Assert(false, "Error: we introduced a new Target Framework but did not update our binary compatibility map");
265 #region String Parsing
267 // If this doesn't work, perhaps we could fall back to parsing the metadata version number.
268 private static bool ParseTargetFrameworkMonikerIntoEnum(String targetFrameworkMoniker, out TargetFrameworkId targetFramework, out int targetFrameworkVersion)
270 Contract.Requires(!String.IsNullOrEmpty(targetFrameworkMoniker));
272 targetFramework = TargetFrameworkId.NotYetChecked;
273 targetFrameworkVersion = 0;
275 String identifier = null;
276 String profile = null;
277 ParseFrameworkName(targetFrameworkMoniker, out identifier, out targetFrameworkVersion, out profile);
281 case ".NETFramework":
282 targetFramework = TargetFrameworkId.NetFramework;
286 targetFramework = TargetFrameworkId.Portable;
290 targetFramework = TargetFrameworkId.NetCore;
294 if (targetFrameworkVersion >= 80100)
296 // A TFM of the form WindowsPhone,Version=v8.1 corresponds to SL 8.1 scenario
297 // and gets the same quirks as WindowsPhoneApp\v8.1 store apps.
298 targetFramework = TargetFrameworkId.Phone;
302 // There is no TFM for Apollo or below and hence we assign the targetFramework to Unspecified.
303 targetFramework = TargetFrameworkId.Unspecified;
307 case "WindowsPhoneApp":
308 targetFramework = TargetFrameworkId.Phone;
312 targetFramework = TargetFrameworkId.Silverlight;
313 // Windows Phone 7 is Silverlight,Version=v4.0,Profile=WindowsPhone
314 // Windows Phone 7.1 is Silverlight,Version=v4.0,Profile=WindowsPhone71
315 if (!String.IsNullOrEmpty(profile))
317 if (profile == "WindowsPhone")
319 targetFramework = TargetFrameworkId.Phone;
320 targetFrameworkVersion = 70000;
322 else if (profile == "WindowsPhone71")
324 targetFramework = TargetFrameworkId.Phone;
325 targetFrameworkVersion = 70100;
327 else if (profile == "WindowsPhone8") // @
329 targetFramework = TargetFrameworkId.Phone;
330 targetFrameworkVersion = 80000;
332 else if (profile.StartsWith("WindowsPhone", StringComparison.Ordinal))
334 Contract.Assert(false, "This is a phone app, but we can't tell what version this is!");
335 targetFramework = TargetFrameworkId.Unrecognized;
336 targetFrameworkVersion = 70100;
340 Contract.Assert(false, String.Format(CultureInfo.InvariantCulture, "Unrecognized Silverlight profile \"{0}\". What is this, an XBox app?", profile));
341 targetFramework = TargetFrameworkId.Unrecognized;
347 Contract.Assert(false, String.Format(CultureInfo.InvariantCulture, "Unrecognized Target Framework Moniker in our Binary Compatibility class. Framework name: \"{0}\"", targetFrameworkMoniker));
348 targetFramework = TargetFrameworkId.Unrecognized;
355 // This code was a constructor copied from the FrameworkName class, which is located in System.dll.
356 // Parses strings in the following format: "<identifier>, Version=[v|V]<version>, Profile=<profile>"
357 // - The identifier and version is required, profile is optional
358 // - Only three components are allowed.
359 // - The version string must be in the System.Version format; an optional "v" or "V" prefix is allowed
360 private static void ParseFrameworkName(String frameworkName, out String identifier, out int version, out String profile)
362 if (frameworkName == null)
364 throw new ArgumentNullException("frameworkName");
366 if (frameworkName.Length == 0)
368 throw new ArgumentException(Environment.GetResourceString("Argument_StringZeroLength"), "frameworkName");
370 Contract.EndContractBlock();
372 String[] components = frameworkName.Split(c_componentSeparator);
375 // Identifer and Version are required, Profile is optional.
376 if (components.Length < 2 || components.Length > 3)
378 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameTooShort"), "frameworkName");
382 // 1) Parse the "Identifier", which must come first. Trim any whitespace
384 identifier = components[0].Trim();
386 if (identifier.Length == 0)
388 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName");
391 bool versionFound = false;
395 // The required "Version" and optional "Profile" component can be in any order
397 for (int i = 1; i < components.Length; i++)
399 // Get the key/value pair separated by '='
400 string[] keyValuePair = components[i].Split(c_keyValueSeparator);
402 if (keyValuePair.Length != 2)
404 throw new ArgumentException(Environment.GetResourceString("SR.Argument_FrameworkNameInvalid"), "frameworkName");
407 // Get the key and value, trimming any whitespace
408 string key = keyValuePair[0].Trim();
409 string value = keyValuePair[1].Trim();
412 // 2) Parse the required "Version" key value
414 if (key.Equals(c_versionKey, StringComparison.OrdinalIgnoreCase))
418 // Allow the version to include a 'v' or 'V' prefix...
419 if (value.Length > 0 && (value[0] == c_versionValuePrefix || value[0] == 'V'))
421 value = value.Substring(1);
423 Version realVersion = new Version(value);
424 // The version class will represent some unset values as -1 internally (instead of 0).
425 version = realVersion.Major * 10000;
426 if (realVersion.Minor > 0)
427 version += realVersion.Minor * 100;
428 if (realVersion.Build > 0)
429 version += realVersion.Build;
432 // 3) Parse the optional "Profile" key value
434 else if (key.Equals(c_profileKey, StringComparison.OrdinalIgnoreCase))
436 if (!String.IsNullOrEmpty(value))
443 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName");
449 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameMissingVersion"), "frameworkName");
455 /// This method checks for CompatibilitySwitches for SL8.1 scenarios.
456 /// PS - This is used only for SL 8.1
458 [System.Security.SecuritySafeCritical]
459 private static bool IsAppUnderSL81CompatMode()
461 Contract.Assert(s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked);
463 if (CompatibilitySwitches.IsAppSilverlight81)
465 // This is an SL8.1 scenario and hence it gets the same quirks as WPBlue+ settings.
466 s_AppWasBuiltForFramework = TargetFrameworkId.Phone;
467 s_AppWasBuiltForVersion = 80100;
474 #endif //FEATURE_CORECLR
476 [System.Security.SecuritySafeCritical]
477 private static void ReadTargetFrameworkId()
480 if (IsAppUnderSL81CompatMode())
482 // Since the SL does not have any Main() the reading of the TFM will not work and as a workaround we use the CompatibilitySwitch.IsAppSilverlight81
483 // to identify if the given app targets SL 8.1 and accordingly give it the value TargetFrameworkId.Phone;80100
485 // PS - This also means that the CompatMode set by AppDomain m_compatFlags with AppDomainCompatMode.APPDOMAINCOMPAT_APP_SL81
486 // will override any other mechanism like TFM, RegistryKey, env variable or config file settings. Since this option
487 // is only used by SL8.1 scenario's I don't think this is an issue and is rather desirable.
491 #endif //FEATURE_CORECLR
492 String targetFrameworkName = AppDomain.CurrentDomain.GetTargetFrameworkName();
494 var overrideValue = System.Runtime.Versioning.CompatibilitySwitch.GetValueInternal("TargetFrameworkMoniker");
495 if (!string.IsNullOrEmpty(overrideValue))
497 targetFrameworkName = overrideValue;
500 // Write to a local then to _targetFramework, after writing the version number.
501 TargetFrameworkId fxId;
503 if (targetFrameworkName == null)
506 // if we don't have a value for targetFrameworkName we need to figure out if we should give the newest behavior or not.
507 if (CompatibilitySwitches.UseLatestBehaviorWhenTFMNotSpecified)
509 fxId = TargetFrameworkId.NetFramework;
510 fxVersion = 50000; // We are going to default to the latest value for version that we have in our code.
514 fxId = TargetFrameworkId.Unspecified;
516 else if (!ParseTargetFrameworkMonikerIntoEnum(targetFrameworkName, out fxId, out fxVersion))
517 fxId = TargetFrameworkId.Unrecognized;
519 s_AppWasBuiltForFramework = fxId;
520 s_AppWasBuiltForVersion = fxVersion;
522 #endregion String Parsing