2 Copyright (C) 2010-2011 Jeroen Frijters
3 Copyright (C) 2011 Marek Safar
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.
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:
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.
27 using System.Runtime.InteropServices;
30 namespace IKVM.Reflection
32 struct ParsedAssemblyName
35 internal Version Version;
36 internal string Culture;
37 internal string PublicKeyToken;
38 internal bool? Retargetable;
39 internal ProcessorArchitecture ProcessorArchitecture;
40 internal bool HasPublicKey;
43 enum ParseAssemblyResult
52 private static readonly bool UseNativeFusion = GetUseNativeFusion();
54 private static bool GetUseNativeFusion()
58 return Environment.OSVersion.Platform == PlatformID.Win32NT
59 && System.Type.GetType("Mono.Runtime") == null
60 && Environment.GetEnvironmentVariable("IKVM_DISABLE_FUSION") == null;
62 catch (System.Security.SecurityException)
68 internal static bool CompareAssemblyIdentity(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
72 return CompareAssemblyIdentityNative(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out result);
76 return CompareAssemblyIdentityPure(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out result);
80 private static bool CompareAssemblyIdentityNative(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
83 Marshal.ThrowExceptionForHR(CompareAssemblyIdentity(assemblyIdentity1, unified1, assemblyIdentity2, unified2, out equivalent, out result));
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);
90 // internal for use by mcs
91 internal static bool CompareAssemblyIdentityPure(string assemblyIdentity1, bool unified1, string assemblyIdentity2, bool unified2, out AssemblyComparisonResult result)
93 ParsedAssemblyName name1;
94 ParsedAssemblyName name2;
96 ParseAssemblyResult r = ParseAssemblyName(assemblyIdentity1, out name1);
97 if (r != ParseAssemblyResult.OK || (r = ParseAssemblyName(assemblyIdentity2, out name2)) != ParseAssemblyResult.OK)
99 result = AssemblyComparisonResult.NonEquivalent;
102 case ParseAssemblyResult.DuplicateKey:
103 throw new System.IO.FileLoadException();
104 case ParseAssemblyResult.GenericError:
106 throw new ArgumentException();
110 bool partial = IsPartial(name1);
112 if ((partial && unified1) || IsPartial(name2))
114 result = AssemblyComparisonResult.NonEquivalent;
115 throw new ArgumentException();
117 if (!name1.Name.Equals(name2.Name, StringComparison.InvariantCultureIgnoreCase))
119 result = AssemblyComparisonResult.NonEquivalent;
122 if (name1.Name.Equals("mscorlib", StringComparison.InvariantCultureIgnoreCase))
124 result = AssemblyComparisonResult.EquivalentFullMatch;
127 if (partial && name1.Culture == null)
130 else if (!name1.Culture.Equals(name2.Culture, StringComparison.InvariantCultureIgnoreCase))
132 result = AssemblyComparisonResult.NonEquivalent;
135 if (IsStrongNamed(name2))
137 if (partial && name1.PublicKeyToken == null)
140 else if (name1.PublicKeyToken != name2.PublicKeyToken)
142 result = AssemblyComparisonResult.NonEquivalent;
145 if (partial && name1.Version == null)
147 result = AssemblyComparisonResult.EquivalentPartialMatch;
150 else if (IsFrameworkAssembly(name2))
152 result = partial ? AssemblyComparisonResult.EquivalentPartialFXUnified : AssemblyComparisonResult.EquivalentFXUnified;
155 else if (name1.Version.Revision == -1 || name2.Version.Revision == -1)
157 result = AssemblyComparisonResult.NonEquivalent;
158 throw new ArgumentException();
160 else if (name1.Version < name2.Version)
164 result = partial ? AssemblyComparisonResult.EquivalentPartialUnified : AssemblyComparisonResult.EquivalentUnified;
169 result = partial ? AssemblyComparisonResult.NonEquivalentPartialVersion : AssemblyComparisonResult.NonEquivalentVersion;
173 else if (name1.Version > name2.Version)
177 result = partial ? AssemblyComparisonResult.EquivalentPartialUnified : AssemblyComparisonResult.EquivalentUnified;
182 result = partial ? AssemblyComparisonResult.NonEquivalentPartialVersion : AssemblyComparisonResult.NonEquivalentVersion;
188 result = partial ? AssemblyComparisonResult.EquivalentPartialMatch : AssemblyComparisonResult.EquivalentFullMatch;
192 else if (IsStrongNamed(name1))
194 result = AssemblyComparisonResult.NonEquivalent;
199 result = partial ? AssemblyComparisonResult.EquivalentPartialWeakNamed : AssemblyComparisonResult.EquivalentWeakNamed;
204 static bool IsFrameworkAssembly(ParsedAssemblyName name)
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
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":
227 case "System.Xml.Linq":
228 return name.PublicKeyToken == "b77a5c561934e089";
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":
243 case "System.Web.Mobile":
244 case "System.Web.Services":
245 return name.PublicKeyToken == "b03f5f7f11d50a3a";
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";
260 internal static ParseAssemblyResult ParseAssemblyName(string fullName, out ParsedAssemblyName parsedName)
262 parsedName = new ParsedAssemblyName();
263 StringBuilder sb = new StringBuilder();
265 while (pos < fullName.Length && char.IsWhiteSpace(fullName[pos]))
269 char quoteOrComma = ',';
270 if (pos < fullName.Length && (fullName[pos] == '\"' || fullName[pos] == '\''))
272 quoteOrComma = fullName[pos++];
274 while (pos < fullName.Length)
276 char ch = fullName[pos++];
279 if (pos == fullName.Length)
281 return ParseAssemblyResult.GenericError;
283 ch = fullName[pos++];
286 return ParseAssemblyResult.GenericError;
289 else if (ch == quoteOrComma)
293 while (pos != fullName.Length)
295 ch = fullName[pos++];
300 if (!char.IsWhiteSpace(ch))
302 return ParseAssemblyResult.GenericError;
308 else if (ch == '=' || (quoteOrComma == ',' && (ch == '\'' || ch == '"')))
310 return ParseAssemblyResult.GenericError;
314 parsedName.Name = sb.ToString().Trim();
315 if (parsedName.Name.Length == 0)
317 return ParseAssemblyResult.GenericError;
319 if (pos == fullName.Length)
321 return fullName[fullName.Length - 1] != ',' ? ParseAssemblyResult.OK : ParseAssemblyResult.GenericError;
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++)
330 string[] kv = parts[i].Split('=');
333 return ParseAssemblyResult.GenericError;
335 switch (kv[0].Trim().ToLowerInvariant())
338 if (parsedName.Version != null)
340 return ParseAssemblyResult.DuplicateKey;
342 if (!ParseVersion(kv[1].Trim(), out parsedName.Version))
344 return ParseAssemblyResult.GenericError;
348 if (parsedName.Culture != null)
350 return ParseAssemblyResult.DuplicateKey;
352 if (!ParseCulture(kv[1].Trim(), out parsedName.Culture))
354 return ParseAssemblyResult.GenericError;
357 case "publickeytoken":
358 if (parsedName.PublicKeyToken != null)
360 return ParseAssemblyResult.DuplicateKey;
362 if (!ParsePublicKeyToken(kv[1].Trim(), out parsedName.PublicKeyToken))
364 return ParseAssemblyResult.GenericError;
368 if (parsedName.PublicKeyToken != null)
370 return ParseAssemblyResult.DuplicateKey;
372 if (!ParsePublicKey(kv[1].Trim(), out parsedName.PublicKeyToken))
374 return ParseAssemblyResult.GenericError;
376 parsedName.HasPublicKey = true;
379 if (parsedName.Retargetable.HasValue)
381 return ParseAssemblyResult.DuplicateKey;
383 switch (kv[1].Trim().ToLowerInvariant())
386 parsedName.Retargetable = true;
389 parsedName.Retargetable = false;
392 return ParseAssemblyResult.GenericError;
395 case "processorarchitecture":
396 if (hasProcessorArchitecture)
398 return ParseAssemblyResult.DuplicateKey;
400 hasProcessorArchitecture = true;
401 switch (kv[1].Trim().ToLowerInvariant())
404 parsedName.ProcessorArchitecture = ProcessorArchitecture.None;
407 parsedName.ProcessorArchitecture = ProcessorArchitecture.MSIL;
410 parsedName.ProcessorArchitecture = ProcessorArchitecture.X86;
413 parsedName.ProcessorArchitecture = ProcessorArchitecture.IA64;
416 parsedName.ProcessorArchitecture = ProcessorArchitecture.Amd64;
419 parsedName.ProcessorArchitecture = ProcessorArchitecture.Arm;
422 return ParseAssemblyResult.GenericError;
426 if (kv[1].Trim() == "")
428 return ParseAssemblyResult.GenericError;
430 if (unknownAttributes == null)
432 unknownAttributes = new System.Collections.Generic.Dictionary<string, string>();
434 if (unknownAttributes.ContainsKey(kv[0].Trim().ToLowerInvariant()))
436 return ParseAssemblyResult.DuplicateKey;
438 unknownAttributes.Add(kv[0].Trim().ToLowerInvariant(), null);
443 return ParseAssemblyResult.OK;
446 private static bool ParseVersion(string str, out Version version)
448 string[] parts = str.Split('.');
449 if (parts.Length < 2 || parts.Length > 4)
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);
456 if (parts[0] == "" || parts[1] == "")
458 // this is a strange scenario, the version is invalid, but not invalid enough to fail the parse of the whole assembly name
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))))
468 if (parts.Length == 4 && parts[3] != "" && parts[2] != "")
470 version = new Version(major, minor, build, revision);
472 else if (parts.Length == 3 && parts[2] != "")
474 version = new Version(major, minor, build);
478 version = new Version(major, minor);
486 private static bool ParseCulture(string str, out string culture)
497 private static bool ParsePublicKeyToken(string str, out string publicKeyToken)
501 publicKeyToken = null;
504 publicKeyToken = str.ToLowerInvariant();
508 private static bool ParsePublicKey(string str, out string publicKeyToken)
512 publicKeyToken = null;
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++)
520 sb.AppendFormat("{0:x2}", token[i]);
522 publicKeyToken = sb.ToString();
526 private static bool IsPartial(ParsedAssemblyName name)
528 return name.Version == null || name.Culture == null || name.PublicKeyToken == null;
531 private static bool IsStrongNamed(ParsedAssemblyName name)
533 return name.PublicKeyToken != null && name.PublicKeyToken != "null";
536 private static bool IsEqual(byte[] b1, byte[] b2)
538 if (b1.Length != b2.Length)
542 for (int i = 0; i < b1.Length; i++)