| File: MSBuildNameIgnoreCaseComparer.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using Microsoft.Build.Framework; #nullable disable namespace Microsoft.Build.Collections { /// <summary> /// This is a custom string comparer that has three advantages over the regular /// string comparer: /// 1) It can generate hash codes and perform equivalence operations on parts of a string rather than a whole /// 2) It uses "unsafe" pointers to maximize performance of those operations /// 3) It takes advantage of limitations on MSBuild Property/Item names to cheaply do case insensitive comparison. /// </summary> [Serializable] internal class MSBuildNameIgnoreCaseComparer : IConstrainedEqualityComparer<string>, IEqualityComparer<string> { /// <summary> /// The processor architecture on which we are running, but default it will be x86 /// </summary> private static readonly NativeMethods.ProcessorArchitectures s_runningProcessorArchitecture = NativeMethods.ProcessorArchitecture; /// <summary> /// The default immutable comparer instance. /// </summary> internal static MSBuildNameIgnoreCaseComparer Default { get; } = new MSBuildNameIgnoreCaseComparer(); public bool Equals(string x, string y) { return Equals(x, y, 0, y?.Length ?? 0); } public int GetHashCode(string obj) { return GetHashCode(obj, 0, obj?.Length ?? 0); } /// <summary> /// Performs the "Equals" operation on two MSBuild property, item or metadata names /// </summary> public bool Equals(string compareToString, string constrainedString, int start, int lengthToCompare) { Assumed.PositiveOrZero(lengthToCompare, $"Invalid {nameof(lengthToCompare)} '{constrainedString}' {start} {lengthToCompare}"); Assumed.PositiveOrZero(start, $"Invalid {nameof(start)} '{constrainedString}' {start} {lengthToCompare}"); Assumed.LessThanOrEqual(start, (constrainedString?.Length ?? 0) - lengthToCompare, $"Invalid {nameof(start)} '{constrainedString}' {start} {lengthToCompare}"); if (ReferenceEquals(compareToString, constrainedString)) { return true; } if (compareToString == null || constrainedString == null) { return false; } #if NET return compareToString.AsSpan().Equals(constrainedString.AsSpan(start, lengthToCompare), StringComparison.OrdinalIgnoreCase); #else if (lengthToCompare != compareToString.Length) { return false; } if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) { // The use of unsafe here is quite a bit faster than the regular // mechanism in the BCL. This is because we can make assumptions // about the characters that are within the strings being compared // i.e. they are valid MSBuild property, item and metadata names unsafe { fixed (char* px = compareToString) { fixed (char* py = constrainedString) { for (int i = 0; i < compareToString.Length; i++) { int chx = px[i]; int chy = py[i + start]; chx &= 0x00DF; // Extract the uppercase character chy &= 0x00DF; // Extract the uppercase character if (chx != chy) { return false; } } } } } } else { return String.Compare(compareToString, 0, constrainedString, start, lengthToCompare, StringComparison.OrdinalIgnoreCase) == 0; } return true; #endif } /// <summary> /// Getting a case insensitive hash code for the msbuild property, item or metadata name /// </summary> public int GetHashCode(string obj, int start, int length) { if (obj == null) { return 0; // per BCL convention } if ((s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.IA64) && (s_runningProcessorArchitecture != NativeMethods.ProcessorArchitectures.ARM)) { unsafe { // This algorithm is based on the 32bit version from the CLR's string::GetHashCode fixed (char* src = obj) { int hash1 = (5381 << 16) + 5381; int hash2 = hash1; char* src2 = src + start; var pint = (int*)src2; while (length > 0) { // We're only interested in uppercase ASCII characters int val = pint[0] & 0x00DF00DF; // When we reach the end of the string, we need to // stop short when gathering our data to compute the // hash code - we are only interested in the data within // the string, and not the null terminator etc. if (length == 1) { if (BitConverter.IsLittleEndian) { val &= 0xFFFF; } else { val &= unchecked((int)0xFFFF0000); } } hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ val; if (length <= 2) { break; } // Once again we're only interested in the uppercase ASCII characters val = pint[1] & 0x00DF00DF; if (length == 3) { if (BitConverter.IsLittleEndian) { val &= 0xFFFF; } else { val &= unchecked((int)0xFFFF0000); } } hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ val; pint += 2; length -= 4; } return hash1 + (hash2 * 1566083941); } } } else { return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Substring(start, length)); } } } }