Prevent race conditions for this cached variable, by using a temporary variable,...
[mono.git] / mcs / class / IKVM.Reflection / Fusion.cs
1 /*
2   Copyright (C) 2010-2011 Jeroen Frijters
3   Copyright (C) 2011 Marek Safar
4
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20
21   Jeroen Frijters
22   jeroen@frijters.net
23   
24 */
25
26 using System;
27 using System.Runtime.InteropServices;
28 using System.Text;
29
30 namespace IKVM.Reflection
31 {
32         struct ParsedAssemblyName
33         {
34                 internal string Name;
35                 internal Version Version;
36                 internal string Culture;
37                 internal string PublicKeyToken;
38                 internal bool? Retargetable;
39                 internal ProcessorArchitecture ProcessorArchitecture;
40                 internal bool HasPublicKey;
41         }
42
43         enum ParseAssemblyResult
44         {
45                 OK,
46                 GenericError,
47                 DuplicateKey,
48         }
49
50         static class Fusion
51         {
52                 private static readonly bool UseNativeFusion = GetUseNativeFusion();
53
54                 private static bool GetUseNativeFusion()
55                 {
56                         try
57                         {
58                                 return Environment.OSVersion.Platform == PlatformID.Win32NT
59                                         && System.Type.GetType("Mono.Runtime") == null
60                                         && Environment.GetEnvironmentVariable("IKVM_DISABLE_FUSION") == null;
61                         }
62                         catch (System.Security.SecurityException)
63                         {
64                                 return false;
65                         }
66                 }
67
68                 internal static bool CompareAssemblyIdentity(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
69                 {
70                         if (UseNativeFusion)
71                         {
72                                 return CompareAssemblyIdentityNative(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out result);
73                         }
74                         else
75                         {
76                                 return CompareAssemblyIdentityPure(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out result);
77                         }
78                 }
79
80                 private static bool CompareAssemblyIdentityNative(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
81                 {
82                         bool equivalent;
83                         Marshal.ThrowExceptionForHR(CompareAssemblyIdentity(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out equivalent, out result));
84                         return equivalent;
85                 }
86
87                 [DllImport("fusion", CharSet = CharSet.Unicode)]
88                 private static extern int CompareAssemblyIdentity(string pwzAssemblyIdentity1, bool fUnified1, string pwzAssemblyIdentity2, bool fUnified2, out bool pfEquivalent, out AssemblyComparisonResult pResult);
89
90                 // internal for use by mcs
91                 internal static bool CompareAssemblyIdentityPure(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
92                 {
93                         ParsedAssemblyName name1;
94                         ParsedAssemblyName name2;
95
96                         ParseAssemblyResult r = ParseAssemblyName(assemblyIdentity1, out name1);
97                         if (r != ParseAssemblyResult.OK || (r = ParseAssemblyName(assemblyIdentity2, out name2)) != ParseAssemblyResult.OK)
98                         {
99                                 result = AssemblyComparisonResult.NonEquivalent;
100                                 switch (r)
101                                 {
102                                         case ParseAssemblyResult.DuplicateKey:
103                                                 throw new System.IO.FileLoadException();
104                                         case ParseAssemblyResult.GenericError:
105                                         default:
106                                                 throw new ArgumentException();
107                                 }
108                         }
109
110                         bool partial = IsPartial(name1);
111
112                         if ((partial && unified1) || IsPartial(name2))
113                         {
114                                 result = AssemblyComparisonResult.NonEquivalent;
115                                 throw new ArgumentException();
116                         }
117                         if (!name1.Name.Equals(name2.Name, StringComparison.InvariantCultureIgnoreCase))
118                         {
119                                 result = AssemblyComparisonResult.NonEquivalent;
120                                 return false;
121                         }
122                         if (name1.Name.Equals("mscorlib", StringComparison.InvariantCultureIgnoreCase))
123                         {
124                                 result = AssemblyComparisonResult.EquivalentFullMatch;
125                                 return true;
126                         }
127                         if (partial && name1.Culture == null)
128                         {
129                         }
130                         else if (!name1.Culture.Equals(name2.Culture, StringComparison.InvariantCultureIgnoreCase))
131                         {
132                                 result = AssemblyComparisonResult.NonEquivalent;
133                                 return false;
134                         }
135                         if (IsStrongNamed(name2))
136                         {
137                                 if (partial && name1.PublicKeyToken == null)
138                                 {
139                                 }
140                                 else if (name1.PublicKeyToken != name2.PublicKeyToken)
141                                 {
142                                         result = AssemblyComparisonResult.NonEquivalent;
143                                         return false;
144                                 }
145                                 if (partial && name1.Version == null)
146                                 {
147                                         result = AssemblyComparisonResult.EquivalentPartialMatch;
148                                         return true;
149                                 }
150                                 else if (IsFrameworkAssembly(name2))
151                                 {
152                                         result = partial ? AssemblyComparisonResult.EquivalentPartialFXUnified : AssemblyComparisonResult.EquivalentFXUnified;
153                                         return true;
154                                 }
155                                 else if (name1.Version.Revision == -1 || name2.Version.Revision == -1)
156                                 {
157                                         result = AssemblyComparisonResult.NonEquivalent;
158                                         throw new ArgumentException();
159                                 }
160                                 else if (name1.Version < name2.Version)
161                                 {
162                                         if (unified2)
163                                         {
164                                                 result = partial ? AssemblyComparisonResult.EquivalentPartialUnified : AssemblyComparisonResult.EquivalentUnified;
165                                                 return true;
166                                         }
167                                         else
168                                         {
169                                                 result = partial ? AssemblyComparisonResult.NonEquivalentPartialVersion : AssemblyComparisonResult.NonEquivalentVersion;
170                                                 return false;
171                                         }
172                                 }
173                                 else if (name1.Version > name2.Version)
174                                 {
175                                         if (unified1)
176                                         {
177                                                 result = partial ? AssemblyComparisonResult.EquivalentPartialUnified : AssemblyComparisonResult.EquivalentUnified;
178                                                 return true;
179                                         }
180                                         else
181                                         {
182                                                 result = partial ? AssemblyComparisonResult.NonEquivalentPartialVersion : AssemblyComparisonResult.NonEquivalentVersion;
183                                                 return false;
184                                         }
185                                 }
186                                 else
187                                 {
188                                         result = partial ? AssemblyComparisonResult.EquivalentPartialMatch : AssemblyComparisonResult.EquivalentFullMatch;
189                                         return true;
190                                 }
191                         }
192                         else if (IsStrongNamed(name1))
193                         {
194                                 result = AssemblyComparisonResult.NonEquivalent;
195                                 return false;
196                         }
197                         else
198                         {
199                                 result = partial ? AssemblyComparisonResult.EquivalentPartialWeakNamed : AssemblyComparisonResult.EquivalentWeakNamed;
200                                 return true;
201                         }
202                 }
203
204                 static bool IsFrameworkAssembly(ParsedAssemblyName name)
205                 {
206                         // A list of FX assemblies which require some form of remapping
207                         // When 4.0 + 1 version  is release, assemblies introduced in v4.0
208                         // will have to be added
209                         switch (name.Name)
210                         {
211                                 case "System":
212                                 case "System.Core":
213                                 case "System.Data":
214                                 case "System.Data.DataSetExtensions":
215                                 case "System.Data.Linq":
216                                 case "System.Data.OracleClient":
217                                 case "System.Data.Services":
218                                 case "System.Data.Services.Client":
219                                 case "System.IdentityModel":
220                                 case "System.IdentityModel.Selectors":
221                                 case "System.Runtime.Remoting":
222                                 case "System.Runtime.Serialization":
223                                 case "System.ServiceModel":
224                                 case "System.Transactions":
225                                 case "System.Windows.Forms":
226                                 case "System.Xml":
227                                 case "System.Xml.Linq":
228                                         return name.PublicKeyToken == "b77a5c561934e089";
229
230                                 case "System.Configuration":
231                                 case "System.Configuration.Install":
232                                 case "System.Design":
233                                 case "System.DirectoryServices":
234                                 case "System.Drawing":
235                                 case "System.Drawing.Design":
236                                 case "System.EnterpriseServices":
237                                 case "System.Management":
238                                 case "System.Messaging":
239                                 case "System.Runtime.Serialization.Formatters.Soap":
240                                 case "System.Security":
241                                 case "System.ServiceProcess":
242                                 case "System.Web":
243                                 case "System.Web.Mobile":
244                                 case "System.Web.Services":
245                                         return name.PublicKeyToken == "b03f5f7f11d50a3a";
246
247                                 case "System.ComponentModel.DataAnnotations":
248                                 case "System.ServiceModel.Web":
249                                 case "System.Web.Abstractions":
250                                 case "System.Web.Extensions":
251                                 case "System.Web.Extensions.Design":
252                                 case "System.Web.DynamicData":
253                                 case "System.Web.Routing":
254                                         return name.PublicKeyToken == "31bf3856ad364e35";
255                         }
256
257                         return false;
258                 }
259
260                 internal static ParseAssemblyResult ParseAssemblyName(string fullName, out ParsedAssemblyName parsedName)
261                 {
262                         parsedName = new ParsedAssemblyName();
263                         StringBuilder sb = new StringBuilder();
264                         int pos = 0;
265                         while (pos < fullName.Length && char.IsWhiteSpace(fullName[pos]))
266                         {
267                                 pos++;
268                         }
269                         char quoteOrComma = ',';
270                         if (pos < fullName.Length && (fullName[pos] == '\"' || fullName[pos] == '\''))
271                         {
272                                 quoteOrComma = fullName[pos++];
273                         }
274                         while (pos < fullName.Length)
275                         {
276                                 char ch = fullName[pos++];
277                                 if (ch == '\\')
278                                 {
279                                         if (pos == fullName.Length)
280                                         {
281                                                 return ParseAssemblyResult.GenericError;
282                                         }
283                                         ch = fullName[pos++];
284                                         if (ch == '\\')
285                                         {
286                                                 return ParseAssemblyResult.GenericError;
287                                         }
288                                 }
289                                 else if (ch == quoteOrComma)
290                                 {
291                                         if (ch != ',')
292                                         {
293                                                 while (pos != fullName.Length)
294                                                 {
295                                                         ch = fullName[pos++];
296                                                         if (ch == ',')
297                                                         {
298                                                                 break;
299                                                         }
300                                                         if (!char.IsWhiteSpace(ch))
301                                                         {
302                                                                 return ParseAssemblyResult.GenericError;
303                                                         }
304                                                 }
305                                         }
306                                         break;
307                                 }
308                                 else if (ch == '=' || (quoteOrComma == ',' && (ch == '\'' || ch == '"')))
309                                 {
310                                         return ParseAssemblyResult.GenericError;
311                                 }
312                                 sb.Append(ch);
313                         }
314                         parsedName.Name = sb.ToString().Trim();
315                         if (parsedName.Name.Length == 0)
316                         {
317                                 return ParseAssemblyResult.GenericError;
318                         }
319                         if (pos == fullName.Length)
320                         {
321                                 return fullName[fullName.Length - 1] != ',' ? ParseAssemblyResult.OK : ParseAssemblyResult.GenericError;
322                         }
323                         else
324                         {
325                                 System.Collections.Generic.Dictionary<string, string> unknownAttributes = null;
326                                 bool hasProcessorArchitecture = false;
327                                 string[] parts = fullName.Substring(pos).Split(',');
328                                 for (int i = 0; i < parts.Length; i++)
329                                 {
330                                         string[] kv = parts[i].Split('=');
331                                         if (kv.Length != 2)
332                                         {
333                                                 return ParseAssemblyResult.GenericError;
334                                         }
335                                         switch (kv[0].Trim().ToLowerInvariant())
336                                         {
337                                                 case "version":
338                                                         if (parsedName.Version != null)
339                                                         {
340                                                                 return ParseAssemblyResult.DuplicateKey;
341                                                         }
342                                                         if (!ParseVersion(kv[1].Trim(), out parsedName.Version))
343                                                         {
344                                                                 return ParseAssemblyResult.GenericError;
345                                                         }
346                                                         break;
347                                                 case "culture":
348                                                         if (parsedName.Culture != null)
349                                                         {
350                                                                 return ParseAssemblyResult.DuplicateKey;
351                                                         }
352                                                         if (!ParseCulture(kv[1].Trim(), out parsedName.Culture))
353                                                         {
354                                                                 return ParseAssemblyResult.GenericError;
355                                                         }
356                                                         break;
357                                                 case "publickeytoken":
358                                                         if (parsedName.PublicKeyToken != null)
359                                                         {
360                                                                 return ParseAssemblyResult.DuplicateKey;
361                                                         }
362                                                         if (!ParsePublicKeyToken(kv[1].Trim(), out parsedName.PublicKeyToken))
363                                                         {
364                                                                 return ParseAssemblyResult.GenericError;
365                                                         }
366                                                         break;
367                                                 case "publickey":
368                                                         if (parsedName.PublicKeyToken != null)
369                                                         {
370                                                                 return ParseAssemblyResult.DuplicateKey;
371                                                         }
372                                                         if (!ParsePublicKey(kv[1].Trim(), out parsedName.PublicKeyToken))
373                                                         {
374                                                                 return ParseAssemblyResult.GenericError;
375                                                         }
376                                                         parsedName.HasPublicKey = true;
377                                                         break;
378                                                 case "retargetable":
379                                                         if (parsedName.Retargetable.HasValue)
380                                                         {
381                                                                 return ParseAssemblyResult.DuplicateKey;
382                                                         }
383                                                         switch (kv[1].Trim().ToLowerInvariant())
384                                                         {
385                                                                 case "yes":
386                                                                         parsedName.Retargetable = true;
387                                                                         break;
388                                                                 case "no":
389                                                                         parsedName.Retargetable = false;
390                                                                         break;
391                                                                 default:
392                                                                         return ParseAssemblyResult.GenericError;
393                                                         }
394                                                         break;
395                                                 case "processorarchitecture":
396                                                         if (hasProcessorArchitecture)
397                                                         {
398                                                                 return ParseAssemblyResult.DuplicateKey;
399                                                         }
400                                                         hasProcessorArchitecture = true;
401                                                         switch (kv[1].Trim().ToLowerInvariant())
402                                                         {
403                                                                 case "none":
404                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.None;
405                                                                         break;
406                                                                 case "msil":
407                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.MSIL;
408                                                                         break;
409                                                                 case "x86":
410                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.X86;
411                                                                         break;
412                                                                 case "ia64":
413                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.IA64;
414                                                                         break;
415                                                                 case "amd64":
416                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.Amd64;
417                                                                         break;
418                                                                 case "arm":
419                                                                         parsedName.ProcessorArchitecture = ProcessorArchitecture.Arm;
420                                                                         break;
421                                                                 default:
422                                                                         return ParseAssemblyResult.GenericError;
423                                                         }
424                                                         break;
425                                                 default:
426                                                         if (kv[1].Trim() == "")
427                                                         {
428                                                                 return ParseAssemblyResult.GenericError;
429                                                         }
430                                                         if (unknownAttributes == null)
431                                                         {
432                                                                 unknownAttributes = new System.Collections.Generic.Dictionary<string, string>();
433                                                         }
434                                                         if (unknownAttributes.ContainsKey(kv[0].Trim().ToLowerInvariant()))
435                                                         {
436                                                                 return ParseAssemblyResult.DuplicateKey;
437                                                         }
438                                                         unknownAttributes.Add(kv[0].Trim().ToLowerInvariant(), null);
439                                                         break;
440                                         }
441                                 }
442                         }
443                         return ParseAssemblyResult.OK;
444                 }
445
446                 private static bool ParseVersion(string str, out Version version)
447                 {
448                         string[] parts = str.Split('.');
449                         if (parts.Length < 2 || parts.Length > 4)
450                         {
451                                 version = null;
452                                 ushort dummy;
453                                 // if the version consists of a single integer, it is invalid, but not invalid enough to fail the parse of the whole assembly name
454                                 return parts.Length == 1 && ushort.TryParse(parts[0], System.Globalization.NumberStyles.Integer, null, out dummy);
455                         }
456                         if (parts[0] == "" || parts[1] == "")
457                         {
458                                 // this is a strange scenario, the version is invalid, but not invalid enough to fail the parse of the whole assembly name
459                                 version = null;
460                                 return true;
461                         }
462                         ushort major, minor, build = 65535, revision = 65535;
463                         if (ushort.TryParse(parts[0], System.Globalization.NumberStyles.Integer, null, out major)
464                                 && ushort.TryParse(parts[1], System.Globalization.NumberStyles.Integer, null, out minor)
465                                 && (parts.Length <= 2 || parts[2] == "" || ushort.TryParse(parts[2], System.Globalization.NumberStyles.Integer, null, out build))
466                                 && (parts.Length <= 3 || parts[3] == "" || (parts[2] != "" && ushort.TryParse(parts[3], System.Globalization.NumberStyles.Integer, null, out revision))))
467                         {
468                                 if (parts.Length == 4 && parts[3] != "" && parts[2] != "")
469                                 {
470                                         version = new Version(major, minor, build, revision);
471                                 }
472                                 else if (parts.Length == 3 && parts[2] != "")
473                                 {
474                                         version = new Version(major, minor, build);
475                                 }
476                                 else
477                                 {
478                                         version = new Version(major, minor);
479                                 }
480                                 return true;
481                         }
482                         version = null;
483                         return false;
484                 }
485
486                 private static bool ParseCulture(string str, out string culture)
487                 {
488                         if (str == null)
489                         {
490                                 culture = null;
491                                 return false;
492                         }
493                         culture = str;
494                         return true;
495                 }
496
497                 private static bool ParsePublicKeyToken(string str, out string publicKeyToken)
498                 {
499                         if (str == null)
500                         {
501                                 publicKeyToken = null;
502                                 return false;
503                         }
504                         publicKeyToken = str.ToLowerInvariant();
505                         return true;
506                 }
507
508                 private static bool ParsePublicKey(string str, out string publicKeyToken)
509                 {
510                         if (str == null)
511                         {
512                                 publicKeyToken = null;
513                                 return false;
514                         }
515                         // HACK use AssemblyName to convert PublicKey to PublicKeyToken
516                         byte[] token = new System.Reflection.AssemblyName("Foo, PublicKey=" + str).GetPublicKeyToken();
517                         StringBuilder sb = new StringBuilder(token.Length * 2);
518                         for (int i = 0; i < token.Length; i++)
519                         {
520                                 sb.AppendFormat("{0:x2}", token[i]);
521                         }
522                         publicKeyToken = sb.ToString();
523                         return true;
524                 }
525
526                 private static bool IsPartial(ParsedAssemblyName name)
527                 {
528                         return name.Version == null || name.Culture == null || name.PublicKeyToken == null;
529                 }
530
531                 private static bool IsStrongNamed(ParsedAssemblyName name)
532                 {
533                         return name.PublicKeyToken != null && name.PublicKeyToken != "null";
534                 }
535
536                 private static bool IsEqual(byte[] b1, byte[] b2)
537                 {
538                         if (b1.Length != b2.Length)
539                         {
540                                 return false;
541                         }
542                         for (int i = 0; i < b1.Length; i++)
543                         {
544                                 if (b1[i] != b2[i])
545                                 {
546                                         return false;
547                                 }
548                         }
549                         return true;
550                 }
551         }
552 }