diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs index 192e555bba800..13c56f1687e9e 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Globalization.cs @@ -9,6 +9,15 @@ internal static partial class Interop { internal static unsafe partial class Kernel32 { + // Under debug mode only, we'll want to check the error codes + // of some of the p/invokes we make. + +#if DEBUG + private const bool SetLastErrorForDebug = true; +#else + private const bool SetLastErrorForDebug = false; +#endif + internal const uint LOCALE_ALLOW_NEUTRAL_NAMES = 0x08000000; // Flag to allow returning neutral names/lcids for name conversion internal const uint LOCALE_ILANGUAGE = 0x00000001; internal const uint LOCALE_SUPPLEMENTAL = 0x00000002; @@ -52,7 +61,7 @@ internal static extern int LCMapStringEx( void* lpReserved, IntPtr sortHandle); - [DllImport("kernel32.dll", EntryPoint = "FindNLSStringEx")] + [DllImport("kernel32.dll", EntryPoint = "FindNLSStringEx", SetLastError = SetLastErrorForDebug)] internal static extern int FindNLSStringEx( char* lpLocaleName, uint dwFindNLSStringFlags, @@ -85,14 +94,14 @@ internal static extern int CompareStringOrdinal( int cchCount2, bool bIgnoreCase); - [DllImport("kernel32.dll", EntryPoint = "FindStringOrdinal")] + [DllImport("kernel32.dll", EntryPoint = "FindStringOrdinal", SetLastError = SetLastErrorForDebug)] internal static extern int FindStringOrdinal( uint dwFindStringOrdinalFlags, char* lpStringSource, int cchSource, char* lpStringValue, int cchValue, - int bIgnoreCase); + BOOL bIgnoreCase); [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] internal static extern bool IsNLSDefinedString( diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Invariant.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Invariant.cs index 4b65d5fcbe905..663530bcdc793 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Invariant.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Invariant.cs @@ -243,7 +243,7 @@ private SortKey InvariantCreateSortKey(string source, CompareOptions options) } } - return new SortKey(Name, source, options, keyData); + return new SortKey(this, source, options, keyData); } private static void InvariantCreateSortKeyOrdinal(ReadOnlySpan source, Span sortKey) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs index c30a491a39a25..2612dcc69e48b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Unix.cs @@ -43,64 +43,22 @@ private void InitSort(CultureInfo culture) } } - internal static unsafe int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase) - { - Debug.Assert(!GlobalizationMode.Invariant); - - Debug.Assert(source != null); - Debug.Assert(value != null); - - if (value.Length == 0) - { - return startIndex; - } - - if (count < value.Length) - { - return -1; - } - - if (ignoreCase) - { - fixed (char* pSource = source) - { - int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + startIndex, count, findLast: false); - return index != -1 ? - startIndex + index : - -1; - } - } - - int endIndex = startIndex + (count - value.Length); - for (int i = startIndex; i <= endIndex; i++) - { - int valueIndex, sourceIndex; - - for (valueIndex = 0, sourceIndex = i; - valueIndex < value.Length && source[sourceIndex] == value[valueIndex]; - valueIndex++, sourceIndex++) ; - - if (valueIndex == value.Length) - { - return i; - } - } - - return -1; - } - internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase, bool fromBeginning) { Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(!value.IsEmpty); - Debug.Assert(source.Length != 0); - Debug.Assert(value.Length != 0); + // Ordinal (non-linguistic) comparisons require the length of the target string to be no greater + // than the length of the search space. Since our caller already checked for empty target strings, + // the below check also handles the case of empty search space strings. if (source.Length < value.Length) { return -1; } + Debug.Assert(!source.IsEmpty); + if (ignoreCase) { fixed (char* pSource = &MemoryMarshal.GetReference(source)) @@ -199,9 +157,14 @@ private static unsafe int CompareStringOrdinalIgnoreCase(ref char string1, int c { Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(count1 > 0); + Debug.Assert(count2 > 0); + fixed (char* char1 = &string1) fixed (char* char2 = &string2) { + Debug.Assert(char1 != null); + Debug.Assert(char2 != null); return Interop.Globalization.CompareStringOrdinalIgnoreCase(char1, count1, char2, count2); } } @@ -215,6 +178,9 @@ private unsafe int CompareString(ReadOnlySpan string1, string string2, Com Debug.Assert(string2 != null); Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + // Unlike NLS, ICU (ucol_getSortKey) allows passing nullptr for either of the source arguments + // as long as the corresponding length parameter is 0. + fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) fixed (char* pString2 = &string2.GetRawStringData()) { @@ -227,6 +193,9 @@ private unsafe int CompareString(ReadOnlySpan string1, ReadOnlySpan Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + // Unlike NLS, ICU (ucol_getSortKey) allows passing nullptr for either of the source arguments + // as long as the corresponding length parameter is 0. + fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) fixed (char* pString2 = &MemoryMarshal.GetReference(string2)) { @@ -540,7 +509,6 @@ private unsafe bool StartsWith(ReadOnlySpan source, ReadOnlySpan pre { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!prefix.IsEmpty); Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); @@ -553,7 +521,7 @@ private unsafe bool StartsWith(ReadOnlySpan source, ReadOnlySpan pre } else { - fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix)) { return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length, options); @@ -565,13 +533,12 @@ private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan source, { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!prefix.IsEmpty); Debug.Assert(_isAsciiEqualityOrdinal); int length = Math.Min(source.Length, prefix.Length); - fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* bp = &MemoryMarshal.GetReference(prefix)) { char* a = ap; @@ -636,13 +603,12 @@ private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan source, ReadOnlyS { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!prefix.IsEmpty); Debug.Assert(_isAsciiEqualityOrdinal); int length = Math.Min(source.Length, prefix.Length); - fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* bp = &MemoryMarshal.GetReference(prefix)) { char* a = ap; @@ -696,7 +662,6 @@ private unsafe bool EndsWith(ReadOnlySpan source, ReadOnlySpan suffi { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!suffix.IsEmpty); Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); @@ -709,7 +674,7 @@ private unsafe bool EndsWith(ReadOnlySpan source, ReadOnlySpan suffi } else { - fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix)) { return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, options); @@ -721,13 +686,12 @@ private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan source, R { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!suffix.IsEmpty); Debug.Assert(_isAsciiEqualityOrdinal); int length = Math.Min(source.Length, suffix.Length); - fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* bp = &MemoryMarshal.GetReference(suffix)) { char* a = ap + source.Length - 1; @@ -773,13 +737,12 @@ private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan source, ReadOnlySpa { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!suffix.IsEmpty); Debug.Assert(_isAsciiEqualityOrdinal); int length = Math.Min(source.Length, suffix.Length); - fixed (char* ap = &MemoryMarshal.GetReference(source)) + fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferenced) fixed (char* bp = &MemoryMarshal.GetReference(suffix)) { char* a = ap + source.Length - 1; @@ -836,7 +799,7 @@ private unsafe SortKey CreateSortKey(string source, CompareOptions options) } } - return new SortKey(Name, source, options, keyData); + return new SortKey(this, source, options, keyData); } private static unsafe bool IsSortable(char *text, int length) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs index 98608107b0474..015dc4c39b5c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Windows.cs @@ -42,33 +42,6 @@ private void InitSort(CultureInfo culture) _sortHandle = GetSortHandle(_sortName); } - private static unsafe int FindStringOrdinal( - uint dwFindStringOrdinalFlags, - string stringSource, - int offset, - int cchSource, - string value, - int cchValue, - bool bIgnoreCase) - { - Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(stringSource != null); - Debug.Assert(value != null); - - fixed (char* pSource = stringSource) - fixed (char* pValue = value) - { - int ret = Interop.Kernel32.FindStringOrdinal( - dwFindStringOrdinalFlags, - pSource + offset, - cchSource, - pValue, - cchValue, - bIgnoreCase ? 1 : 0); - return ret < 0 ? ret : ret + offset; - } - } - private static unsafe int FindStringOrdinal( uint dwFindStringOrdinalFlags, ReadOnlySpan source, @@ -82,25 +55,24 @@ private static unsafe int FindStringOrdinal( fixed (char* pSource = &MemoryMarshal.GetReference(source)) fixed (char* pValue = &MemoryMarshal.GetReference(value)) { + Debug.Assert(pSource != null); + Debug.Assert(pValue != null); + int ret = Interop.Kernel32.FindStringOrdinal( dwFindStringOrdinalFlags, pSource, source.Length, pValue, value.Length, - bIgnoreCase ? 1 : 0); - return ret; - } - } + bIgnoreCase ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); - internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase) - { - Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(ret >= -1 && ret <= source.Length); - Debug.Assert(source != null); - Debug.Assert(value != null); + // SetLastError is only performed under debug builds. + Debug.Assert(ret >= 0 || Marshal.GetLastWin32Error() == Interop.Errors.ERROR_SUCCESS); - return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase); + return ret; + } } internal static int IndexOfOrdinalCore(ReadOnlySpan source, ReadOnlySpan value, bool ignoreCase, bool fromBeginning) @@ -121,7 +93,13 @@ internal static int LastIndexOfOrdinalCore(string source, string value, int star Debug.Assert(source != null); Debug.Assert(value != null); - return FindStringOrdinal(FIND_FROMEND, source, startIndex - count + 1, count, value, value.Length, ignoreCase); + int offset = startIndex - count + 1; + int result = FindStringOrdinal(FIND_FROMEND, source.AsSpan(offset, count), value, ignoreCase); + if (result >= 0) + { + result += offset; + } + return result; } private unsafe int GetHashCodeOfStringCore(ReadOnlySpan source, CompareOptions options) @@ -192,11 +170,22 @@ private static unsafe int CompareStringOrdinalIgnoreCase(ref char string1, int c { Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(count1 > 0); + Debug.Assert(count2 > 0); + fixed (char* char1 = &string1) fixed (char* char2 = &string2) { + Debug.Assert(char1 != null); + Debug.Assert(char2 != null); + // Use the OS to compare and then convert the result to expected value by subtracting 2 - return Interop.Kernel32.CompareStringOrdinal(char1, count1, char2, count2, true) - 2; + int result = Interop.Kernel32.CompareStringOrdinal(char1, count1, char2, count2, bIgnoreCase: true); + if (result == 0) + { + throw new ArgumentException(SR.Arg_ExternalException); + } + return result - 2; } } @@ -211,11 +200,22 @@ private unsafe int CompareString(ReadOnlySpan string1, string string2, Com string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName; + // CompareStringEx may try to dereference the first character of its input, even if an explicit + // length of 0 is specified. To work around potential AVs we'll always ensure zero-length inputs + // are normalized to a null-terminated empty string. + + if (string1.IsEmpty) + { + string1 = string.Empty; + } + fixed (char* pLocaleName = localeName) fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) - fixed (char* pString2 = &string2.GetRawStringData()) + fixed (char* pString2 = &string2.GetPinnableReference()) { - Debug.Assert(pString1 != null); + Debug.Assert(*pString1 >= 0); // assert that we can always dereference this + Debug.Assert(*pString2 >= 0); // assert that we can always dereference this + int result = Interop.Kernel32.CompareStringEx( pLocaleName, (uint)GetNativeCompareFlags(options), @@ -244,12 +244,27 @@ private unsafe int CompareString(ReadOnlySpan string1, ReadOnlySpan string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName; + // CompareStringEx may try to dereference the first character of its input, even if an explicit + // length of 0 is specified. To work around potential AVs we'll always ensure zero-length inputs + // are normalized to a null-terminated empty string. + + if (string1.IsEmpty) + { + string1 = string.Empty; + } + + if (string2.IsEmpty) + { + string2 = string.Empty; + } + fixed (char* pLocaleName = localeName) fixed (char* pString1 = &MemoryMarshal.GetReference(string1)) fixed (char* pString2 = &MemoryMarshal.GetReference(string2)) { - Debug.Assert(pString1 != null); - Debug.Assert(pString2 != null); + Debug.Assert(*pString1 >= 0); // assert that we can always dereference this + Debug.Assert(*pString2 >= 0); // assert that we can always dereference this + int result = Interop.Kernel32.CompareStringEx( pLocaleName, (uint)GetNativeCompareFlags(options), @@ -278,63 +293,47 @@ private unsafe int FindString( int* pcchFound) { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!lpStringSource.IsEmpty); Debug.Assert(!lpStringValue.IsEmpty); string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName; + // FindNLSStringEx disallows passing an explicit 0 for cchSource or cchValue. + // The caller should've already checked that 'lpStringValue' isn't empty, + // but it's possible for 'lpStringSource' to be empty. In this case we'll + // substitute an empty null-terminated string and pass -1 so that the NLS + // function uses the implicit string length. + + int lpStringSourceLength = lpStringSource.Length; + if (lpStringSourceLength == 0) + { + lpStringSource = string.Empty; + lpStringSourceLength = -1; + } + fixed (char* pLocaleName = localeName) fixed (char* pSource = &MemoryMarshal.GetReference(lpStringSource)) fixed (char* pValue = &MemoryMarshal.GetReference(lpStringValue)) { - return Interop.Kernel32.FindNLSStringEx( + Debug.Assert(pSource != null && pValue != null); + + int result = Interop.Kernel32.FindNLSStringEx( pLocaleName, dwFindNLSStringFlags, pSource, - lpStringSource.Length, + lpStringSourceLength, pValue, lpStringValue.Length, pcchFound, null, null, _sortHandle); - } - } - private unsafe int FindString( - uint dwFindNLSStringFlags, - string lpStringSource, - int startSource, - int cchSource, - string lpStringValue, - int startValue, - int cchValue, - int* pcchFound) - { - Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(lpStringSource != null); - Debug.Assert(lpStringValue != null); - - string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName; + Debug.Assert(result >= -1 && result <= lpStringSource.Length); - fixed (char* pLocaleName = localeName) - fixed (char* pSource = lpStringSource) - fixed (char* pValue = lpStringValue) - { - char* pS = pSource + startSource; - char* pV = pValue + startValue; + // SetLastError is only performed under debug builds. + Debug.Assert(result >= 0 || Marshal.GetLastWin32Error() == Interop.Errors.ERROR_SUCCESS); - return Interop.Kernel32.FindNLSStringEx( - pLocaleName, - dwFindNLSStringFlags, - pS, - cchSource, - pV, - cchValue, - pcchFound, - null, - null, - _sortHandle); + return result; } } @@ -342,13 +341,11 @@ internal unsafe int IndexOfCore(string source, string target, int startIndex, in { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(source != null); Debug.Assert(target != null); Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); Debug.Assert((options & CompareOptions.Ordinal) == 0); - int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, startIndex, count, - target, 0, target.Length, matchLengthPtr); + int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source.AsSpan(startIndex, count), target, matchLengthPtr); if (retValue >= 0) { return retValue + startIndex; @@ -361,7 +358,6 @@ internal unsafe int IndexOfCore(ReadOnlySpan source, ReadOnlySpan ta { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(source.Length != 0); Debug.Assert(target.Length != 0); Debug.Assert(options == CompareOptions.None || options == CompareOptions.IgnoreCase); @@ -373,7 +369,6 @@ private unsafe int LastIndexOfCore(string source, string target, int startIndex, { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!string.IsNullOrEmpty(source)); Debug.Assert(target != null); Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); @@ -386,8 +381,7 @@ private unsafe int LastIndexOfCore(string source, string target, int startIndex, } else { - int retValue = FindString(FIND_FROMEND | (uint)GetNativeCompareFlags(options), source, startIndex - count + 1, - count, target, 0, target.Length, null); + int retValue = FindString(FIND_FROMEND | (uint)GetNativeCompareFlags(options), source.AsSpan(startIndex - count + 1, count), target, null); if (retValue >= 0) { @@ -398,18 +392,6 @@ private unsafe int LastIndexOfCore(string source, string target, int startIndex, return -1; } - private unsafe bool StartsWith(string source, string prefix, CompareOptions options) - { - Debug.Assert(!GlobalizationMode.Invariant); - - Debug.Assert(!string.IsNullOrEmpty(source)); - Debug.Assert(!string.IsNullOrEmpty(prefix)); - Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); - - return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length, - prefix, 0, prefix.Length, null) >= 0; - } - private unsafe bool StartsWith(ReadOnlySpan source, ReadOnlySpan prefix, CompareOptions options) { Debug.Assert(!GlobalizationMode.Invariant); @@ -421,23 +403,10 @@ private unsafe bool StartsWith(ReadOnlySpan source, ReadOnlySpan pre return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, prefix, null) >= 0; } - private unsafe bool EndsWith(string source, string suffix, CompareOptions options) - { - Debug.Assert(!GlobalizationMode.Invariant); - - Debug.Assert(!string.IsNullOrEmpty(source)); - Debug.Assert(!string.IsNullOrEmpty(suffix)); - Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); - - return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, 0, source.Length, - suffix, 0, suffix.Length, null) >= 0; - } - private unsafe bool EndsWith(ReadOnlySpan source, ReadOnlySpan suffix, CompareOptions options) { Debug.Assert(!GlobalizationMode.Invariant); - Debug.Assert(!source.IsEmpty); Debug.Assert(!suffix.IsEmpty); Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); @@ -549,7 +518,7 @@ private unsafe SortKey CreateSortKey(string source, CompareOptions options) } } - return new SortKey(Name, source, options, keyData); + return new SortKey(this, source, options, keyData); } private static unsafe bool IsSortable(char* text, int length) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs index 3971e9caa7896..7753e86092ed2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs @@ -1112,23 +1112,50 @@ ref value.GetRawStringData(), internal static int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { + Debug.Assert(source != null); + Debug.Assert(value != null); + Debug.Assert((uint)startIndex <= (uint)source.Length); + Debug.Assert((uint)count <= (uint)(source.Length - startIndex)); + + // For ordinal (non-linguistic) comparisons, an empty target string is always + // found at the beginning of the search space, and a non-empty target string + // can never be found within an empty search space. This assumption is not + // valid for linguistic comparisons, including InvariantCulture comparisons. + + if (value.Length == 0) + { + return startIndex; + } + + if (count == 0) + { + return -1; + } + + int result; + if (!ignoreCase) { - int result = SpanHelpers.IndexOf( + result = SpanHelpers.IndexOf( ref Unsafe.Add(ref source.GetRawStringData(), startIndex), count, ref value.GetRawStringData(), value.Length); - - return (result >= 0 ? startIndex : 0) + result; } - - if (GlobalizationMode.Invariant) + else if (GlobalizationMode.Invariant) { - return InvariantIndexOf(source, value, startIndex, count, ignoreCase); + result = InvariantIndexOf(source.AsSpan(startIndex, count), value, ignoreCase, fromBeginning: true); + } + else + { + result = IndexOfOrdinalCore(source.AsSpan(startIndex, count), value, ignoreCase, fromBeginning: true); } - return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase); + if (result >= 0) + { + result += startIndex; + } + return result; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/SortKey.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/SortKey.cs index a3690c1f3593b..f845908c76df6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/SortKey.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/SortKey.cs @@ -11,7 +11,7 @@ namespace System.Globalization /// public sealed partial class SortKey { - private readonly string _localeName; + private readonly CompareInfo _compareInfo; private readonly CompareOptions _options; private readonly string _string; private readonly byte[] _keyData; @@ -20,10 +20,10 @@ public sealed partial class SortKey /// The following constructor is designed to be called from CompareInfo to get the /// the sort key of specific string for synthetic culture /// - internal SortKey(string localeName, string str, CompareOptions options, byte[] keyData) + internal SortKey(CompareInfo compareInfo, string str, CompareOptions options, byte[] keyData) { _keyData = keyData; - _localeName = localeName; + _compareInfo = compareInfo; _options = options; _string = str; } @@ -75,13 +75,12 @@ public override bool Equals(object? value) public override int GetHashCode() { - // keep this in sync with CompareInfo.GetHashCodeOfString - return Marvin.ComputeHash32(_keyData, Marvin.DefaultSeed); + return _compareInfo.GetHashCode(_string, _options); } public override string ToString() { - return "SortKey - " + _localeName + ", " + _options + ", " + _string; + return "SortKey - " + _compareInfo.Name + ", " + _options + ", " + _string; } } }