Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 9aa86c5

Browse files
Add string.GetHashCode(ROS<char>) and related APIs
1 parent 9ff41e7 commit 9aa86c5

File tree

5 files changed

+112
-31
lines changed

5 files changed

+112
-31
lines changed

src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Collation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal static partial class Globalization
4848
internal static extern unsafe bool EndsWith(SafeSortHandle sortHandle, string target, int cwTargetLength, string source, int cwSourceLength, CompareOptions options);
4949

5050
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetSortKey")]
51-
internal static extern unsafe int GetSortKey(SafeSortHandle sortHandle, string str, int strLength, byte* sortKey, int sortKeyLength, CompareOptions options);
51+
internal static extern unsafe int GetSortKey(SafeSortHandle sortHandle, char* str, int strLength, byte* sortKey, int sortKeyLength, CompareOptions options);
5252

5353
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_CompareStringOrdinalIgnoreCase")]
5454
internal static extern unsafe int CompareStringOrdinalIgnoreCase(char* lpStr1, int cwStr1Len, char* lpStr2, int cwStr2Len);

src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -798,14 +798,17 @@ private unsafe SortKey CreateSortKey(string source, CompareOptions options)
798798
}
799799
else
800800
{
801-
int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
802-
keyData = new byte[sortKeyLength];
803-
804-
fixed (byte* pSortKey = keyData)
801+
fixed (char* pSource = source)
805802
{
806-
if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
803+
int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options);
804+
keyData = new byte[sortKeyLength];
805+
806+
fixed (byte* pSortKey = keyData)
807807
{
808-
throw new ArgumentException(SR.Arg_ExternalException);
808+
if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
809+
{
810+
throw new ArgumentException(SR.Arg_ExternalException);
811+
}
809812
}
810813
}
811814
}
@@ -856,7 +859,7 @@ private static unsafe bool IsSortable(char *text, int length)
856859
// ---- PAL layer ends here ----
857860
// -----------------------------
858861

859-
internal unsafe int GetHashCodeOfStringCore(string source, CompareOptions options)
862+
internal unsafe int GetHashCodeOfStringCore(ReadOnlySpan<char> source, CompareOptions options)
860863
{
861864
Debug.Assert(!_invariantMode);
862865

@@ -868,30 +871,33 @@ internal unsafe int GetHashCodeOfStringCore(string source, CompareOptions option
868871
return 0;
869872
}
870873

871-
int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
874+
fixed (char* pSource = source)
875+
{
876+
int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options);
872877

873-
byte[] borrowedArr = null;
874-
Span<byte> span = sortKeyLength <= 512 ?
875-
stackalloc byte[512] :
876-
(borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
878+
byte[] borrowedArr = null;
879+
Span<byte> span = sortKeyLength <= 512 ?
880+
stackalloc byte[512] :
881+
(borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
877882

878-
fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
879-
{
880-
if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
883+
fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
881884
{
882-
throw new ArgumentException(SR.Arg_ExternalException);
885+
if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
886+
{
887+
throw new ArgumentException(SR.Arg_ExternalException);
888+
}
883889
}
884-
}
885890

886-
int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
891+
int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
887892

888-
// Return the borrowed array if necessary.
889-
if (borrowedArr != null)
890-
{
891-
ArrayPool<byte>.Shared.Return(borrowedArr);
892-
}
893+
// Return the borrowed array if necessary.
894+
if (borrowedArr != null)
895+
{
896+
ArrayPool<byte>.Shared.Return(borrowedArr);
897+
}
893898

894-
return hash;
899+
return hash;
900+
}
895901
}
896902

897903
private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)

src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Windows.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ internal static int LastIndexOfOrdinalCore(string source, string value, int star
112112
return FindStringOrdinal(FIND_FROMEND, source, startIndex - count + 1, count, value, value.Length, ignoreCase);
113113
}
114114

115-
private unsafe int GetHashCodeOfStringCore(string source, CompareOptions options)
115+
private unsafe int GetHashCodeOfStringCore(ReadOnlySpan<char> source, CompareOptions options)
116116
{
117117
Debug.Assert(!_invariantMode);
118118

@@ -138,6 +138,11 @@ private unsafe int GetHashCodeOfStringCore(string source, CompareOptions options
138138
throw new ArgumentException(SR.Arg_ExternalException);
139139
}
140140

141+
// Note in calls to LCMapStringEx below, the input buffer is specified in wchars (and wchar count),
142+
// but the output buffer is specified in bytes (and byte count). This is because when generating
143+
// sort keys, LCMapStringEx treats the output buffer as containing opaque binary data.
144+
// See https://docs.microsoft.com/en-us/windows/desktop/api/winnls/nf-winnls-lcmapstringex.
145+
141146
byte[] borrowedArr = null;
142147
Span<byte> span = sortKeyLength <= 512 ?
143148
stackalloc byte[512] :

src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,14 +1315,22 @@ internal int GetHashCodeOfString(string source, CompareOptions options)
13151315
throw new ArgumentNullException(nameof(source));
13161316
}
13171317

