1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Text; 6 using System.Reflection; 7 using System.Collections; 8 using System.Globalization; 9 using System.Diagnostics; 10 using System.Collections.Generic; 11 using System.Configuration.Assemblies; 12 using System.Runtime.Serialization; 13 using System.IO; 14 #if !FEATURE_ASSEMBLY_LOADFROM || MONO 15 using System.Reflection.PortableExecutable; 16 using System.Reflection.Metadata; 17 #endif 18 19 namespace Microsoft.Build.Shared 20 { 21 /// <summary> 22 /// Specifies the parts of the assembly name to partially match 23 /// </summary> 24 [FlagsAttribute] 25 internal enum PartialComparisonFlags : int 26 { 27 /// <summary> 28 /// Compare SimpleName A.PartialCompare(B,SimpleName) match the simple name on A and B if the simple name on A is not null. 29 /// </summary> 30 SimpleName = 1, // 0000 0000 0000 0001 31 32 /// <summary> 33 /// Compare Version A.PartialCompare(B, Version) match the Version on A and B if the Version on A is not null. 34 /// </summary> 35 Version = 2, // 0000 0000 0000 0010 36 37 /// <summary> 38 /// Compare Culture A.PartialCompare(B, Culture) match the Culture on A and B if the Culture on A is not null. 39 /// </summary> 40 Culture = 4, // 0000 0000 0000 0100 41 42 /// <summary> 43 /// Compare PublicKeyToken A.PartialCompare(B, PublicKeyToken) match the PublicKeyToken on A and B if the PublicKeyToken on A is not null. 44 /// </summary> 45 PublicKeyToken = 8, // 0000 0000 0000 1000 46 47 /// <summary> 48 /// When doing a comparison A.PartialCompare(B, Default) compare all fields of A which are not null with B. 49 /// </summary> 50 Default = 15, // 0000 0000 0000 1111 51 } 52 53 /// <summary> 54 /// A replacement for AssemblyName that optimizes calls to FullName which is expensive. 55 /// The assembly name is represented internally by an AssemblyName and a string, conversion 56 /// between the two is done lazily on demand. 57 /// </summary> 58 [Serializable] 59 internal sealed class AssemblyNameExtension : ISerializable, IEquatable<AssemblyNameExtension> 60 { 61 private AssemblyName asAssemblyName = null; 62 private string asString = null; 63 private bool isSimpleName = false; 64 private bool hasProcessorArchitectureInFusionName; 65 private bool immutable; 66 67 /// <summary> 68 /// Set of assemblyNameExtensions that THIS assemblyname was remapped from. 69 /// </summary> 70 private HashSet<AssemblyNameExtension> remappedFrom; 71 72 private static readonly AssemblyNameExtension s_unnamedAssembly = new AssemblyNameExtension(); 73 74 /// <summary> 75 /// Construct an unnamed assembly. 76 /// Private because we want only one of these. 77 /// </summary> AssemblyNameExtension()78 private AssemblyNameExtension() 79 { 80 } 81 82 /// <summary> 83 /// Construct with AssemblyName. 84 /// </summary> 85 /// <param name="assemblyName"></param> AssemblyNameExtension(AssemblyName assemblyName)86 internal AssemblyNameExtension(AssemblyName assemblyName) : this() 87 { 88 asAssemblyName = assemblyName; 89 } 90 91 /// <summary> 92 /// Construct with string. 93 /// </summary> 94 /// <param name="assemblyName"></param> AssemblyNameExtension(string assemblyName)95 internal AssemblyNameExtension(string assemblyName) : this() 96 { 97 asString = assemblyName; 98 } 99 100 /// <summary> 101 /// Construct from a string, but immediately construct a real AssemblyName. 102 /// This will cause an exception to be thrown up front if the assembly name 103 /// isn't well formed. 104 /// </summary> 105 /// <param name="assemblyName"> 106 /// The string version of the assembly name. 107 /// </param> 108 /// <param name="validate"> 109 /// Used when the assembly name comes from a user-controlled source like a project file or config file. 110 /// Does extra checking on the assembly name and will throw exceptions if something is invalid. 111 /// </param> AssemblyNameExtension(string assemblyName, bool validate)112 internal AssemblyNameExtension(string assemblyName, bool validate) : this() 113 { 114 asString = assemblyName; 115 116 if (validate) 117 { 118 // This will throw... 119 CreateAssemblyName(); 120 } 121 } 122 123 /// <summary> 124 /// Ctor for deserializing from state file (binary serialization). 125 /// <remarks>This is required because AssemblyName is not Serializable on .NET Core.</remarks> 126 /// </summary> 127 /// <param name="info"></param> 128 /// <param name="context"></param> AssemblyNameExtension(SerializationInfo info, StreamingContext context)129 private AssemblyNameExtension(SerializationInfo info, StreamingContext context) 130 { 131 var hasAssemblyName = info.GetBoolean("hasAN"); 132 133 if (hasAssemblyName) 134 { 135 var name = info.GetString("name"); 136 var publicKey = (byte[]) info.GetValue("pk", typeof(byte[])); 137 var publicKeyToken = (byte[]) info.GetValue("pkt", typeof(byte[])); 138 var version = (Version)info.GetValue("ver", typeof(Version)); 139 var flags = (AssemblyNameFlags) info.GetInt32("flags"); 140 var processorArchitecture = (ProcessorArchitecture) info.GetInt32("cpuarch"); 141 142 CultureInfo cultureInfo = null; 143 var hasCultureInfo = info.GetBoolean("hasCI"); 144 if (hasCultureInfo) 145 { 146 cultureInfo = new CultureInfo(info.GetInt32("ci")); 147 } 148 149 var hashAlgorithm = (System.Configuration.Assemblies.AssemblyHashAlgorithm) info.GetInt32("hashAlg"); 150 var versionCompatibility = (AssemblyVersionCompatibility) info.GetInt32("verCompat"); 151 var codeBase = info.GetString("codebase"); 152 var keyPair = (StrongNameKeyPair) info.GetValue("keypair", typeof(StrongNameKeyPair)); 153 154 asAssemblyName = new AssemblyName 155 { 156 Name = name, 157 Version = version, 158 Flags = flags, 159 ProcessorArchitecture = processorArchitecture, 160 CultureInfo = cultureInfo, 161 HashAlgorithm = hashAlgorithm, 162 VersionCompatibility = versionCompatibility, 163 CodeBase = codeBase, 164 KeyPair = keyPair 165 }; 166 167 asAssemblyName.SetPublicKey(publicKey); 168 asAssemblyName.SetPublicKeyToken(publicKeyToken); 169 } 170 171 asString = info.GetString("asStr"); 172 isSimpleName = info.GetBoolean("isSName"); 173 hasProcessorArchitectureInFusionName = info.GetBoolean("hasCpuArch"); 174 immutable = info.GetBoolean("immutable"); 175 remappedFrom = (HashSet<AssemblyNameExtension>) info.GetValue("remapped", typeof(HashSet<AssemblyNameExtension>)); 176 } 177 178 /// <summary> 179 /// To be used as a delegate. Gets the AssemblyName of the given file. 180 /// </summary> 181 /// <param name="path"></param> 182 /// <returns></returns> GetAssemblyNameEx(string path)183 internal static AssemblyNameExtension GetAssemblyNameEx(string path) 184 { 185 AssemblyName assemblyName = null; 186 #if FEATURE_ASSEMBLY_LOADFROM && !MONO 187 try 188 { 189 assemblyName = AssemblyName.GetAssemblyName(path); 190 } 191 catch (System.IO.FileLoadException) 192 { 193 // Its pretty hard to get here, you need an assembly that contains a valid reference 194 // to a dependent assembly that, in turn, throws a FileLoadException during GetAssemblyName. 195 // Still it happened once, with an older version of the CLR. 196 197 // ...falling through and relying on the targetAssemblyName==null behavior below... 198 } 199 catch (System.IO.FileNotFoundException) 200 { 201 // Its pretty hard to get here, also since we do a file existence check right before calling this method so it can only happen if the file got deleted between that check and this call. 202 } 203 #else 204 using (var stream = File.OpenRead(path)) 205 using (var peFile = new PEReader(stream)) 206 { 207 var metadataReader = peFile.GetMetadataReader(); 208 209 var entry = metadataReader.GetAssemblyDefinition(); 210 211 assemblyName = new AssemblyName(); 212 assemblyName.Name = metadataReader.GetString(entry.Name); 213 assemblyName.Version = entry.Version; 214 var cultureString = metadataReader.GetString(entry.Culture); 215 if (!NativeMethodsShared.IsMono) 216 { 217 // set_CultureName throws NotImplementedException on Mono 218 assemblyName.CultureName = cultureString; 219 } 220 else if (cultureString != null) 221 { 222 assemblyName.CultureInfo = new CultureInfo(cultureString); 223 } 224 assemblyName.SetPublicKey(metadataReader.GetBlobBytes(entry.PublicKey)); 225 assemblyName.Flags = (AssemblyNameFlags)(int)entry.Flags; 226 } 227 #endif 228 return assemblyName == null ? null : new AssemblyNameExtension(assemblyName); 229 } 230 231 /// <summary> 232 /// Run after the object has been deserialized 233 /// </summary> 234 [OnDeserialized] SetRemappedFromDefaultAfterSerialization(StreamingContext sc)235 private void SetRemappedFromDefaultAfterSerialization(StreamingContext sc) 236 { 237 InitializeRemappedFrom(); 238 } 239 240 /// <summary> 241 /// Initialize the remapped from structure. 242 /// </summary> InitializeRemappedFrom()243 private void InitializeRemappedFrom() 244 { 245 if (remappedFrom == null) 246 { 247 remappedFrom = new HashSet<AssemblyNameExtension>(AssemblyNameComparer.GenericComparerConsiderRetargetable); 248 } 249 } 250 251 /// <summary> 252 /// Assume there is a string version, create the AssemblyName version. 253 /// </summary> CreateAssemblyName()254 private void CreateAssemblyName() 255 { 256 if (asAssemblyName == null) 257 { 258 asAssemblyName = GetAssemblyNameFromDisplayName(asString); 259 260 if (asAssemblyName != null) 261 { 262 hasProcessorArchitectureInFusionName = asString.IndexOf("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase) != -1; 263 isSimpleName = ((Version == null) && (CultureInfo == null) && (GetPublicKeyToken() == null) && (!hasProcessorArchitectureInFusionName)); 264 } 265 } 266 } 267 268 /// <summary> 269 /// Assume there is a string version, create the AssemblyName version. 270 /// </summary> CreateFullName()271 private void CreateFullName() 272 { 273 if (asString == null) 274 { 275 asString = asAssemblyName.FullName; 276 } 277 } 278 279 /// <summary> 280 /// The base name of the assembly. 281 /// </summary> 282 /// <value></value> 283 internal string Name 284 { 285 get 286 { 287 // Is there a string? 288 CreateAssemblyName(); 289 return asAssemblyName.Name; 290 } 291 } 292 293 /// <summary> 294 /// Gets the backing AssemblyName, this can be None. 295 /// </summary> 296 internal ProcessorArchitecture ProcessorArchitecture => 297 asAssemblyName?.ProcessorArchitecture ?? ProcessorArchitecture.None; 298 299 /// <summary> 300 /// The assembly's version number. 301 /// </summary> 302 /// <value></value> 303 internal Version Version 304 { 305 get 306 { 307 // Is there a string? 308 CreateAssemblyName(); 309 return asAssemblyName.Version; 310 } 311 } 312 313 /// <summary> 314 /// Is the assembly a complex name or a simple name. A simple name is where only the name is set 315 /// a complex name is where the version, culture or publickeytoken is also set 316 /// </summary> 317 internal bool IsSimpleName 318 { 319 get 320 { 321 CreateAssemblyName(); 322 return isSimpleName; 323 } 324 } 325 326 /// <summary> 327 /// Does the fullName have the processor architecture defined 328 /// </summary> 329 internal bool HasProcessorArchitectureInFusionName 330 { 331 get 332 { 333 CreateAssemblyName(); 334 return hasProcessorArchitectureInFusionName; 335 } 336 } 337 338 /// <summary> 339 /// Replace the current version with a new version. 340 /// </summary> 341 /// <param name="version"></param> ReplaceVersion(Version version)342 internal void ReplaceVersion(Version version) 343 { 344 ErrorUtilities.VerifyThrow(!immutable, "Object is immutable cannot replace the version"); 345 CreateAssemblyName(); 346 if (asAssemblyName.Version != version) 347 { 348 asAssemblyName.Version = version; 349 350 // String would now be invalid. 351 asString = null; 352 } 353 } 354 355 /// <summary> 356 /// The assembly's Culture 357 /// </summary> 358 /// <value></value> 359 internal CultureInfo CultureInfo 360 { 361 get 362 { 363 // Is there a string? 364 CreateAssemblyName(); 365 return asAssemblyName.CultureInfo; 366 } 367 } 368 369 /// <summary> 370 /// The assembly's retargetable bit 371 /// </summary> 372 /// <value></value> 373 internal bool Retargetable 374 { 375 get 376 { 377 // Is there a string? 378 CreateAssemblyName(); 379 // Cannot use the HasFlag method on the Flags enum because this class needs to work with 3.5 380 return (asAssemblyName.Flags & AssemblyNameFlags.Retargetable) == AssemblyNameFlags.Retargetable; 381 } 382 } 383 384 /// <summary> 385 /// The full name of the original extension we were before being remapped. 386 /// </summary> 387 internal IEnumerable<AssemblyNameExtension> RemappedFromEnumerator 388 { 389 get 390 { 391 InitializeRemappedFrom(); 392 return remappedFrom; 393 } 394 } 395 396 /// <summary> 397 /// Add an assemblyNameExtension which represents an assembly name which was mapped to THIS assemblyName. 398 /// </summary> AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd)399 internal void AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd) 400 { 401 ErrorUtilities.VerifyThrow(extensionToAdd.Immutable, "ExtensionToAdd is not immutable"); 402 InitializeRemappedFrom(); 403 remappedFrom.Add(extensionToAdd); 404 } 405 406 /// <summary> 407 /// As an AssemblyName 408 /// </summary> 409 /// <value></value> 410 internal AssemblyName AssemblyName 411 { 412 get 413 { 414 // Is there a string? 415 CreateAssemblyName(); 416 return asAssemblyName; 417 } 418 } 419 420 /// <summary> 421 /// The assembly's full name. 422 /// </summary> 423 /// <value></value> 424 internal string FullName 425 { 426 get 427 { 428 // Is there a string? 429 CreateFullName(); 430 return asString; 431 } 432 } 433 434 /// <summary> 435 /// Get the assembly's public key token. 436 /// </summary> 437 /// <returns></returns> GetPublicKeyToken()438 internal byte[] GetPublicKeyToken() 439 { 440 // Is there a string? 441 CreateAssemblyName(); 442 return asAssemblyName.GetPublicKeyToken(); 443 } 444 445 446 /// <summary> 447 /// A special "unnamed" instance of AssemblyNameExtension. 448 /// </summary> 449 /// <value></value> 450 internal static AssemblyNameExtension UnnamedAssembly => s_unnamedAssembly; 451 452 /// <summary> 453 /// Compare one assembly name to another. 454 /// </summary> 455 /// <param name="that"></param> 456 /// <returns></returns> CompareTo(AssemblyNameExtension that)457 internal int CompareTo(AssemblyNameExtension that) 458 { 459 return CompareTo(that, false); 460 } 461 462 /// <summary> 463 /// Compare one assembly name to another. 464 /// </summary> CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag)465 internal int CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag) 466 { 467 // Are they identical? 468 if (this.Equals(that, considerRetargetableFlag)) 469 { 470 return 0; 471 } 472 473 // Are the base names not identical? 474 int result = CompareBaseNameTo(that); 475 if (result != 0) 476 { 477 return result; 478 } 479 480 // We would like to compare the version numerically rather than alphabetically (because for example version 10.0.0. should be below 9 not between 1 and 2) 481 if (this.Version != that.Version) 482 { 483 if (this.Version == null) 484 { 485 // This is therefore less than that. Since this is null and that is not null 486 return -1; 487 } 488 489 // Will not return 0 as the this != that check above takes care of the case where they are equal. 490 result = this.Version.CompareTo(that.Version); 491 return result; 492 } 493 494 // We need some final collating order for these, alphabetical by FullName seems as good as any. 495 return string.Compare(this.FullName, that.FullName, StringComparison.OrdinalIgnoreCase); 496 } 497 498 /// <summary> 499 /// Get a hash code for this assembly name. 500 /// </summary> 501 /// <returns></returns> GetHashCode()502 internal new int GetHashCode() 503 { 504 // Ok, so this isn't a great hashing algorithm. However, basenames with different 505 // versions or PKTs are relatively uncommon and so collisions should be low. 506 // Hashing on FullName is wrong because the order of tuple fields is undefined. 507 int hash = StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name); 508 return hash; 509 } 510 511 /// <summary> 512 /// Compare two base names as quickly as possible. 513 /// </summary> 514 /// <param name="that"></param> 515 /// <returns></returns> CompareBaseNameTo(AssemblyNameExtension that)516 internal int CompareBaseNameTo(AssemblyNameExtension that) 517 { 518 int result = CompareBaseNameToImpl(that); 519 #if DEBUG 520 // Now, compare to the real value to make sure the result was accurate. 521 AssemblyName a1 = asAssemblyName; 522 AssemblyName a2 = that.asAssemblyName; 523 if (a1 == null) 524 { 525 a1 = new AssemblyName(asString); 526 } 527 if (a2 == null) 528 { 529 a2 = new AssemblyName(that.asString); 530 } 531 532 int baselineResult = string.Compare(a1.Name, a2.Name, StringComparison.OrdinalIgnoreCase); 533 ErrorUtilities.VerifyThrow(result == baselineResult, "Optimized version of CompareBaseNameTo didn't return the same result as the baseline."); 534 #endif 535 return result; 536 } 537 538 /// <summary> 539 /// An implementation of compare that compares two base 540 /// names as quickly as possible. 541 /// </summary> 542 /// <param name="that"></param> 543 /// <returns></returns> CompareBaseNameToImpl(AssemblyNameExtension that)544 private int CompareBaseNameToImpl(AssemblyNameExtension that) 545 { 546 // Pointer compare, if identical then base names are equal. 547 if (this == that) 548 { 549 return 0; 550 } 551 552 // Do both have assembly names? 553 if (asAssemblyName != null && that.asAssemblyName != null) 554 { 555 // Pointer compare or base name compare. 556 return asAssemblyName == that.asAssemblyName 557 ? 0 558 : string.Compare(asAssemblyName.Name, that.asAssemblyName.Name, StringComparison.OrdinalIgnoreCase); 559 } 560 561 // Do both have strings? 562 if (asString != null && that.asString != null) 563 { 564 // If we have two random-case strings, then we need to compare case sensitively. 565 return CompareBaseNamesStringWise(asString, that.asString); 566 } 567 568 // Fall back to comparing by name. This is the slow path. 569 return string.Compare(this.Name, that.Name, StringComparison.OrdinalIgnoreCase); 570 } 571 572 /// <summary> 573 /// Compare two basenames. 574 /// </summary> 575 /// <param name="asString1"></param> 576 /// <param name="asString2"></param> 577 /// <returns></returns> CompareBaseNamesStringWise(string asString1, string asString2)578 private static int CompareBaseNamesStringWise(string asString1, string asString2) 579 { 580 // Identical strings just match. 581 if (asString1 == asString2) 582 { 583 return 0; 584 } 585 586 // Get the lengths of base names to compare. 587 int baseLenThis = asString1.IndexOf(','); 588 int baseLenThat = asString2.IndexOf(','); 589 if (baseLenThis == -1) 590 { 591 baseLenThis = asString1.Length; 592 } 593 if (baseLenThat == -1) 594 { 595 baseLenThat = asString2.Length; 596 } 597 598 // If the lengths are the same then we can compare without copying. 599 if (baseLenThis == baseLenThat) 600 { 601 return string.Compare(asString1, 0, asString2, 0, baseLenThis, StringComparison.OrdinalIgnoreCase); 602 } 603 604 // Lengths are different, so string copy is required. 605 string nameThis = asString1.Substring(0, baseLenThis); 606 string nameThat = asString2.Substring(0, baseLenThat); 607 return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); 608 } 609 610 /// <summary> 611 /// Clone this assemblyNameExtension 612 /// </summary> Clone()613 internal AssemblyNameExtension Clone() 614 { 615 AssemblyNameExtension newExtension = new AssemblyNameExtension(); 616 617 if (asAssemblyName != null) 618 { 619 newExtension.asAssemblyName = asAssemblyName.CloneIfPossible(); 620 } 621 622 newExtension.asString = asString; 623 newExtension.isSimpleName = isSimpleName; 624 newExtension.hasProcessorArchitectureInFusionName = hasProcessorArchitectureInFusionName; 625 newExtension.remappedFrom = remappedFrom; 626 627 // We are cloning so we can now party on the object even if the parent was immutable 628 newExtension.immutable = false; 629 630 return newExtension; 631 } 632 633 /// <summary> 634 /// Clone the object but mark and mark the cloned object as immutable 635 /// </summary> 636 /// <returns></returns> CloneImmutable()637 internal AssemblyNameExtension CloneImmutable() 638 { 639 AssemblyNameExtension clonedExtension = Clone(); 640 clonedExtension.MarkImmutable(); 641 return clonedExtension; 642 } 643 644 /// <summary> 645 /// Is this object immutable 646 /// </summary> 647 public bool Immutable => immutable; 648 649 /// <summary> 650 /// Mark this object as immutable 651 /// </summary> MarkImmutable()652 internal void MarkImmutable() 653 { 654 immutable = true; 655 } 656 657 /// <summary> 658 /// Compare two assembly names for equality. 659 /// </summary> 660 /// <param name="that"></param> 661 /// <returns></returns> Equals(AssemblyNameExtension that)662 internal bool Equals(AssemblyNameExtension that) 663 { 664 return EqualsImpl(that, false, false); 665 } 666 667 /// <summary> 668 /// Interface method for IEquatable<AssemblyNameExtension> 669 /// </summary> 670 /// <param name="other"></param> 671 /// <returns></returns> Equals(AssemblyNameExtension other)672 bool IEquatable<AssemblyNameExtension>.Equals(AssemblyNameExtension other) 673 { 674 return Equals(other); 675 } 676 677 /// <summary> 678 /// Compare two assembly names for equality ignoring version. 679 /// </summary> 680 /// <param name="that"></param> 681 /// <returns></returns> EqualsIgnoreVersion(AssemblyNameExtension that)682 internal bool EqualsIgnoreVersion(AssemblyNameExtension that) 683 { 684 return EqualsImpl(that, true, false); 685 } 686 687 /// <summary> 688 /// Compare two assembly names and consider the retargetable flag during the comparison 689 /// </summary> Equals(AssemblyNameExtension that, bool considerRetargetableFlag)690 internal bool Equals(AssemblyNameExtension that, bool considerRetargetableFlag) 691 { 692 return EqualsImpl(that, false, considerRetargetableFlag); 693 } 694 695 /// <summary> 696 /// Compare two assembly names for equality. 697 /// </summary> EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag)698 private bool EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag) 699 { 700 // Pointer compare. 701 if (object.ReferenceEquals(this, that)) 702 { 703 return true; 704 } 705 706 // If that is null then this and that are not equal. Also, this would cause a crash on the next line. 707 if (object.ReferenceEquals(that, null)) 708 { 709 return false; 710 } 711 712 // Do both have assembly names? 713 if (asAssemblyName != null && that.asAssemblyName != null) 714 { 715 // Pointer compare. 716 if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) 717 { 718 return true; 719 } 720 } 721 722 // Do both have strings that equal each-other? 723 if (asString != null && that.asString != null) 724 { 725 if (asString == that.asString) 726 { 727 return true; 728 } 729 730 // If they weren't identical then they might still differ only by 731 // case. So we can't assume that they don't match. So fall through... 732 } 733 734 // Do the names match? 735 if (0 != string.Compare(Name, that.Name, StringComparison.OrdinalIgnoreCase)) 736 { 737 return false; 738 } 739 740 if (!ignoreVersion && (this.Version != that.Version)) 741 { 742 return false; 743 } 744 745 if (!CompareCultures(AssemblyName, that.AssemblyName)) 746 { 747 return false; 748 } 749 750 if (!ComparePublicKeyToken(that)) 751 { 752 return false; 753 } 754 755 if (considerRetargetableFlag && this.Retargetable != that.Retargetable) 756 { 757 return false; 758 } 759 760 return true; 761 } 762 763 /// <summary> 764 /// Allows the comparison of the culture. 765 /// </summary> CompareCultures(AssemblyName a, AssemblyName b)766 internal static bool CompareCultures(AssemblyName a, AssemblyName b) 767 { 768 // Do the Cultures match? 769 CultureInfo aCulture = a.CultureInfo; 770 CultureInfo bCulture = b.CultureInfo; 771 if (aCulture == null) 772 { 773 aCulture = CultureInfo.InvariantCulture; 774 } 775 if (bCulture == null) 776 { 777 bCulture = CultureInfo.InvariantCulture; 778 } 779 780 return aCulture.LCID == bCulture.LCID; 781 } 782 783 /// <summary> 784 /// Allows the comparison of just the PublicKeyToken 785 /// </summary> ComparePublicKeyToken(AssemblyNameExtension that)786 internal bool ComparePublicKeyToken(AssemblyNameExtension that) 787 { 788 // Do the PKTs match? 789 byte[] aPKT = GetPublicKeyToken(); 790 byte[] bPKT = that.GetPublicKeyToken(); 791 return ComparePublicKeyTokens(aPKT, bPKT); 792 } 793 794 /// <summary> 795 /// Compare two public key tokens. 796 /// </summary> ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT)797 internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) 798 { 799 // Some assemblies (real case was interop assembly) may have null PKTs. 800 if (aPKT == null) 801 { 802 aPKT = new byte[0]; 803 } 804 if (bPKT == null) 805 { 806 bPKT = new byte[0]; 807 } 808 809 if (aPKT.Length != bPKT.Length) 810 { 811 return false; 812 } 813 for (int i = 0; i < aPKT.Length; ++i) 814 { 815 if (aPKT[i] != bPKT[i]) 816 { 817 return false; 818 } 819 } 820 return true; 821 } 822 823 /// <summary> 824 /// Only the unnamed assembly has both null assemblyname and null string. 825 /// </summary> 826 /// <returns></returns> 827 internal bool IsUnnamedAssembly => asAssemblyName == null && asString == null; 828 829 /// <summary> 830 /// Given a display name, construct an assembly name. 831 /// </summary> 832 /// <param name="displayName">The display name.</param> 833 /// <returns>The assembly name.</returns> GetAssemblyNameFromDisplayName(string displayName)834 private static AssemblyName GetAssemblyNameFromDisplayName(string displayName) 835 { 836 AssemblyName assemblyName = new AssemblyName(displayName); 837 return assemblyName; 838 } 839 840 /// <summary> 841 /// Return a string that has AssemblyName special characters escaped. 842 /// Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\). 843 /// </summary> 844 /// <remarks> 845 /// WARNING! This method is not meant as a general purpose escaping method for assembly names. 846 /// Use only if you really know that this does what you need. 847 /// </remarks> 848 /// <param name="displayName"></param> 849 /// <returns></returns> EscapeDisplayNameCharacters(string displayName)850 internal static string EscapeDisplayNameCharacters(string displayName) 851 { 852 StringBuilder sb = new StringBuilder(displayName); 853 sb = sb.Replace("\\", "\\\\"); 854 sb = sb.Replace("=", "\\="); 855 sb = sb.Replace(",", "\\,"); 856 sb = sb.Replace("\"", "\\\""); 857 sb = sb.Replace("'", "\\'"); 858 return sb.ToString(); 859 } 860 861 /// <summary> 862 /// Convert to a string for display. 863 /// </summary> 864 /// <returns></returns> ToString()865 public override string ToString() 866 { 867 CreateFullName(); 868 return asString; 869 } 870 871 /// <summary> 872 /// Compare the fields of this with that if they are not null. 873 /// </summary> PartialNameCompare(AssemblyNameExtension that)874 internal bool PartialNameCompare(AssemblyNameExtension that) 875 { 876 return PartialNameCompare(that, PartialComparisonFlags.Default, false /* do not consider retargetable flag*/); 877 } 878 879 /// <summary> 880 /// Compare the fields of this with that if they are not null. 881 /// </summary> PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag)882 internal bool PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag) 883 { 884 return PartialNameCompare(that, PartialComparisonFlags.Default, considerRetargetableFlag); 885 } 886 887 /// <summary> 888 /// Do a partial comparison between two assembly name extensions. 889 /// Compare the fields of A and B on the following conditions: 890 /// 1) A.Field has a non null value 891 /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. 892 /// 893 /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. 894 /// </summary> PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags)895 internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags) 896 { 897 return PartialNameCompare(that, comparisonFlags, false /* do not consider retargetable flag*/); 898 } 899 900 /// <summary> 901 /// Do a partial comparison between two assembly name extensions. 902 /// Compare the fields of A and B on the following conditions: 903 /// 1) A.Field has a non null value 904 /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in. 905 /// 906 /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false. 907 /// </summary> PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag)908 internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag) 909 { 910 // Pointer compare. 911 if (object.ReferenceEquals(this, that)) 912 { 913 return true; 914 } 915 916 // If that is null then this and that are not equal. Also, this would cause a crash on the next line. 917 if (object.ReferenceEquals(that, null)) 918 { 919 return false; 920 } 921 922 // Do both have assembly names? 923 if (asAssemblyName != null && that.asAssemblyName != null) 924 { 925 // Pointer compare. 926 if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName)) 927 { 928 return true; 929 } 930 } 931 932 // Do the names match? 933 if ((comparisonFlags & PartialComparisonFlags.SimpleName) != 0 && Name != null && !string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase)) 934 { 935 return false; 936 } 937 938 if ((comparisonFlags & PartialComparisonFlags.Version) != 0 && Version != null && this.Version != that.Version) 939 { 940 return false; 941 } 942 943 if ((comparisonFlags & PartialComparisonFlags.Culture) != 0 && CultureInfo != null && (that.CultureInfo == null || !CompareCultures(AssemblyName, that.AssemblyName))) 944 { 945 return false; 946 } 947 948 if ((comparisonFlags & PartialComparisonFlags.PublicKeyToken) != 0 && GetPublicKeyToken() != null && !ComparePublicKeyToken(that)) 949 { 950 return false; 951 } 952 953 if (considerRetargetableFlag && (Retargetable != that.Retargetable)) 954 { 955 return false; 956 } 957 return true; 958 } 959 GetObjectData(SerializationInfo info, StreamingContext context)960 public void GetObjectData(SerializationInfo info, StreamingContext context) 961 { 962 info.AddValue("hasAN", asAssemblyName != null); 963 if (asAssemblyName != null) 964 { 965 info.AddValue("name", asAssemblyName.Name); 966 info.AddValue("pk", asAssemblyName.GetPublicKey()); 967 info.AddValue("pkt", asAssemblyName.GetPublicKeyToken()); 968 info.AddValue("ver", asAssemblyName.Version); 969 info.AddValue("flags", (int) asAssemblyName.Flags); 970 info.AddValue("cpuarch", (int) asAssemblyName.ProcessorArchitecture); 971 972 info.AddValue("hasCI", asAssemblyName.CultureInfo != null); 973 if (asAssemblyName.CultureInfo != null) 974 { 975 info.AddValue("ci", asAssemblyName.CultureInfo.LCID); 976 } 977 978 info.AddValue("hashAlg", asAssemblyName.HashAlgorithm); 979 info.AddValue("verCompat", asAssemblyName.VersionCompatibility); 980 info.AddValue("codebase", asAssemblyName.CodeBase); 981 info.AddValue("keypair", asAssemblyName.KeyPair); 982 } 983 984 info.AddValue("asStr", asString); 985 info.AddValue("isSName", isSimpleName); 986 info.AddValue("hasCpuArch", hasProcessorArchitectureInFusionName); 987 info.AddValue("immutable", immutable); 988 info.AddValue("remapped", remappedFrom); 989 } 990 } 991 } 992