Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / mscorlib / system / runtime / versioning / binarycompatibility.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 /*============================================================
7 **
8 ** Class:  BinaryCompatibility
9 ** 
10 ** <OWNER>[....]</OWNER>
11 **
12 **
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.
18 **
19 ** 
20 ===========================================================*/
21 using System;
22 using System.Diagnostics.Contracts;
23 using System.Globalization;
24 using System.Runtime.CompilerServices;
25
26 namespace System.Runtime.Versioning
27 {
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.
32     // 
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!
36     // 
37     // To use this class:
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:
41     //
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
44     // }
45     // else {
46     //     // Legacy behavior
47     // }
48     //
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.
52     // 
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)?  
57     // 
58     // History:
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.
64     //
65     //            Desktop            Silverlight             Windows Phone
66     //      .NET Framework 3.0   ->  Silverlight 2
67     //      .NET Framework 3.5
68     //                               Silverlight 3
69     //                               Silverlight 4
70     //      .NET Framework 4                                   Phone 8.0
71     //      .NET Framework 4.5                                 Phone 8.1
72     //      .NET Framework 4.5.1                               Phone 8.1
73     //           
74     // (Note: Windows Phone 7.0 was built using the .NET Compact Framework, which forked around v1 or v1.1)
75     // 
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!
81     [FriendAccessAllowed]
82     internal static class BinaryCompatibility
83     {
84         // Use this for new behavior introduced in the phone branch.  It will do the right thing for desktop & SL.
85         [FriendAccessAllowed]
86         internal static bool TargetsAtLeast_Phone_V7_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V7_1; } }
87
88         [FriendAccessAllowed]
89         internal static bool TargetsAtLeast_Phone_V8_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Phone_V8_0; } }
90
91         // Use this for new behavior introduced in the Desktop branch.  It will do the right thing for Phone & SL.
92         [FriendAccessAllowed]
93         internal static bool TargetsAtLeast_Desktop_V4_5 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5; } }
94         [FriendAccessAllowed]
95         internal static bool TargetsAtLeast_Desktop_V4_5_1 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_1; } }
96         [FriendAccessAllowed]
97         internal static bool TargetsAtLeast_Desktop_V4_5_2 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V4_5_2; } }
98         [FriendAccessAllowed]
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; } }
102
103         [FriendAccessAllowed]
104         internal static bool TargetsAtLeast_Desktop_V5_0 { [FriendAccessAllowed] get { return s_map.TargetsAtLeast_Desktop_V5_0; } }
105
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; } }
113
114         [FriendAccessAllowed]
115         internal static TargetFrameworkId AppWasBuiltForFramework {
116             [FriendAccessAllowed]
117             get {
118                 Contract.Ensures(Contract.Result<TargetFrameworkId>() > TargetFrameworkId.NotYetChecked);
119
120                 if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked)
121                     ReadTargetFrameworkId();
122
123                 return s_AppWasBuiltForFramework;
124             }
125         }
126
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]
131             get {
132                 Contract.Ensures(Contract.Result<int>() > 0 || s_AppWasBuiltForFramework == TargetFrameworkId.Unspecified);
133
134                 if (s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked)
135                     ReadTargetFrameworkId();
136
137                 Contract.Assert(s_AppWasBuiltForFramework != TargetFrameworkId.Unrecognized);
138
139                 return s_AppWasBuiltForVersion;
140             }
141         }
142
143         #region private
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;
147
148         readonly static BinaryCompatibilityMap s_map = new BinaryCompatibilityMap();
149         
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";
156
157         /// <summary>
158         /// BinaryCompatibilityMap is basically a bitvector.  There is a boolean field for each of the
159         /// properties in BinaryCompatibility
160         /// </summary>
161         private sealed class BinaryCompatibilityMap
162         {
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;
176
177             internal BinaryCompatibilityMap()
178             {
179                 AddQuirksForFramework(AppWasBuiltForFramework, AppWasBuiltForVersion);
180             }
181
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)
187             {
188                 Contract.Requires(buildAgainstVersion > 0  || builtAgainstFramework == TargetFrameworkId.Unspecified);
189
190                 switch (builtAgainstFramework)
191                 {
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;
196
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;
206
207                         if (buildAgainstVersion >= 40500)
208                         {
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);
215                         }
216                         break;
217
218                     case TargetFrameworkId.Phone:
219                         if (buildAgainstVersion >= 80000)
220                         {
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;
224                         }
225                         if (buildAgainstVersion >= 80100)
226                         {
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;
230                         }
231
232                         if (buildAgainstVersion >= 710)
233                             TargetsAtLeast_Phone_V7_1 = true;
234                         break;
235
236                     case TargetFrameworkId.Silverlight:
237                         if (buildAgainstVersion >= 40000)
238                             TargetsAtLeast_Silverlight_V4 = true;
239
240                         if (buildAgainstVersion >= 50000)
241                             TargetsAtLeast_Silverlight_V5 = true;
242
243                         if (buildAgainstVersion >= 60000)
244                         {
245                             TargetsAtLeast_Silverlight_V6 = true;
246                             // @
247
248                         }
249                         break;
250
251                     case TargetFrameworkId.Unspecified:
252                         break;
253
254                     case TargetFrameworkId.NotYetChecked:
255                     case TargetFrameworkId.Unrecognized:
256                         Contract.Assert(false, "Bad framework kind");
257                         break;
258                     default:
259                         Contract.Assert(false, "Error: we introduced a new Target Framework but did not update our binary compatibility map");
260                         break;
261                 }
262             }
263         }
264
265         #region String Parsing
266
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)
269         {
270             Contract.Requires(!String.IsNullOrEmpty(targetFrameworkMoniker));
271
272             targetFramework = TargetFrameworkId.NotYetChecked;
273             targetFrameworkVersion = 0;
274
275             String identifier = null;
276             String profile = null;
277             ParseFrameworkName(targetFrameworkMoniker, out identifier, out targetFrameworkVersion, out profile);
278
279             switch (identifier)
280             {
281                 case ".NETFramework":
282                     targetFramework = TargetFrameworkId.NetFramework;
283                     break;
284
285                 case ".NETPortable":
286                     targetFramework = TargetFrameworkId.Portable;
287                     break;
288
289                 case ".NETCore":
290                     targetFramework = TargetFrameworkId.NetCore;
291                     break;
292
293                 case "WindowsPhone":
294                     if (targetFrameworkVersion >= 80100)
295                     {
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;
299                     }
300                     else
301                     {
302                         // There is no TFM for Apollo or below and hence we assign the targetFramework to Unspecified. 
303                         targetFramework = TargetFrameworkId.Unspecified;
304                     }
305                     break;
306
307                 case "WindowsPhoneApp":
308                     targetFramework = TargetFrameworkId.Phone;
309                     break;
310
311                 case "Silverlight":
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))
316                     {
317                         if (profile == "WindowsPhone")
318                         {
319                             targetFramework = TargetFrameworkId.Phone;
320                             targetFrameworkVersion = 70000;
321                         }
322                         else if (profile == "WindowsPhone71")
323                         {
324                             targetFramework = TargetFrameworkId.Phone;
325                             targetFrameworkVersion = 70100;
326                         }
327                         else if (profile == "WindowsPhone8")  // @
328                         {
329                             targetFramework = TargetFrameworkId.Phone;
330                             targetFrameworkVersion = 80000;
331                         }
332                         else if (profile.StartsWith("WindowsPhone", StringComparison.Ordinal))
333                         {
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;
337                         }
338                         else
339                         {
340                             Contract.Assert(false, String.Format(CultureInfo.InvariantCulture, "Unrecognized Silverlight profile \"{0}\".  What is this, an XBox app?", profile));
341                             targetFramework = TargetFrameworkId.Unrecognized;
342                         }
343                     }
344                     break;
345
346                 default:
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;
349                     break;
350             }
351
352             return true;
353         }
354
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)
361         {
362             if (frameworkName == null)
363             {
364                 throw new ArgumentNullException("frameworkName");
365             }
366             if (frameworkName.Length == 0)
367             {
368                 throw new ArgumentException(Environment.GetResourceString("Argument_StringZeroLength"), "frameworkName");
369             }
370             Contract.EndContractBlock();
371
372             String[] components = frameworkName.Split(c_componentSeparator);
373             version = 0;
374
375             // Identifer and Version are required, Profile is optional.
376             if (components.Length < 2 || components.Length > 3)
377             {
378                 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameTooShort"), "frameworkName");
379             }
380
381             //
382             // 1) Parse the "Identifier", which must come first. Trim any whitespace
383             //
384             identifier = components[0].Trim();
385
386             if (identifier.Length == 0)
387             {
388                 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName");
389             }
390
391             bool versionFound = false;
392             profile = null;
393
394             // 
395             // The required "Version" and optional "Profile" component can be in any order
396             //
397             for (int i = 1; i < components.Length; i++)
398             {
399                 // Get the key/value pair separated by '='
400                 string[] keyValuePair = components[i].Split(c_keyValueSeparator);
401
402                 if (keyValuePair.Length != 2)
403                 {
404                     throw new ArgumentException(Environment.GetResourceString("SR.Argument_FrameworkNameInvalid"), "frameworkName");
405                 }
406
407                 // Get the key and value, trimming any whitespace
408                 string key = keyValuePair[0].Trim();
409                 string value = keyValuePair[1].Trim();
410
411                 //
412                 // 2) Parse the required "Version" key value
413                 //
414                 if (key.Equals(c_versionKey, StringComparison.OrdinalIgnoreCase))
415                 {
416                     versionFound = true;
417
418                     // Allow the version to include a 'v' or 'V' prefix...
419                     if (value.Length > 0 && (value[0] == c_versionValuePrefix || value[0] == 'V'))
420                     {
421                         value = value.Substring(1);
422                     }
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;
430                 }
431                 //
432                 // 3) Parse the optional "Profile" key value
433                 //
434                 else if (key.Equals(c_profileKey, StringComparison.OrdinalIgnoreCase))
435                 {
436                     if (!String.IsNullOrEmpty(value))
437                     {
438                         profile = value;
439                     }
440                 }
441                 else
442                 {
443                     throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameInvalid"), "frameworkName");
444                 }
445             }
446
447             if (!versionFound)
448             {
449                 throw new ArgumentException(Environment.GetResourceString("Argument_FrameworkNameMissingVersion"), "frameworkName");
450             }
451         }
452
453 #if FEATURE_CORECLR
454         /// <summary>
455         /// This method checks for CompatibilitySwitches for SL8.1 scenarios.
456         /// PS - This is used only for SL 8.1
457         /// </summary>
458         [System.Security.SecuritySafeCritical]
459         private static bool IsAppUnderSL81CompatMode()
460         {
461             Contract.Assert(s_AppWasBuiltForFramework == TargetFrameworkId.NotYetChecked);
462
463             if (CompatibilitySwitches.IsAppSilverlight81)
464             {
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;
468
469                 return true;
470             }
471
472             return false;
473         }
474 #endif //FEATURE_CORECLR
475
476         [System.Security.SecuritySafeCritical]
477         private static void ReadTargetFrameworkId()
478         {
479 #if FEATURE_CORECLR
480             if (IsAppUnderSL81CompatMode())
481             {
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
484
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.
488
489                 return;
490             }
491 #endif //FEATURE_CORECLR
492             String targetFrameworkName = AppDomain.CurrentDomain.GetTargetFrameworkName();
493
494             var overrideValue = System.Runtime.Versioning.CompatibilitySwitch.GetValueInternal("TargetFrameworkMoniker");
495             if (!string.IsNullOrEmpty(overrideValue))
496             {
497                 targetFrameworkName = overrideValue;
498             }
499
500             // Write to a local then to _targetFramework, after writing the version number.
501             TargetFrameworkId fxId;
502             int fxVersion = 0;
503             if (targetFrameworkName == null)
504             {
505 #if FEATURE_CORECLR
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)
508                 {
509                     fxId = TargetFrameworkId.NetFramework;
510                     fxVersion = 50000; // We are going to default to the latest value for version that we have in our code.
511                 }
512                 else
513 #endif
514                     fxId = TargetFrameworkId.Unspecified;
515             }
516             else if (!ParseTargetFrameworkMonikerIntoEnum(targetFrameworkName, out fxId, out fxVersion))
517                 fxId = TargetFrameworkId.Unrecognized;
518
519             s_AppWasBuiltForFramework = fxId;
520             s_AppWasBuiltForVersion = fxVersion;
521         }
522         #endregion String Parsing
523
524         #endregion private
525     }
526 }