1318+
return GetHashCodeOfString(source.AsSpan(), options);
1319+
}
1320+
1321+
internal int GetHashCodeOfString(ReadOnlySpan<char> source, CompareOptions options)
1322+
{
1323+
//
1324+
// Parameter validation
1325+
//
13181326
if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
13191327
{
13201328
throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
13211329
}
13221330

13231331
if (_invariantMode)
13241332
{
1325-
return ((options & CompareOptions.IgnoreCase) != 0) ? source.GetHashCodeOrdinalIgnoreCase() : source.GetHashCode();
1333+
return ((options & CompareOptions.IgnoreCase) != 0) ? string.GetHashCodeOrdinalIgnoreCase(source) : string.GetHashCode(source);
13261334
}
13271335

13281336
return GetHashCodeOfStringCore(source, options);
@@ -1335,14 +1343,19 @@ public virtual int GetHashCode(string source, CompareOptions options)
13351343
throw new ArgumentNullException(nameof(source));
13361344
}
13371345

1346+
return GetHashCode(source.AsSpan(), options);
1347+
}
1348+
1349+
public int GetHashCode(ReadOnlySpan<char> source, CompareOptions options)
1350+
{
13381351
if (options == CompareOptions.Ordinal)
13391352
{
1340-
return source.GetHashCode();
1353+
return string.GetHashCode(source);
13411354
}
13421355

13431356
if (options == CompareOptions.OrdinalIgnoreCase)
13441357
{
1345-
return source.GetHashCodeOrdinalIgnoreCase();
1358+
return string.GetHashCodeOrdinalIgnoreCase(source);
13461359
}
13471360

13481361
//

src/System.Private.CoreLib/shared/System/String.Comparison.cs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ public static bool Equals(string a, string b, StringComparison comparisonType)
748748
public override int GetHashCode()
749749
{
750750
ulong seed = Marvin.DefaultSeed;
751-
return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, (uint)seed, (uint)(seed >> 32));
751+
return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2 /* in bytes, not chars */, (uint)seed, (uint)(seed >> 32));
752752
}
753753

754754
// Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
@@ -759,7 +759,64 @@ public override int GetHashCode()
759759
internal int GetHashCodeOrdinalIgnoreCase()
760760
{
761761
ulong seed = Marvin.DefaultSeed;
762-
return Marvin.ComputeHash32OrdinalIgnoreCase(ref _firstChar, _stringLength, (uint)seed, (uint)(seed >> 32));
762+
return Marvin.ComputeHash32OrdinalIgnoreCase(ref _firstChar, _stringLength /* in chars, not bytes */, (uint)seed, (uint)(seed >> 32));
763+
}
764+
765+
// A span-based equivalent of String.GetHashCode(). Computes an ordinal hash code.
766+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
767+
public static int GetHashCode(ReadOnlySpan<char> value)
768+
{
769+
ulong seed = Marvin.DefaultSeed;
770+
return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(value)), value.Length * 2 /* in bytes, not chars */, (uint)seed, (uint)(seed >> 32));
771+
}
772+
773+
// A span-based equivalent of String.GetHashCode(StringComparison). Uses the specified comparison type.
774+
public static int GetHashCode(ReadOnlySpan<char> value, StringComparison comparisonType)
775+
{
776+
CultureInfo culture;
777+
CompareOptions compareOptions;
778+
779+
switch (comparisonType)
780+
{
781+
case StringComparison.CurrentCulture:
782+
culture = CultureInfo.CurrentCulture;
783+
compareOptions = CompareOptions.None;
784+
break;
785+
786+
case StringComparison.CurrentCultureIgnoreCase:
787+
culture = CultureInfo.CurrentCulture;
788+
compareOptions = CompareOptions.IgnoreCase;
789+
break;
790+
791+
case StringComparison.InvariantCulture:
792+
culture = CultureInfo.InvariantCulture;
793+
compareOptions = CompareOptions.None;
794+
break;
795+
796+
case StringComparison.InvariantCultureIgnoreCase:
797+
culture = CultureInfo.InvariantCulture;
798+
compareOptions = CompareOptions.IgnoreCase;
799+
break;
800+
801+
case StringComparison.Ordinal:
802+
return GetHashCode(value);
803+
804+
case StringComparison.OrdinalIgnoreCase:
805+
return GetHashCodeOrdinalIgnoreCase(value);
806+
807+
default:
808+
ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);
809+
throw null; // shouldn't be hit
810+
}
811+
812+
return culture.CompareInfo.GetHashCodeOfString(value, compareOptions);
813+
}
814+
815+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
816+
internal static int GetHashCodeOrdinalIgnoreCase(ReadOnlySpan<char> value)
817+
{
818+
ulong seed = Marvin.DefaultSeed;
819+
return Marvin.ComputeHash32OrdinalIgnoreCase(ref MemoryMarshal.GetReference(value), value.Length /* in chars, not bytes */, (uint)seed, (uint)(seed >> 32));
763820
}
764821

765822
// Use this if and only if 'Denial of Service' attacks are not a concern (i.e. never used for free-form user input),

0 commit comments

Comments
 (0)