< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 1236
Coverable lines: 1236
Total lines: 3659
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 770
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%110%
File 1: .ctor(...)100%110%
File 1: GetCompareInfo(...)0%220%
File 1: GetCompareInfo(...)0%220%
File 1: GetCompareInfo(...)0%220%
File 1: GetCompareInfo(...)100%110%
File 1: IsSortable(...)100%110%
File 1: IsSortable(...)100%110%
File 1: IsSortable(...)0%660%
File 1: IsSortable(...)100%110%
File 1: InitSort(...)0%220%
File 1: OnDeserializing(...)100%110%
File 1: System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(...)100%110%
File 1: OnDeserialized(...)100%110%
File 1: OnDeserialized()0%220%
File 1: OnSerializing(...)100%110%
File 1: Compare(...)100%110%
File 1: Compare(...)0%660%
File 1: CompareOptionIgnoreCase(...)0%220%
File 1: Compare(...)100%110%
File 1: Compare(...)0%440%
File 1: Compare(...)100%110%
File 1: Compare(...)0%28280%
File 1: Compare(...)0%12120%
File 1: CheckCompareOptionsForCompare(...)0%660%
File 1: ThrowCompareOptionsCheckFailed(...)0%220%
File 1: CompareStringCore(...)0%220%
File 1: IsPrefix(...)0%440%
File 1: IsPrefix(...)0%12120%
File 1: IsPrefix(...)0%880%
File 1: StartsWithCore(...)0%220%
File 1: IsPrefix(...)100%110%
File 1: IsSuffix(...)0%440%
File 1: IsSuffix(...)0%12120%
File 1: IsSuffix(...)0%880%
File 1: IsSuffix(...)100%110%
File 1: EndsWithCore(...)0%220%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)0%220%
File 1: IndexOf(...)0%440%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)0%220%
File 1: IndexOf(...)0%220%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)0%880%
File 1: IndexOf(...)0%10100%
File 1: IndexOf(...)0%12120%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)100%110%
File 1: IndexOf(...)0%24240%
File 1: IndexOfCore(...)0%220%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)0%220%
File 1: LastIndexOf(...)0%440%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)0%16160%
File 1: LastIndexOf(...)0%18180%
File 1: LastIndexOf(...)0%12120%
File 1: LastIndexOf(...)100%110%
File 1: LastIndexOf(...)100%110%
File 1: GetSortKey(...)0%220%
File 1: GetSortKey(...)0%220%
File 1: CreateSortKeyCore(...)0%220%
File 1: GetSortKey(...)0%440%
File 1: GetSortKeyCore(...)0%220%
File 1: GetSortKeyLength(...)0%440%
File 1: GetSortKeyLengthCore(...)0%220%
File 1: Equals(...)0%220%
File 1: GetHashCode()100%110%
File 1: GetHashCode(...)0%220%
File 1: GetHashCode(...)0%880%
File 1: GetHashCodeOfStringCore(...)0%220%
File 1: ToString()100%110%
File 2: .cctor()100%110%
File 2: CreateNonSpecialAsciiChars()0%440%
File 2: IcuInitSortHandle(...)0%220%
File 2: GetIsAsciiEqualityOrdinal(...)0%12120%
File 2: IcuCompareString(...)100%110%
File 2: IcuIndexOfCore(...)0%880%
File 2: IndexOfOrdinalIgnoreCaseHelper(...)0%42420%
File 2: IndexOfOrdinalHelper(...)0%32320%
File 2: IcuStartsWith(...)0%660%
File 2: StartsWithOrdinalIgnoreCaseHelper(...)0%42420%
File 2: StartsWithOrdinalHelper(...)0%36360%
File 2: IcuEndsWith(...)0%660%
File 2: EndsWithOrdinalIgnoreCaseHelper(...)0%42420%
File 2: EndsWithOrdinalHelper(...)0%36360%
File 2: IcuCreateSortKey(...)0%10100%
File 2: IcuGetSortKey(...)0%440%
File 2: IcuGetSortKeyLength(...)100%110%
File 2: IcuIsSortable(...)0%880%
File 2: IcuGetHashCodeOfString(...)0%14140%
File 2: CanUseAsciiOrdinalForOptions(...)100%110%
File 2: IcuGetSortVersion()100%110%
File 2: .cctor()100%110%
File 2: GetCachedSortHandle(...)0%660%
File 3: InvariantCreateSortKey(...)0%660%
File 3: InvariantCreateSortKeyOrdinal(...)0%220%
File 3: InvariantCreateSortKeyOrdinalIgnoreCase(...)0%880%
File 3: InvariantGetSortKey(...)0%440%
File 3: InvariantGetSortKeyLength(...)0%220%
File 3: InvariantGetHashCode(...)0%220%
File 4: NlsInitSortHandle()100%110%
File 4: NlsGetSortHandle(...)0%660%
File 4: FindStringOrdinal(...)0%660%
File 4: NlsIndexOfOrdinalCore(...)0%220%
File 4: NlsGetHashCodeOfString(...)0%14140%
File 4: NlsCompareStringOrdinalIgnoreCase(...)0%220%
File 4: NlsCompareString(...)0%10100%
File 4: FindString(...)0%12120%
File 4: NlsIndexOfCore(...)0%220%
File 4: NlsStartsWith(...)0%440%
File 4: NlsEndsWith(...)0%440%
File 4: NlsCreateSortKey(...)0%18180%
File 4: NlsGetSortKey(...)0%10100%
File 4: NlsGetSortKeyLength(...)0%660%
File 4: NlsIsSortable(...)100%110%
File 4: GetNativeCompareFlags(...)0%18180%
File 4: NlsGetSortVersion()0%220%
File 5: IsPrefixUtf8(...)0%12120%
File 5: StartsWithCoreUtf8(...)0%18180%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Reflection;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9using System.Runtime.Serialization;
 10using System.Text;
 11
 12namespace System.Globalization
 13{
 14    /// <summary>
 15    /// This class implements a set of methods for comparing strings.
 16    /// </summary>
 17    [Serializable]
 18    [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
 19    public sealed partial class CompareInfo : IDeserializationCallback
 20    {
 21        // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
 22        private const CompareOptions ValidIndexMaskOffFlags =
 23            ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
 24              CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
 25
 26        // Mask used to check if Compare() / GetHashCode(string) / GetSortKey has the right flags.
 27        private const CompareOptions ValidCompareMaskOffFlags =
 28            ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
 29              CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort |
 30              CompareOptions.NumericOrdering);
 31
 32        // Cache the invariant CompareInfo
 033        internal static readonly CompareInfo Invariant = CultureInfo.InvariantCulture.CompareInfo;
 34
 35        // CompareInfos have an interesting identity.  They are attached to the locale that created them,
 36        // ie: en-US would have an en-US sort.  For haw-US (custom), then we serialize it as haw-US.
 37        // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
 38        // locale, which is what SCOMPAREINFO does.
 39        [OptionalField(VersionAdded = 2)]
 40        private string m_name;  // The name used to construct this CompareInfo. Do not rename (binary serialization)
 41
 42        [NonSerialized]
 43        private IntPtr _sortHandle;
 44
 45        [NonSerialized]
 46        private string _sortName; // The name that defines our behavior
 47
 48        [OptionalField(VersionAdded = 3)]
 49        private SortVersion? m_SortVersion; // Do not rename (binary serialization)
 50
 51        private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop seri
 52
 053        internal CompareInfo(CultureInfo culture)
 54        {
 055            m_name = culture._name;
 056            InitSort(culture);
 057        }
 58
 59        /// <summary>
 60        /// Get the CompareInfo constructed from the data table in the specified
 61        /// assembly for the specified culture.
 62        /// Warning: The assembly versioning mechanism is dead!
 63        /// </summary>
 64        public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
 65        {
 066            ArgumentNullException.ThrowIfNull(assembly);
 67
 68            // Parameter checking.
 069            if (assembly != typeof(object).Module.Assembly)
 70            {
 071                throw new ArgumentException(SR.Argument_OnlyMscorlib, nameof(assembly));
 72            }
 73
 074            return GetCompareInfo(culture);
 75        }
 76
 77        /// <summary>
 78        /// Get the CompareInfo constructed from the data table in the specified
 79        /// assembly for the specified culture.
 80        /// The purpose of this method is to provide version for CompareInfo tables.
 81        /// </summary>
 82        public static CompareInfo GetCompareInfo(string name, Assembly assembly)
 83        {
 084            ArgumentNullException.ThrowIfNull(name);
 085            ArgumentNullException.ThrowIfNull(assembly);
 86
 087            if (assembly != typeof(object).Module.Assembly)
 88            {
 089                throw new ArgumentException(SR.Argument_OnlyMscorlib, nameof(assembly));
 90            }
 91
 092            return GetCompareInfo(name);
 93        }
 94
 95        /// <summary>
 96        /// Get the CompareInfo for the specified culture.
 97        /// This method is provided for ease of integration with NLS-based software.
 98        /// </summary>
 99        public static CompareInfo GetCompareInfo(int culture)
 100        {
 0101            if (CultureData.IsCustomCultureId(culture))
 102            {
 0103                throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
 104            }
 105
 0106            return CultureInfo.GetCultureInfo(culture).CompareInfo;
 107        }
 108
 109        /// <summary>
 110        /// Get the CompareInfo for the specified culture.
 111        /// </summary>
 112        public static CompareInfo GetCompareInfo(string name)
 113        {
 0114            ArgumentNullException.ThrowIfNull(name);
 115
 0116            return CultureInfo.GetCultureInfo(name).CompareInfo;
 117        }
 118
 119        public static bool IsSortable(char ch)
 120        {
 0121            return IsSortable(new ReadOnlySpan<char>(in ch));
 122        }
 123
 124        public static bool IsSortable(string text)
 125        {
 0126            ArgumentNullException.ThrowIfNull(text);
 127
 0128            return IsSortable(text.AsSpan());
 129        }
 130
 131        /// <summary>
 132        /// Indicates whether a specified Unicode string is sortable.
 133        /// </summary>
 134        /// <param name="text">A string of zero or more Unicode characters.</param>
 135        /// <returns>
 136        /// <see langword="true"/> if <paramref name="text"/> is non-empty and contains
 137        /// only sortable Unicode characters; otherwise, <see langword="false"/>.
 138        /// </returns>
 139        public static bool IsSortable(ReadOnlySpan<char> text)
 140        {
 0141            if (text.Length == 0)
 142            {
 0143                return false;
 144            }
 145
 0146            if (GlobalizationMode.Invariant)
 147            {
 0148                return true; // all chars are sortable in invariant mode
 149            }
 150
 0151            return (GlobalizationMode.UseNls) ? NlsIsSortable(text) : IcuIsSortable(text);
 152        }
 153
 154        /// <summary>
 155        /// Indicates whether a specified <see cref="Rune"/> is sortable.
 156        /// </summary>
 157        /// <param name="value">A Unicode scalar value.</param>
 158        /// <returns>
 159        /// <see langword="true"/> if <paramref name="value"/> is a sortable Unicode scalar
 160        /// value; otherwise, <see langword="false"/>.
 161        /// </returns>
 162        public static unsafe bool IsSortable(Rune value)
 163        {
 0164            Span<char> valueAsUtf16 = stackalloc char[Rune.MaxUtf16CharsPerRune];
 0165            int charCount = value.EncodeToUtf16(valueAsUtf16);
 0166            return IsSortable(valueAsUtf16.Slice(0, charCount));
 167        }
 168
 169        [MemberNotNull(nameof(_sortName))]
 170        private void InitSort(CultureInfo culture)
 171        {
 0172            _sortName = culture.SortName;
 173
 0174            if (GlobalizationMode.UseNls)
 175            {
 0176                NlsInitSortHandle();
 177            }
 178            else
 179            {
 0180                IcuInitSortHandle(culture.InteropName!);
 181            }
 0182        }
 183
 184        [OnDeserializing]
 185        private void OnDeserializing(StreamingContext ctx)
 186        {
 187            // this becomes null for a brief moment before deserialization
 188            // after serialization is finished it is never null.
 0189            m_name = null!;
 0190        }
 191
 192        void IDeserializationCallback.OnDeserialization(object? sender)
 193        {
 0194            OnDeserialized();
 0195        }
 196
 197        [OnDeserialized]
 198        private void OnDeserialized(StreamingContext ctx)
 199        {
 0200            OnDeserialized();
 0201        }
 202
 203        private void OnDeserialized()
 204        {
 205            // If we didn't have a name, use the LCID
 0206            if (m_name == null)
 207            {
 208                // From whidbey, didn't have a name
 0209                m_name = CultureInfo.GetCultureInfo(culture)._name;
 210            }
 211            else
 212            {
 0213                InitSort(CultureInfo.GetCultureInfo(m_name));
 214            }
 0215        }
 216
 217        [OnSerializing]
 218        private void OnSerializing(StreamingContext ctx)
 219        {
 220            // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that
 0221            culture = CultureInfo.GetCultureInfo(Name).LCID; // This is the lcid of the constructing culture (still have
 0222            Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
 0223        }
 224
 225        /// <summary>
 226        ///  Returns the name of the culture (well actually, of the sort).
 227        ///  Very important for providing a non-LCID way of identifying
 228        ///  what the sort is.
 229        ///
 230        ///  Note that this name isn't dereferenced in case the CompareInfo is a different locale
 231        ///  which is consistent with the behaviors of earlier versions.  (so if you ask for a sort
 232        ///  and the locale's changed behavior, then you'll get changed behavior, which is like
 233        ///  what happens for a version update)
 234        /// </summary>
 235        public string Name
 236        {
 237            get
 238            {
 0239                Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
 0240                if (m_name == "zh-CHT" || m_name == "zh-CHS")
 241                {
 0242                    return m_name;
 243                }
 244
 0245                return _sortName;
 246            }
 247        }
 248
 249        /// <summary>
 250        /// Compares the two strings with the given options.  Returns 0 if the
 251        /// two strings are equal, a number less than 0 if string1 is less
 252        /// than string2, and a number greater than 0 if string1 is greater
 253        /// than string2.
 254        /// </summary>
 255        public int Compare(string? string1, string? string2)
 256        {
 0257            return Compare(string1, string2, CompareOptions.None);
 258        }
 259
 260        public int Compare(string? string1, string? string2, CompareOptions options)
 261        {
 262            int retVal;
 263
 264            // Our paradigm is that null sorts less than any other string and
 265            // that two nulls sort as equal.
 266
 0267            if (string1 == null)
 268            {
 0269                retVal = (string2 == null) ? 0 : -1;
 0270                goto CheckOptionsAndReturn;
 271            }
 0272            if (string2 == null)
 273            {
 0274                retVal = 1;
 0275                goto CheckOptionsAndReturn;
 276            }
 277
 0278            return Compare(string1.AsSpan(), string2.AsSpan(), options);
 279
 280        CheckOptionsAndReturn:
 281
 282            // If we're short-circuiting the globalization logic, we still need to check that
 283            // the provided options were valid.
 284
 0285            CheckCompareOptionsForCompare(options);
 0286            return retVal;
 287        }
 288
 289        internal int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2) =>
 0290             GlobalizationMode.Invariant ?
 0291                InvariantModeCasing.CompareStringIgnoreCase(ref MemoryMarshal.GetReference(string1), string1.Length, ref
 0292                CompareStringCore(string1, string2, CompareOptions.IgnoreCase);
 293
 294        /// <summary>
 295        /// Compares the specified regions of the two strings with the given
 296        /// options.
 297        /// Returns 0 if the two strings are equal, a number less than 0 if
 298        /// string1 is less than string2, and a number greater than 0 if
 299        /// string1 is greater than string2.
 300        /// </summary>
 301        public int Compare(string? string1, int offset1, int length1, string? string2, int offset2, int length2)
 302        {
 0303            return Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.None);
 304        }
 305
 306        public int Compare(string? string1, int offset1, string? string2, int offset2, CompareOptions options)
 307        {
 0308            return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
 0309                           string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
 310        }
 311
 312        public int Compare(string? string1, int offset1, string? string2, int offset2)
 313        {
 0314            return Compare(string1, offset1, string2, offset2, CompareOptions.None);
 315        }
 316
 317        public int Compare(string? string1, int offset1, int length1, string? string2, int offset2, int length2, Compare
 318        {
 0319            ReadOnlySpan<char> span1 = default;
 0320            ReadOnlySpan<char> span2 = default;
 321
 0322            if (string1 == null)
 323            {
 0324                if (offset1 != 0 || length1 != 0)
 325                {
 0326                    goto BoundsCheckError;
 327                }
 328            }
 0329            else if (!string1.TryGetSpan(offset1, length1, out span1))
 330            {
 331                goto BoundsCheckError;
 332            }
 333
 0334            if (string2 == null)
 335            {
 0336                if (offset2 != 0 || length2 != 0)
 337                {
 0338                    goto BoundsCheckError;
 339                }
 340            }
 0341            else if (!string2.TryGetSpan(offset2, length2, out span2))
 342            {
 343                goto BoundsCheckError;
 344            }
 345
 346            // At this point both string1 and string2 have been bounds-checked.
 347
 348            int retVal;
 349
 350            // Our paradigm is that null sorts less than any other string and
 351            // that two nulls sort as equal.
 352
 0353            if (string1 == null)
 354            {
 0355                retVal = (string2 == null) ? 0 : -1;
 0356                goto CheckOptionsAndReturn;
 357            }
 0358            if (string2 == null)
 359            {
 0360                retVal = 1;
 0361                goto CheckOptionsAndReturn;
 362            }
 363
 364            // At this point we know both string1 and string2 weren't null,
 365            // though they may have been empty.
 366
 0367            Debug.Assert(!Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span1)));
 0368            Debug.Assert(!Unsafe.IsNullRef(ref MemoryMarshal.GetReference(span2)));
 369
 0370            return Compare(span1, span2, options);
 371
 372        CheckOptionsAndReturn:
 373
 374            // If we're short-circuiting the globalization logic, we still need to check that
 375            // the provided options were valid.
 376
 0377            CheckCompareOptionsForCompare(options);
 0378            return retVal;
 379
 380        BoundsCheckError:
 381
 382            // We know a bounds check error occurred. Now we just need to figure
 383            // out the correct error message to surface.
 384
 0385            ArgumentOutOfRangeException.ThrowIfNegative(length1);
 0386            ArgumentOutOfRangeException.ThrowIfNegative(length2);
 387
 0388            ArgumentOutOfRangeException.ThrowIfNegative(offset1);
 0389            ArgumentOutOfRangeException.ThrowIfNegative(offset2);
 390
 0391            if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
 392            {
 0393                throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
 394            }
 395
 0396            Debug.Assert(offset2 > (string2 == null ? 0 : string2.Length) - length2);
 0397            throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
 398        }
 399
 400        /// <summary>
 401        /// Compares two strings.
 402        /// </summary>
 403        /// <param name="string1">The first string to compare.</param>
 404        /// <param name="string2">The second string to compare.</param>
 405        /// <param name="options">The <see cref="CompareOptions"/> to use during the comparison.</param>
 406        /// <returns>
 407        /// Zero if <paramref name="string1"/> and <paramref name="string2"/> are equal;
 408        /// or a negative value if <paramref name="string1"/> sorts before <paramref name="string2"/>;
 409        /// or a positive value if <paramref name="string1"/> sorts after <paramref name="string2"/>.
 410        /// </returns>
 411        /// <exception cref="ArgumentException">
 412        /// <paramref name="options"/> contains an unsupported combination of flags.
 413        /// </exception>
 414        public int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options = CompareOptio
 415        {
 0416            if (string1 == string2) // referential equality + length
 417            {
 0418                CheckCompareOptionsForCompare(options);
 0419                return 0;
 420            }
 421
 0422            if ((options & ValidCompareMaskOffFlags) == 0)
 423            {
 424                // Common case: caller is attempting to perform linguistic comparison.
 425                // Pass the flags down to NLS or ICU unless we're running in invariant
 426                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 427
 0428                if (!GlobalizationMode.Invariant)
 429                {
 0430                    return CompareStringCore(string1, string2, options);
 431                }
 432
 0433                if ((options & CompareOptions.IgnoreCase) == 0)
 434                {
 0435                    return string1.SequenceCompareTo(string2);
 436                }
 437
 0438                return Ordinal.CompareStringIgnoreCase(ref MemoryMarshal.GetReference(string1), string1.Length, ref Memo
 439            }
 440            else
 441            {
 442                // Less common case: caller is attempting to perform non-linguistic comparison,
 443                // or an invalid combination of flags was supplied.
 444
 0445                if (options == CompareOptions.Ordinal)
 446                {
 0447                    return string1.SequenceCompareTo(string2);
 448                }
 449
 0450                if (options == CompareOptions.OrdinalIgnoreCase)
 451                {
 0452                    return Ordinal.CompareStringIgnoreCase(ref MemoryMarshal.GetReference(string1), string1.Length, ref 
 453                }
 454
 0455                ThrowCompareOptionsCheckFailed(options);
 456
 457                return -1; // make the compiler happy;
 458            }
 459        }
 460
 461        // Checks that 'CompareOptions' is valid for a call to Compare, throwing the appropriate
 462        // exception if the check fails.
 463        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 464        [StackTraceHidden]
 465        private static void CheckCompareOptionsForCompare(CompareOptions options)
 466        {
 467            // Any combination of defined CompareOptions flags is valid, except for
 468            // Ordinal and OrdinalIgnoreCase, which may only be used in isolation.
 469
 0470            if ((options & ValidCompareMaskOffFlags) != 0)
 471            {
 0472                if (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase)
 473                {
 0474                    ThrowCompareOptionsCheckFailed(options);
 475                }
 476            }
 0477        }
 478
 479        [DoesNotReturn]
 480        [StackTraceHidden]
 481        private static void ThrowCompareOptionsCheckFailed(CompareOptions options)
 482        {
 0483            throw new ArgumentException(
 0484                paramName: nameof(options),
 0485                message: ((options & CompareOptions.Ordinal) != 0) ? SR.Argument_CompareOptionOrdinal : SR.Argument_Inva
 486        }
 487
 488        private int CompareStringCore(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options) =>
 0489            GlobalizationMode.UseNls ?
 0490                NlsCompareString(string1, string2, options) :
 0491#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 0492            GlobalizationMode.Hybrid ?
 0493                CompareStringNative(string1, string2, options) :
 0494#endif
 0495                IcuCompareString(string1, string2, options);
 496
 497        /// <summary>
 498        /// Determines whether prefix is a prefix of string.  If prefix equals
 499        /// string.Empty, true is returned.
 500        /// </summary>
 501        public bool IsPrefix(string source, string prefix, CompareOptions options)
 502        {
 0503            if (source == null)
 504            {
 0505                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 506            }
 0507            if (prefix == null)
 508            {
 0509                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.prefix);
 510            }
 511
 0512            return IsPrefix(source.AsSpan(), prefix.AsSpan(), options);
 513        }
 514
 515        /// <summary>
 516        /// Determines whether a string starts with a specific prefix.
 517        /// </summary>
 518        /// <param name="source">The string to search within.</param>
 519        /// <param name="prefix">The prefix to attempt to match at the start of <paramref name="source"/>.</param>
 520        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
 521        /// <returns>
 522        /// <see langword="true"/> if <paramref name="prefix"/> occurs at the start of <paramref name="source"/>;
 523        /// otherwise, <see langword="false"/>.
 524        /// </returns>
 525        /// <exception cref="ArgumentException">
 526        /// <paramref name="options"/> contains an unsupported combination of flags.
 527        /// </exception>
 528        public unsafe bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options = Compa
 529        {
 530            // The empty string is trivially a prefix of every other string. For compat with
 531            // earlier versions of the Framework we'll early-exit here before validating the
 532            // 'options' argument.
 533
 0534            if (prefix.IsEmpty)
 535            {
 0536                return true;
 537            }
 538
 0539            if ((options & ValidIndexMaskOffFlags) == 0)
 540            {
 541                // Common case: caller is attempting to perform a linguistic search.
 542                // Pass the flags down to NLS or ICU unless we're running in invariant
 543                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 544
 0545                if (!GlobalizationMode.Invariant)
 546                {
 0547                    return StartsWithCore(source, prefix, options, matchLengthPtr: null);
 548                }
 549
 0550                if ((options & CompareOptions.IgnoreCase) == 0)
 551                {
 0552                    return source.StartsWith(prefix);
 553                }
 554
 0555                return source.StartsWithOrdinalIgnoreCase(prefix);
 556            }
 557            else
 558            {
 559                // Less common case: caller is attempting to perform non-linguistic comparison,
 560                // or an invalid combination of flags was supplied.
 561
 0562                if (options == CompareOptions.Ordinal)
 563                {
 0564                    return source.StartsWith(prefix);
 565                }
 566
 0567                if (options == CompareOptions.OrdinalIgnoreCase)
 568                {
 0569                    return source.StartsWithOrdinalIgnoreCase(prefix);
 570                }
 571
 0572                ThrowCompareOptionsCheckFailed(options);
 573
 574                return false; // make the compiler happy;
 575            }
 576        }
 577
 578        /// <summary>
 579        /// Determines whether a string starts with a specific prefix.
 580        /// </summary>
 581        /// <param name="source">The string to search within.</param>
 582        /// <param name="prefix">The prefix to attempt to match at the start of <paramref name="source"/>.</param>
 583        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
 584        /// <param name="matchLength">When this method returns, contains the number of characters of
 585        /// <paramref name="source"/> that matched the desired prefix. This may be different than the
 586        /// length of <paramref name="prefix"/> if a linguistic comparison is performed. Set to 0
 587        /// if the prefix did not match.</param>
 588        /// <returns>
 589        /// <see langword="true"/> if <paramref name="prefix"/> occurs at the start of <paramref name="source"/>;
 590        /// otherwise, <see langword="false"/>.
 591        /// </returns>
 592        /// <exception cref="ArgumentException">
 593        /// <paramref name="options"/> contains an unsupported combination of flags.
 594        /// </exception>
 595        /// <remarks>
 596        /// This method has greater overhead than other <see cref="IsPrefix"/> overloads which don't
 597        /// take a <paramref name="matchLength"/> argument. Call this overload only if you require
 598        /// the match length information.
 599        /// </remarks>
 600        public unsafe bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options, out in
 601        {
 602            bool matched;
 603
 0604            if (GlobalizationMode.Invariant || prefix.IsEmpty || (options & ValidIndexMaskOffFlags) != 0)
 605            {
 606                // Non-linguistic (ordinal) comparison requested, or options are invalid.
 607                // Delegate to other overload, which validates options and throws on failure.
 608                // If success, non-linguistic matches will always preserve prefix length.
 609
 0610                matched = IsPrefix(source, prefix, options);
 0611                matchLength = (matched) ? prefix.Length : 0;
 612            }
 613            else
 614            {
 615                // Linguistic comparison requested and we don't need to special-case any args.
 616#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 617                if (GlobalizationMode.Hybrid)
 618                {
 619                    throw new PlatformNotSupportedException(SR.PlatformNotSupported_HybridGlobalizationWithMatchLength);
 620                }
 621#endif
 0622                int tempMatchLength = 0;
 0623                matched = StartsWithCore(source, prefix, options, &tempMatchLength);
 0624                matchLength = tempMatchLength;
 625            }
 626
 0627            return matched;
 628        }
 629
 630        private unsafe bool StartsWithCore(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options,
 0631            GlobalizationMode.UseNls ?
 0632                NlsStartsWith(source, prefix, options, matchLengthPtr) :
 0633                IcuStartsWith(source, prefix, options, matchLengthPtr);
 634
 635        public bool IsPrefix(string source, string prefix)
 636        {
 0637            return IsPrefix(source, prefix, CompareOptions.None);
 638        }
 639
 640        /// <summary>
 641        /// Determines whether suffix is a suffix of string.  If suffix equals
 642        /// string.Empty, true is returned.
 643        /// </summary>
 644        public bool IsSuffix(string source, string suffix, CompareOptions options)
 645        {
 0646            if (source == null)
 647            {
 0648                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 649            }
 0650            if (suffix == null)
 651            {
 0652                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.suffix);
 653            }
 654
 0655            return IsSuffix(source.AsSpan(), suffix.AsSpan(), options);
 656        }
 657
 658        /// <summary>
 659        /// Determines whether a string ends with a specific suffix.
 660        /// </summary>
 661        /// <param name="source">The string to search within.</param>
 662        /// <param name="suffix">The suffix to attempt to match at the end of <paramref name="source"/>.</param>
 663        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
 664        /// <returns>
 665        /// <see langword="true"/> if <paramref name="suffix"/> occurs at the end of <paramref name="source"/>;
 666        /// otherwise, <see langword="false"/>.
 667        /// </returns>
 668        /// <exception cref="ArgumentException">
 669        /// <paramref name="options"/> contains an unsupported combination of flags.
 670        /// </exception>
 671        public unsafe bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options = Compa
 672        {
 673            // The empty string is trivially a suffix of every other string. For compat with
 674            // earlier versions of the Framework we'll early-exit here before validating the
 675            // 'options' argument.
 676
 0677            if (suffix.IsEmpty)
 678            {
 0679                return true;
 680            }
 681
 0682            if ((options & ValidIndexMaskOffFlags) == 0)
 683            {
 684                // Common case: caller is attempting to perform a linguistic search.
 685                // Pass the flags down to NLS or ICU unless we're running in invariant
 686                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 687
 0688                if (!GlobalizationMode.Invariant)
 689                {
 0690                    return EndsWithCore(source, suffix, options, matchLengthPtr: null);
 691                }
 692
 0693                if ((options & CompareOptions.IgnoreCase) == 0)
 694                {
 0695                    return source.EndsWith(suffix);
 696                }
 697
 0698                return source.EndsWithOrdinalIgnoreCase(suffix);
 699            }
 700            else
 701            {
 702                // Less common case: caller is attempting to perform non-linguistic comparison,
 703                // or an invalid combination of flags was supplied.
 704
 0705                if (options == CompareOptions.Ordinal)
 706                {
 0707                    return source.EndsWith(suffix);
 708                }
 709
 0710                if (options == CompareOptions.OrdinalIgnoreCase)
 711                {
 0712                    return source.EndsWithOrdinalIgnoreCase(suffix);
 713                }
 714
 0715                ThrowCompareOptionsCheckFailed(options);
 716
 717                return false; // make the compiler happy;
 718            }
 719        }
 720
 721        /// <summary>
 722        /// Determines whether a string ends with a specific suffix.
 723        /// </summary>
 724        /// <param name="source">The string to search within.</param>
 725        /// <param name="suffix">The suffix to attempt to match at the end of <paramref name="source"/>.</param>
 726        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
 727        /// <param name="matchLength">When this method returns, contains the number of characters of
 728        /// <paramref name="source"/> that matched the desired suffix. This may be different than the
 729        /// length of <paramref name="suffix"/> if a linguistic comparison is performed. Set to 0
 730        /// if the suffix did not match.</param>
 731        /// <returns>
 732        /// <see langword="true"/> if <paramref name="suffix"/> occurs at the end of <paramref name="source"/>;
 733        /// otherwise, <see langword="false"/>.
 734        /// </returns>
 735        /// <exception cref="ArgumentException">
 736        /// <paramref name="options"/> contains an unsupported combination of flags.
 737        /// </exception>
 738        /// <remarks>
 739        /// This method has greater overhead than other <see cref="IsSuffix"/> overloads which don't
 740        /// take a <paramref name="matchLength"/> argument. Call this overload only if you require
 741        /// the match length information.
 742        /// </remarks>
 743        public unsafe bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options, out in
 744        {
 745            bool matched;
 746
 0747            if (GlobalizationMode.Invariant || suffix.IsEmpty || (options & ValidIndexMaskOffFlags) != 0)
 748            {
 749                // Non-linguistic (ordinal) comparison requested, or options are invalid.
 750                // Delegate to other overload, which validates options and throws on failure.
 751                // If success, non-linguistic matches will always preserve prefix length.
 752
 0753                matched = IsSuffix(source, suffix, options);
 0754                matchLength = (matched) ? suffix.Length : 0;
 755            }
 756            else
 757            {
 758                // Linguistic comparison requested and we don't need to special-case any args.
 759#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 760                if (GlobalizationMode.Hybrid)
 761                {
 762                    throw new PlatformNotSupportedException(SR.PlatformNotSupported_HybridGlobalizationWithMatchLength);
 763                }
 764#endif
 0765                int tempMatchLength = 0;
 0766                matched = EndsWithCore(source, suffix, options, &tempMatchLength);
 0767                matchLength = tempMatchLength;
 768            }
 769
 0770            return matched;
 771        }
 772
 773        public bool IsSuffix(string source, string suffix)
 774        {
 0775            return IsSuffix(source, suffix, CompareOptions.None);
 776        }
 777
 778        private unsafe bool EndsWithCore(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options, i
 0779            GlobalizationMode.UseNls ?
 0780                NlsEndsWith(source, suffix, options, matchLengthPtr) :
 0781                IcuEndsWith(source, suffix, options, matchLengthPtr);
 782
 783        /// <summary>
 784        /// Returns the first index where value is found in string.  The
 785        /// search starts from startIndex and ends at endIndex.  Returns -1 if
 786        /// the specified value is not found.  If value equals string.Empty,
 787        /// startIndex is returned.  Throws IndexOutOfRange if startIndex or
 788        /// endIndex is less than zero or greater than the length of string.
 789        /// Throws ArgumentException if value (as a string) is null.
 790        /// </summary>
 791        public int IndexOf(string source, char value)
 792        {
 0793            return IndexOf(source, value, CompareOptions.None);
 794        }
 795
 796        public int IndexOf(string source, string value)
 797        {
 0798            return IndexOf(source, value, CompareOptions.None);
 799        }
 800
 801        public int IndexOf(string source, char value, CompareOptions options)
 802        {
 0803            if (source == null)
 804            {
 0805                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 806            }
 807
 0808            return IndexOf(source, new ReadOnlySpan<char>(in value), options);
 809        }
 810
 811        public int IndexOf(string source, string value, CompareOptions options)
 812        {
 0813            if (source == null)
 814            {
 0815                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 816            }
 0817            if (value == null)
 818            {
 0819                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
 820            }
 821
 0822            return IndexOf(source.AsSpan(), value.AsSpan(), options);
 823        }
 824
 825        public int IndexOf(string source, char value, int startIndex)
 826        {
 0827            return IndexOf(source, value, startIndex, CompareOptions.None);
 828        }
 829
 830        public int IndexOf(string source, string value, int startIndex)
 831        {
 0832            return IndexOf(source, value, startIndex, CompareOptions.None);
 833        }
 834
 835        public int IndexOf(string source, char value, int startIndex, CompareOptions options)
 836        {
 0837            if (source == null)
 838            {
 0839                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 840            }
 841
 0842            return IndexOf(source, value, startIndex, source.Length - startIndex, options);
 843
 844        }
 845
 846        public int IndexOf(string source, string value, int startIndex, CompareOptions options)
 847        {
 0848            if (source == null)
 849            {
 0850                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 851            }
 852
 0853            return IndexOf(source, value, startIndex, source.Length - startIndex, options);
 854        }
 855
 856        public int IndexOf(string source, char value, int startIndex, int count)
 857        {
 0858            return IndexOf(source, value, startIndex, count, CompareOptions.None);
 859        }
 860
 861        public int IndexOf(string source, string value, int startIndex, int count)
 862        {
 0863            return IndexOf(source, value, startIndex, count, CompareOptions.None);
 864        }
 865
 866        public int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
 867        {
 0868            if (source == null)
 869            {
 0870                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 871            }
 872
 0873            if (!source.TryGetSpan(startIndex, count, out ReadOnlySpan<char> sourceSpan))
 874            {
 875                // Bounds check failed - figure out exactly what went wrong so that we can
 876                // surface the correct argument exception.
 877
 0878                if ((uint)startIndex > (uint)source.Length)
 879                {
 0880                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.Argumen
 881                }
 882                else
 883                {
 0884                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutO
 885                }
 886            }
 887
 0888            int result = IndexOf(sourceSpan, new ReadOnlySpan<char>(in value), options);
 0889            if (result >= 0)
 890            {
 0891                result += startIndex;
 892            }
 0893            return result;
 894        }
 895
 896        public int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
 897        {
 0898            if (source == null)
 899            {
 0900                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 901            }
 0902            if (value == null)
 903            {
 0904                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
 905            }
 906
 0907            if (!source.TryGetSpan(startIndex, count, out ReadOnlySpan<char> sourceSpan))
 908            {
 909                // Bounds check failed - figure out exactly what went wrong so that we can
 910                // surface the correct argument exception.
 911
 0912                if ((uint)startIndex > (uint)source.Length)
 913                {
 0914                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.Argumen
 915                }
 916                else
 917                {
 0918                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutO
 919                }
 920            }
 921
 0922            int result = IndexOf(sourceSpan, value, options);
 0923            if (result >= 0)
 924            {
 0925                result += startIndex;
 926            }
 0927            return result;
 928        }
 929
 930        /// <summary>
 931        /// Searches for the first occurrence of a substring within a source string.
 932        /// </summary>
 933        /// <param name="source">The string to search within.</param>
 934        /// <param name="value">The substring to locate within <paramref name="source"/>.</param>
 935        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 936        /// <returns>
 937        /// The zero-based index into <paramref name="source"/> where the substring <paramref name="value"/>
 938        /// first appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 939        /// </returns>
 940        /// <exception cref="ArgumentException">
 941        /// <paramref name="options"/> contains an unsupported combination of flags.
 942        /// </exception>
 943        public unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options = CompareO
 944        {
 0945            if ((options & ValidIndexMaskOffFlags) == 0)
 946            {
 947                // Common case: caller is attempting to perform a linguistic search.
 948                // Pass the flags down to NLS or ICU unless we're running in invariant
 949                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 950
 0951                if (!GlobalizationMode.Invariant)
 952                {
 0953                    if (value.IsEmpty)
 954                    {
 0955                        return 0; // Empty target string trivially occurs at index 0 of every search space.
 956                    }
 957                    else
 958                    {
 0959                        return IndexOfCore(source, value, options, matchLengthPtr: null, fromBeginning: true);
 960                    }
 961                }
 962
 0963                if ((options & CompareOptions.IgnoreCase) == 0)
 964                {
 0965                    return source.IndexOf(value);
 966                }
 967
 0968                return Ordinal.IndexOfOrdinalIgnoreCase(source, value);
 969            }
 970            else
 971            {
 972                // Less common case: caller is attempting to perform non-linguistic comparison,
 973                // or an invalid combination of flags was supplied.
 974
 0975                if (options == CompareOptions.Ordinal)
 976                {
 0977                    return source.IndexOf(value);
 978                }
 979
 0980                if (options == CompareOptions.OrdinalIgnoreCase)
 981                {
 0982                    return Ordinal.IndexOfOrdinalIgnoreCase(source, value);
 983                }
 984
 0985                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidFlag, ExceptionArgument.options);
 986
 987                return -1; // make the compiler happy;
 988            }
 989        }
 990
 991        /// <summary>
 992        /// Searches for the first occurrence of a substring within a source string.
 993        /// </summary>
 994        /// <param name="source">The string to search within.</param>
 995        /// <param name="value">The substring to locate within <paramref name="source"/>.</param>
 996        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 997        /// <param name="matchLength">When this method returns, contains the number of characters of
 998        /// <paramref name="source"/> that matched the desired value. This may be different than the
 999        /// length of <paramref name="value"/> if a linguistic comparison is performed. Set to 0
 1000        /// if <paramref name="value"/> is not found within <paramref name="source"/>.</param>
 1001        /// <returns>
 1002        /// The zero-based index into <paramref name="source"/> where the substring <paramref name="value"/>
 1003        /// first appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 1004        /// </returns>
 1005        /// <exception cref="ArgumentException">
 1006        /// <paramref name="options"/> contains an unsupported combination of flags.
 1007        /// </exception>
 1008        /// <remarks>
 1009        /// This method has greater overhead than other <see cref="IndexOf"/> overloads which don't
 1010        /// take a <paramref name="matchLength"/> argument. Call this overload only if you require
 1011        /// the match length information.
 1012        /// </remarks>
 1013        public unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options, out int m
 1014        {
 1015            int tempMatchLength;
 01016            int retVal = IndexOf(source, value, &tempMatchLength, options, fromBeginning: true);
 01017            matchLength = tempMatchLength;
 01018            return retVal;
 1019        }
 1020
 1021        /// <summary>
 1022        /// Searches for the first occurrence of a <see cref="Rune"/> within a source string.
 1023        /// </summary>
 1024        /// <param name="source">The string to search within.</param>
 1025        /// <param name="value">The <see cref="Rune"/> to locate within <paramref name="source"/>.</param>
 1026        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 1027        /// <returns>
 1028        /// The zero-based index into <paramref name="source"/> where <paramref name="value"/>
 1029        /// first appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 1030        /// </returns>
 1031        /// <exception cref="ArgumentException">
 1032        /// <paramref name="options"/> contains an unsupported combination of flags.
 1033        /// </exception>
 1034        public unsafe int IndexOf(ReadOnlySpan<char> source, Rune value, CompareOptions options = CompareOptions.None)
 1035        {
 01036            Span<char> valueAsUtf16 = stackalloc char[Rune.MaxUtf16CharsPerRune];
 01037            int charCount = value.EncodeToUtf16(valueAsUtf16);
 01038            return IndexOf(source, valueAsUtf16.Slice(0, charCount), options);
 1039        }
 1040
 1041        /// <summary>
 1042        /// IndexOf overload used when the caller needs the length of the matching substring.
 1043        /// Caller needs to ensure <paramref name="matchLengthPtr"/> is non-null and points
 1044        /// to a valid address. This method will validate <paramref name="options"/>.
 1045        /// </summary>
 1046        private unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, int* matchLengthPtr, CompareOpti
 1047        {
 01048            Debug.Assert(matchLengthPtr != null);
 01049            *matchLengthPtr = 0;
 1050
 01051            int retVal = 0;
 1052
 01053            if ((options & ValidIndexMaskOffFlags) == 0)
 1054            {
 1055                // Common case: caller is attempting to perform a linguistic search.
 1056                // Pass the flags down to NLS or ICU unless we're running in invariant
 1057                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 1058
 01059                if (!GlobalizationMode.Invariant)
 1060                {
 01061                    if (value.IsEmpty)
 1062                    {
 1063                        // empty target substring trivially occurs at beginning / end of search space
 01064                        return (fromBeginning) ? 0 : source.Length;
 1065                    }
 1066                    else
 1067                    {
 01068                        return IndexOfCore(source, value, options, matchLengthPtr, fromBeginning);
 1069                    }
 1070                }
 1071
 01072                if ((options & CompareOptions.IgnoreCase) == 0)
 1073                {
 01074                    retVal = (fromBeginning) ? source.IndexOf(value) : source.LastIndexOf(value);
 1075                }
 1076                else
 1077                {
 01078                    retVal = fromBeginning ? Ordinal.IndexOfOrdinalIgnoreCase(source, value) : Ordinal.LastIndexOfOrdina
 1079                }
 1080            }
 1081            else
 1082            {
 1083                // Less common case: caller is attempting to perform non-linguistic comparison,
 1084                // or an invalid combination of flags was supplied.
 1085
 01086                if (options == CompareOptions.Ordinal)
 1087                {
 01088                    retVal = (fromBeginning) ? source.IndexOf(value) : source.LastIndexOf(value);
 1089                }
 01090                else if (options == CompareOptions.OrdinalIgnoreCase)
 1091                {
 01092                    retVal = fromBeginning ? Ordinal.IndexOfOrdinalIgnoreCase(source, value) : Ordinal.LastIndexOfOrdina
 1093                }
 1094                else
 1095                {
 01096                    ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidFlag, ExceptionArgument.options
 1097                }
 1098            }
 1099
 1100            // Both Ordinal and OrdinalIgnoreCase match by individual code points in a non-linguistic manner.
 1101            // Non-BMP code points will never match BMP code points, so given UTF-16 inputs the match length
 1102            // will always be equivalent to the target string length.
 1103
 01104            if (retVal >= 0)
 1105            {
 01106                *matchLengthPtr = value.Length;
 1107            }
 01108            return retVal;
 1109        }
 1110
 1111        private unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int
 01112            GlobalizationMode.UseNls ?
 01113                NlsIndexOfCore(source, target, options, matchLengthPtr, fromBeginning) :
 01114                IcuIndexOfCore(source, target, options, matchLengthPtr, fromBeginning);
 1115
 1116        /// <summary>
 1117        /// Returns the last index where value is found in string.  The
 1118        /// search starts from startIndex and ends at endIndex.  Returns -1 if
 1119        /// the specified value is not found.  If value equals string.Empty,
 1120        /// endIndex is returned.  Throws IndexOutOfRange if startIndex or
 1121        /// endIndex is less than zero or greater than the length of string.
 1122        /// Throws ArgumentException if value (as a string) is null.
 1123        /// </summary>
 1124        public int LastIndexOf(string source, char value)
 1125        {
 01126            return LastIndexOf(source, value, CompareOptions.None);
 1127        }
 1128
 1129        public int LastIndexOf(string source, string value)
 1130        {
 01131            return LastIndexOf(source, value, CompareOptions.None);
 1132        }
 1133
 1134        public int LastIndexOf(string source, char value, CompareOptions options)
 1135        {
 01136            if (source == null)
 1137            {
 01138                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 1139            }
 1140
 01141            return LastIndexOf(source, new ReadOnlySpan<char>(in value), options);
 1142        }
 1143
 1144        public int LastIndexOf(string source, string value, CompareOptions options)
 1145        {
 01146            if (source == null)
 1147            {
 01148                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 1149            }
 01150            if (value == null)
 1151            {
 01152                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
 1153            }
 1154
 01155            return LastIndexOf(source.AsSpan(), value.AsSpan(), options);
 1156        }
 1157
 1158        public int LastIndexOf(string source, char value, int startIndex)
 1159        {
 01160            return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
 1161        }
 1162
 1163        public int LastIndexOf(string source, string value, int startIndex)
 1164        {
 01165            return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
 1166        }
 1167
 1168        public int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
 1169        {
 01170            return LastIndexOf(source, value, startIndex, startIndex + 1, options);
 1171        }
 1172
 1173        public int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
 1174        {
 01175            return LastIndexOf(source, value, startIndex, startIndex + 1, options);
 1176        }
 1177
 1178        public int LastIndexOf(string source, char value, int startIndex, int count)
 1179        {
 01180            return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
 1181        }
 1182
 1183        public int LastIndexOf(string source, string value, int startIndex, int count)
 1184        {
 01185            return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
 1186        }
 1187
 1188        public int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
 1189        {
 01190            if (source == null)
 1191            {
 01192                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 1193            }
 1194
 1195        TryAgain:
 1196
 1197            // Previous versions of the Framework special-cased empty 'source' to allow startIndex = -1 or startIndex = 
 1198            // ignoring 'count' and short-circuiting the entire operation. We'll silently fix up the 'count' parameter
 1199            // if this occurs.
 1200            //
 1201            // See the comments just before string.IndexOf(string) for more information on how these computations are
 1202            // performed.
 1203
 01204            if ((uint)startIndex >= (uint)source.Length)
 1205            {
 01206                if (startIndex == -1 && source.Length == 0)
 1207                {
 01208                    count = 0; // normalize
 1209                }
 01210                else if (startIndex == source.Length)
 1211                {
 1212                    // The caller likely had an off-by-one error when invoking the API. The Framework has historically
 1213                    // allowed for this and tried to fix up the parameters, so we'll continue to do so for compat.
 1214
 01215                    startIndex--;
 01216                    if (count > 0)
 1217                    {
 01218                        count--;
 1219                    }
 1220
 01221                    goto TryAgain; // guaranteed never to loop more than once
 1222                }
 1223                else
 1224                {
 01225                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.Argumen
 1226                }
 1227            }
 1228
 01229            startIndex = startIndex - count + 1; // this will be the actual index where we begin our search
 1230
 01231            if (!source.TryGetSpan(startIndex, count, out ReadOnlySpan<char> sourceSpan))
 1232            {
 01233                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
 1234            }
 1235
 01236            int retVal = LastIndexOf(sourceSpan, new ReadOnlySpan<char>(in value), options);
 01237            if (retVal >= 0)
 1238            {
 01239                retVal += startIndex;
 1240            }
 01241            return retVal;
 1242        }
 1243
 1244        public int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
 1245        {
 01246            if (source == null)
 1247            {
 01248                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 1249            }
 01250            if (value == null)
 1251            {
 01252                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
 1253            }
 1254
 1255        TryAgain:
 1256
 1257            // Previous versions of the Framework special-cased empty 'source' to allow startIndex = -1 or startIndex = 
 1258            // ignoring 'count' and short-circuiting the entire operation. We'll silently fix up the 'count' parameter
 1259            // if this occurs.
 1260            //
 1261            // See the comments just before string.IndexOf(string) for more information on how these computations are
 1262            // performed.
 1263
 01264            if ((uint)startIndex >= (uint)source.Length)
 1265            {
 01266                if (startIndex == -1 && source.Length == 0)
 1267                {
 01268                    count = 0; // normalize
 1269                }
 01270                else if (startIndex == source.Length)
 1271                {
 1272                    // The caller likely had an off-by-one error when invoking the API. The Framework has historically
 1273                    // allowed for this and tried to fix up the parameters, so we'll continue to do so for compat.
 1274
 01275                    startIndex--;
 01276                    if (count > 0)
 1277                    {
 01278                        count--;
 1279                    }
 1280
 01281                    goto TryAgain; // guaranteed never to loop more than once
 1282                }
 1283                else
 1284                {
 01285                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.Argumen
 1286                }
 1287            }
 1288
 01289            startIndex = startIndex - count + 1; // this will be the actual index where we begin our search
 1290
 01291            if (!source.TryGetSpan(startIndex, count, out ReadOnlySpan<char> sourceSpan))
 1292            {
 01293                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
 1294            }
 1295
 01296            int retVal = LastIndexOf(sourceSpan, value, options);
 01297            if (retVal >= 0)
 1298            {
 01299                retVal += startIndex;
 1300            }
 01301            return retVal;
 1302        }
 1303
 1304        /// <summary>
 1305        /// Searches for the last occurrence of a substring within a source string.
 1306        /// </summary>
 1307        /// <param name="source">The string to search within.</param>
 1308        /// <param name="value">The substring to locate within <paramref name="source"/>.</param>
 1309        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 1310        /// <returns>
 1311        /// The zero-based index into <paramref name="source"/> where the substring <paramref name="value"/>
 1312        /// last appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 1313        /// </returns>
 1314        /// <exception cref="ArgumentException">
 1315        /// <paramref name="options"/> contains an unsupported combination of flags.
 1316        /// </exception>
 1317        public unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options = Comp
 1318        {
 01319            if ((options & ValidIndexMaskOffFlags) == 0)
 1320            {
 1321                // Common case: caller is attempting to perform a linguistic search.
 1322                // Pass the flags down to NLS or ICU unless we're running in invariant
 1323                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 1324
 01325                if (!GlobalizationMode.Invariant)
 1326                {
 01327                    if (value.IsEmpty)
 1328                    {
 01329                        return source.Length; // Empty target string trivially occurs at the last index of every search 
 1330                    }
 1331                    else
 1332                    {
 01333                        return IndexOfCore(source, value, options, matchLengthPtr: null, fromBeginning: false);
 1334                    }
 1335                }
 1336
 01337                if ((options & CompareOptions.IgnoreCase) == 0)
 1338                {
 01339                    return source.LastIndexOf(value);
 1340                }
 1341
 01342                return Ordinal.LastIndexOfOrdinalIgnoreCase(source, value);
 1343            }
 1344            else
 1345            {
 1346                // Less common case: caller is attempting to perform non-linguistic comparison,
 1347                // or an invalid combination of flags was supplied.
 1348
 01349                if (options == CompareOptions.Ordinal)
 1350                {
 01351                    return source.LastIndexOf(value);
 1352                }
 1353
 01354                if (options == CompareOptions.OrdinalIgnoreCase)
 1355                {
 01356                    return Ordinal.LastIndexOfOrdinalIgnoreCase(source, value);
 1357                }
 1358
 01359                throw new ArgumentException(paramName: nameof(options), message: SR.Argument_InvalidFlag);
 1360            }
 1361        }
 1362
 1363        /// <summary>
 1364        /// Searches for the last occurrence of a substring within a source string.
 1365        /// </summary>
 1366        /// <param name="source">The string to search within.</param>
 1367        /// <param name="value">The substring to locate within <paramref name="source"/>.</param>
 1368        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 1369        /// <param name="matchLength">When this method returns, contains the number of characters of
 1370        /// <paramref name="source"/> that matched the desired value. This may be different than the
 1371        /// length of <paramref name="value"/> if a linguistic comparison is performed. Set to 0
 1372        /// if <paramref name="value"/> is not found within <paramref name="source"/>.</param>
 1373        /// <returns>
 1374        /// The zero-based index into <paramref name="source"/> where the substring <paramref name="value"/>
 1375        /// last appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 1376        /// </returns>
 1377        /// <exception cref="ArgumentException">
 1378        /// <paramref name="options"/> contains an unsupported combination of flags.
 1379        /// </exception>
 1380        /// <remarks>
 1381        /// This method has greater overhead than other <see cref="IndexOf"/> overloads which don't
 1382        /// take a <paramref name="matchLength"/> argument. Call this overload only if you require
 1383        /// the match length information.
 1384        /// </remarks>
 1385        public unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options, out i
 1386        {
 1387            int tempMatchLength;
 01388            int retVal = IndexOf(source, value, &tempMatchLength, options, fromBeginning: false);
 01389            matchLength = tempMatchLength;
 01390            return retVal;
 1391        }
 1392
 1393        /// <summary>
 1394        /// Searches for the last occurrence of a <see cref="Rune"/> within a source string.
 1395        /// </summary>
 1396        /// <param name="source">The string to search within.</param>
 1397        /// <param name="value">The <see cref="Rune"/> to locate within <paramref name="source"/>.</param>
 1398        /// <param name="options">The <see cref="CompareOptions"/> to use during the search.</param>
 1399        /// <returns>
 1400        /// The zero-based index into <paramref name="source"/> where <paramref name="value"/>
 1401        /// last appears; or -1 if <paramref name="value"/> cannot be found within <paramref name="source"/>.
 1402        /// </returns>
 1403        /// <exception cref="ArgumentException">
 1404        /// <paramref name="options"/> contains an unsupported combination of flags.
 1405        /// </exception>
 1406        public unsafe int LastIndexOf(ReadOnlySpan<char> source, Rune value, CompareOptions options = CompareOptions.Non
 1407        {
 01408            Span<char> valueAsUtf16 = stackalloc char[Rune.MaxUtf16CharsPerRune];
 01409            int charCount = value.EncodeToUtf16(valueAsUtf16);
 01410            return LastIndexOf(source, valueAsUtf16.Slice(0, charCount), options);
 1411        }
 1412
 1413        /// <summary>
 1414        /// Gets the SortKey for the given string with the given options.
 1415        /// </summary>
 1416        public SortKey GetSortKey(string source, CompareOptions options)
 1417        {
 01418            if (GlobalizationMode.Invariant)
 1419            {
 01420                return InvariantCreateSortKey(source, options);
 1421            }
 1422
 01423            return CreateSortKeyCore(source, options);
 1424        }
 1425
 1426        public SortKey GetSortKey(string source)
 1427        {
 01428            if (GlobalizationMode.Invariant)
 1429            {
 01430                return InvariantCreateSortKey(source, CompareOptions.None);
 1431            }
 1432
 01433            return CreateSortKeyCore(source, CompareOptions.None);
 1434        }
 1435
 1436        private SortKey CreateSortKeyCore(string source, CompareOptions options) =>
 01437            GlobalizationMode.UseNls ?
 01438                NlsCreateSortKey(source, options) :
 01439                IcuCreateSortKey(source, options);
 1440
 1441        /// <summary>
 1442        /// Computes a sort key over the specified input.
 1443        /// </summary>
 1444        /// <param name="source">The text over which to compute the sort key.</param>
 1445        /// <param name="destination">The buffer into which to write the resulting sort key bytes.</param>
 1446        /// <param name="options">The <see cref="CompareOptions"/> used for computing the sort key.</param>
 1447        /// <returns>The number of bytes written to <paramref name="destination"/>.</returns>
 1448        /// <remarks>
 1449        /// Use <see cref="GetSortKeyLength"/> to query the required size of <paramref name="destination"/>.
 1450        /// It is acceptable to provide a larger-than-necessary output buffer to this method.
 1451        /// </remarks>
 1452        /// <exception cref="ArgumentException">
 1453        /// <paramref name="destination"/> is too small to contain the resulting sort key;
 1454        /// or <paramref name="options"/> contains an unsupported flag;
 1455        /// or <paramref name="source"/> cannot be processed using the desired <see cref="CompareOptions"/>
 1456        /// under the current <see cref="CompareInfo"/>.
 1457        /// </exception>
 1458        public int GetSortKey(ReadOnlySpan<char> source, Span<byte> destination, CompareOptions options = CompareOptions
 1459        {
 01460            if ((options & ValidCompareMaskOffFlags) != 0)
 1461            {
 01462                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidFlag, ExceptionArgument.options);
 1463            }
 1464
 01465            if (GlobalizationMode.Invariant)
 1466            {
 01467                return InvariantGetSortKey(source, destination, options);
 1468            }
 1469            else
 1470            {
 01471                return GetSortKeyCore(source, destination, options);
 1472            }
 1473        }
 1474
 1475        private int GetSortKeyCore(ReadOnlySpan<char> source, Span<byte> destination, CompareOptions options) =>
 01476            GlobalizationMode.UseNls ?
 01477                NlsGetSortKey(source, destination, options) :
 01478                IcuGetSortKey(source, destination, options);
 1479
 1480        /// <summary>
 1481        /// Returns the length (in bytes) of the sort key that would be produced from the specified input.
 1482        /// </summary>
 1483        /// <param name="source">The text over which to compute the sort key.</param>
 1484        /// <param name="options">The <see cref="CompareOptions"/> used for computing the sort key.</param>
 1485        /// <returns>The length (in bytes) of the sort key.</returns>
 1486        /// <exception cref="ArgumentException">
 1487        /// <paramref name="options"/> contains an unsupported flag;
 1488        /// or <paramref name="source"/> cannot be processed using the desired <see cref="CompareOptions"/>
 1489        /// under the current <see cref="CompareInfo"/>.
 1490        /// </exception>
 1491        public int GetSortKeyLength(ReadOnlySpan<char> source, CompareOptions options = CompareOptions.None)
 1492        {
 01493            if ((options & ValidCompareMaskOffFlags) != 0)
 1494            {
 01495                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidFlag, ExceptionArgument.options);
 1496            }
 1497
 01498            if (GlobalizationMode.Invariant)
 1499            {
 01500                return InvariantGetSortKeyLength(source, options);
 1501            }
 1502            else
 1503            {
 01504                return GetSortKeyLengthCore(source, options);
 1505            }
 1506        }
 1507
 1508        private int GetSortKeyLengthCore(ReadOnlySpan<char> source, CompareOptions options) =>
 01509            GlobalizationMode.UseNls ?
 01510              NlsGetSortKeyLength(source, options) :
 01511              IcuGetSortKeyLength(source, options);
 1512
 1513        public override bool Equals([NotNullWhen(true)] object? value)
 1514        {
 01515            return value is CompareInfo otherCompareInfo
 01516                && Name == otherCompareInfo.Name;
 1517        }
 1518
 01519        public override int GetHashCode() => Name.GetHashCode();
 1520
 1521        /// <summary>
 1522        /// This method performs the equivalent of of creating a Sortkey for a string from CompareInfo,
 1523        /// then generates a randomized hashcode value from the sort key.
 1524        ///
 1525        /// The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
 1526        /// the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
 1527        /// treat the string the same way, this implementation will treat them differently (the same way that
 1528        /// Sortkey does at the moment).
 1529        /// </summary>
 1530        public int GetHashCode(string source, CompareOptions options)
 1531        {
 01532            if (source == null)
 1533            {
 01534                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
 1535            }
 1536
 01537            return GetHashCode(source.AsSpan(), options);
 1538        }
 1539
 1540        public int GetHashCode(ReadOnlySpan<char> source, CompareOptions options)
 1541        {
 01542            if ((options & ValidCompareMaskOffFlags) == 0)
 1543            {
 1544                // Common case: caller is attempting to get a linguistic sort key.
 1545                // Pass the flags down to NLS or ICU unless we're running in invariant
 1546                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 1547
 01548                if (!GlobalizationMode.Invariant)
 1549                {
 01550                    return GetHashCodeOfStringCore(source, options);
 1551                }
 1552
 01553                return InvariantGetHashCode(source, options);
 1554            }
 1555            else
 1556            {
 1557                // Less common case: caller is attempting to get a non-linguistic sort key,
 1558                // or an invalid combination of flags was supplied.
 1559
 01560                if (options == CompareOptions.Ordinal)
 1561                {
 01562                    return string.GetHashCode(source);
 1563                }
 1564
 01565                if (options == CompareOptions.OrdinalIgnoreCase)
 1566                {
 01567                    return string.GetHashCodeOrdinalIgnoreCase(source);
 1568                }
 1569
 01570                ThrowCompareOptionsCheckFailed(options);
 1571
 1572                return -1; // make the compiler happy;
 1573            }
 1574        }
 1575
 1576        private int GetHashCodeOfStringCore(ReadOnlySpan<char> source, CompareOptions options) =>
 01577            GlobalizationMode.UseNls ?
 01578                NlsGetHashCodeOfString(source, options) :
 01579                IcuGetHashCodeOfString(source, options);
 1580
 01581        public override string ToString() => "CompareInfo - " + Name;
 1582
 1583        public SortVersion Version
 1584        {
 1585            get
 1586            {
 01587                if (m_SortVersion == null)
 1588                {
 01589                    if (GlobalizationMode.Invariant)
 1590                    {
 01591                        m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
 01592                                                                        (byte)(CultureInfo.LOCALE_INVARIANT >> 24),
 01593                                                                        (byte)((CultureInfo.LOCALE_INVARIANT & 0x00FF000
 01594                                                                        (byte)((CultureInfo.LOCALE_INVARIANT & 0x0000FF0
 01595                                                                        (byte)(CultureInfo.LOCALE_INVARIANT & 0xFF)));
 1596                    }
 1597                    else
 1598                    {
 1599#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 1600                if (GlobalizationMode.Hybrid)
 1601                {
 1602                    throw new PlatformNotSupportedException(GetPNSEText("SortVersion"));
 1603                }
 1604#endif
 01605                        m_SortVersion = GlobalizationMode.UseNls ? NlsGetSortVersion() : IcuGetSortVersion();
 1606                    }
 1607                }
 1608
 01609                return m_SortVersion;
 1610            }
 1611        }
 1612
 01613        public int LCID => CultureInfo.GetCultureInfo(Name).LCID;
 1614
 1615#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 1616        private static string GetPNSEText(string funcName) => SR.Format(SR.PlatformNotSupported_HybridGlobalization, fun
 1617        private static string GetPNSEWithReason(string funcName, string reason) => SR.Format(SR.PlatformNotSupportedWith
 1618#endif
 1619    }
 1620}
 1621

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Runtime.InteropServices;
 9using System.Text;
 10
 11namespace System.Globalization
 12{
 13    public partial class CompareInfo
 14    {
 15        private static class IcuSearchValues
 16        {
 17            // Characters which do not require special handling
 018            internal static readonly SearchValues<char> s_nonSpecialAsciiChars = CreateNonSpecialAsciiChars();
 19
 20            private static SearchValues<char> CreateNonSpecialAsciiChars()
 21            {
 022                ReadOnlySpan<bool> highCharTable = HighCharTable;
 023                Span<char> values = stackalloc char[highCharTable.Length];
 024                int valueIndex = 0;
 25
 026                for (int i = 0; i < highCharTable.Length; i++)
 27                {
 028                    if (!highCharTable[i])
 29                    {
 030                        values[valueIndex++] = (char)i;
 31                    }
 32                }
 33
 034                return SearchValues.Create(values.Slice(0, valueIndex));
 35            }
 36        }
 37
 38        [NonSerialized]
 39        private bool _isAsciiEqualityOrdinal;
 40
 41        private void IcuInitSortHandle(string interopCultureName)
 42        {
 043            _isAsciiEqualityOrdinal = GetIsAsciiEqualityOrdinal(interopCultureName);
 044            if (!GlobalizationMode.Invariant)
 45            {
 46#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 47                if (GlobalizationMode.Hybrid)
 48                    return;
 49#endif
 050                 _sortHandle = SortHandleCache.GetCachedSortHandle(interopCultureName);
 51            }
 052        }
 53
 54        private bool GetIsAsciiEqualityOrdinal(string interopCultureName)
 55        {
 056            if (GlobalizationMode.Invariant)
 057                return true;
 058            Debug.Assert(!GlobalizationMode.UseNls);
 059            Debug.Assert(interopCultureName != null);
 60
 61            // Inline the following condition to avoid potential implementation cycles within globalization
 62            //
 63            // _isAsciiEqualityOrdinal = _sortName == "" || _sortName == "en" || _sortName.StartsWith("en-", StringCompa
 64            //
 065            return _sortName.Length == 0 ||
 066                (_sortName.Length >= 2 && _sortName[0] == 'e' && _sortName[1] == 'n' && (_sortName.Length == 2 || _sortN
 67        }
 68
 69        private unsafe int IcuCompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions optio
 70        {
 071            Debug.Assert(!GlobalizationMode.Invariant);
 072            Debug.Assert(!GlobalizationMode.UseNls);
 073            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 74
 75            // GetReference may return nullptr if the input span is defaulted. The native layer handles
 76            // this appropriately; no workaround is needed on the managed side.
 77
 078            fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
 079            fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
 80            {
 081                return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Leng
 82            }
 83        }
 84
 85        private unsafe int IcuIndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, 
 86        {
 087            Debug.Assert(!GlobalizationMode.Invariant);
 088            Debug.Assert(!GlobalizationMode.UseNls);
 089            Debug.Assert(target.Length != 0);
 90
 091            if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
 92            {
 093                if ((options & CompareOptions.IgnoreCase) != 0)
 094                    return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr, fromBeginning);
 95                else
 096                    return IndexOfOrdinalHelper(source, target, options, matchLengthPtr, fromBeginning);
 97            }
 98            else
 099            {
 100#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 101                if (GlobalizationMode.Hybrid)
 102                    return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
 103#endif
 104                // GetReference may return nullptr if the input span is defaulted. The native layer handles
 105                // this appropriately; no workaround is needed on the managed side.
 0106                fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0107                fixed (char* pTarget = &MemoryMarshal.GetReference(target))
 108                {
 0109                    if (fromBeginning)
 0110                        return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length
 111                    else
 0112                        return Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource, source.Le
 113                }
 114            }
 115        }
 116
 117        /// <summary>
 118        /// Duplicate of IndexOfOrdinalHelper that also handles ignore case. Can't converge both methods
 119        /// as the JIT wouldn't be able to optimize the ignoreCase path away.
 120        /// </summary>
 121        /// <returns></returns>
 122        private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareO
 123        {
 0124            Debug.Assert(!GlobalizationMode.Invariant);
 125
 0126            Debug.Assert(!target.IsEmpty);
 0127            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 128
 0129            fixed (char* ap = &MemoryMarshal.GetReference(source))
 0130            fixed (char* bp = &MemoryMarshal.GetReference(target))
 131            {
 0132                char* a = ap;
 0133                char* b = bp;
 134
 0135                if (target.ContainsAnyExcept(IcuSearchValues.s_nonSpecialAsciiChars))
 136                {
 137                    goto InteropCall;
 138                }
 139
 0140                if (target.Length > source.Length)
 141                {
 0142                    if (source.ContainsAnyExcept(IcuSearchValues.s_nonSpecialAsciiChars))
 143                    {
 144                        goto InteropCall;
 145                    }
 146
 0147                    return -1;
 148                }
 149
 150                int startIndex, endIndex, jump;
 0151                if (fromBeginning)
 152                {
 153                    // Left to right, from zero to last possible index in the source string.
 154                    // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
 0155                    startIndex = 0;
 0156                    endIndex = source.Length - target.Length + 1;
 0157                    jump = 1;
 158                }
 159                else
 160                {
 161                    // Right to left, from first possible index in the source string to zero.
 162                    // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
 0163                    startIndex = source.Length - target.Length;
 0164                    endIndex = -1;
 0165                    jump = -1;
 166                }
 167
 0168                for (int i = startIndex; i != endIndex; i += jump)
 169                {
 0170                    int targetIndex = 0;
 0171                    int sourceIndex = i;
 172
 0173                    for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
 174                    {
 0175                        char valueChar = *(a + sourceIndex);
 0176                        char targetChar = *(b + targetIndex);
 177
 0178                        if (valueChar >= 0x80 || HighCharTable[valueChar])
 179                            goto InteropCall;
 180
 0181                        if (valueChar == targetChar)
 182                        {
 183                            continue;
 184                        }
 185
 186                        // uppercase both chars - notice that we need just one compare per char
 0187                        if (char.IsAsciiLetterLower(valueChar))
 0188                            valueChar = (char)(valueChar - 0x20);
 0189                        if (char.IsAsciiLetterLower(targetChar))
 0190                            targetChar = (char)(targetChar - 0x20);
 191
 0192                        if (valueChar == targetChar)
 193                        {
 194                            continue;
 195                        }
 196
 197                        // The match may be affected by special character. Verify that the following character is regula
 0198                        if (sourceIndex < source.Length - 1 && *(a + sourceIndex + 1) >= 0x80)
 0199                            goto InteropCall;
 200                        goto Next;
 201                    }
 202
 203                    // The match may be affected by special character. Verify that the following character is regular AS
 0204                    if (sourceIndex < source.Length && *(a + sourceIndex) >= 0x80)
 205                        goto InteropCall;
 0206                    if (matchLengthPtr != null)
 0207                        *matchLengthPtr = target.Length;
 0208                    return i;
 209
 210                Next: ;
 211                }
 212
 213                // Before we return -1, check if the remaining source contains any special or non-Ascii characters.
 0214                ReadOnlySpan<char> remainingSource = fromBeginning
 0215                    ? source.Slice(endIndex)
 0216                    : source.Slice(0, startIndex);
 217
 0218                if (remainingSource.ContainsAnyExcept(IcuSearchValues.s_nonSpecialAsciiChars))
 219                {
 220                    goto InteropCall;
 221                }
 222
 0223                return -1;
 224
 225            InteropCall:
 226#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 227                if (GlobalizationMode.Hybrid)
 228                    return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
 229#endif
 0230                if (fromBeginning)
 0231                    return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, match
 232                else
 0233                    return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options, m
 234            }
 235        }
 236
 237        private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions opt
 238        {
 0239            Debug.Assert(!GlobalizationMode.Invariant);
 240
 0241            Debug.Assert(!target.IsEmpty);
 0242            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 243
 0244            fixed (char* ap = &MemoryMarshal.GetReference(source))
 0245            fixed (char* bp = &MemoryMarshal.GetReference(target))
 246            {
 0247                char* a = ap;
 0248                char* b = bp;
 249
 0250                if (target.ContainsAnyExcept(IcuSearchValues.s_nonSpecialAsciiChars))
 251                {
 252                    goto InteropCall;
 253                }
 254
 0255                if (target.Length > source.Length)
 256                {
 0257                    if (source.ContainsAnyExcept(IcuSearchValues.s_nonSpecialAsciiChars))
 258                    {
 259                        goto InteropCall;
 260                    }
 261
 0262                    return -1;
 263                }
 264
 265                int startIndex, endIndex, jump;
 0266                if (fromBeginning)
 267                {
 268                    // Left to right, from zero to last possible index in the source string.
 269                    // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
 0270                    startIndex = 0;
 0271                    endIndex = source.Length - target.Length + 1;
 0272                    jump = 1;
 273                }
 274                else
 275                {
 276                    // Right to left, from first possible index in the source string to zero.
 277                    // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
 0278                    startIndex = source.Length - target.Length;
 0279                    endIndex = -1;
 0280                    jump = -1;
 281                }
 282
 0283                for (int i = startIndex; i != endIndex; i += jump)
 284                {
 0285                    int targetIndex = 0;
 0286                    int sourceIndex = i;
 287
 0288                    for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
 289                    {
 0290                        char valueChar = *(a + sourceIndex);
 0291                        char targetChar = *(b + targetIndex);
 292
 0293                        if (valueChar >= 0x80 || HighCharTable[valueChar])
 294                            goto InteropCall;
 295
 0296                        if (valueChar == targetChar)
 297                        {
 298                            continue;
 299                        }
 300
 301                        // The match may be affected by special character. Verify that the following character is regula
 0302                        if (sourceIndex < source.Length - 1 && *(a + sourceIndex + 1) >= 0x80)
 0303                            goto InteropCall;
 304                        goto Next;
 305                    }
 306
 307                    // The match may be affected by special character. Verify that the following character is regular AS
 0308                    if (sourceIndex < source.Length && *(a + sourceIndex) >= 0x80)
 309                        goto InteropCall;
 0310                    if (matchLengthPtr != null)
 0311                        *matchLengthPtr = target.Length;
 0312                    return i;
 313
 314                Next: ;
 315                }
 316
 0317                return -1;
 318
 319            InteropCall:
 320#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 321                if (GlobalizationMode.Hybrid)
 322                    return IndexOfCoreNative(target, source, options, fromBeginning, matchLengthPtr);
 323#endif
 0324                if (fromBeginning)
 0325                    return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, match
 326                else
 0327                    return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options, m
 328            }
 329        }
 330
 331        // this method sets '*matchLengthPtr' (if not nullptr) only on success
 332        private unsafe bool IcuStartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options, 
 333        {
 0334            Debug.Assert(!GlobalizationMode.Invariant);
 0335            Debug.Assert(!GlobalizationMode.UseNls);
 336
 0337            Debug.Assert(!prefix.IsEmpty);
 0338            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 339
 0340            if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
 341            {
 0342                if ((options & CompareOptions.IgnoreCase) != 0)
 0343                    return StartsWithOrdinalIgnoreCaseHelper(source, prefix, options, matchLengthPtr);
 344                else
 0345                    return StartsWithOrdinalHelper(source, prefix, options, matchLengthPtr);
 346            }
 347            else
 0348            {
 349#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 350                if (GlobalizationMode.Hybrid)
 351                    return NativeStartsWith(prefix, source, options);
 352#endif
 0353                fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be 
 0354                fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
 355                {
 0356                    return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length,
 357                }
 358            }
 359        }
 360
 361        private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, Comp
 362        {
 0363            Debug.Assert(!GlobalizationMode.Invariant);
 364
 0365            Debug.Assert(!prefix.IsEmpty);
 0366            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 367
 0368            int length = Math.Min(source.Length, prefix.Length);
 369
 0370            fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferen
 0371            fixed (char* bp = &MemoryMarshal.GetReference(prefix))
 372            {
 0373                char* a = ap;
 0374                char* b = bp;
 375
 0376                while (length != 0)
 377                {
 0378                    int charA = *a;
 0379                    int charB = *b;
 380
 0381                    if (charA >= 0x80 || charB >= 0x80 || HighCharTable[charA] || HighCharTable[charB])
 382                        goto InteropCall;
 383
 0384                    if (charA == charB)
 385                    {
 0386                        a++; b++;
 0387                        length--;
 0388                        continue;
 389                    }
 390
 391                    // uppercase both chars - notice that we need just one compare per char
 0392                    if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
 0393                    if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
 394
 0395                    if (charA == charB)
 396                    {
 0397                        a++; b++;
 0398                        length--;
 0399                        continue;
 400                    }
 401
 402                    // The match may be affected by special character. Verify that the following character is regular AS
 0403                    if (a < ap + source.Length - 1 && *(a + 1) >= 0x80)
 404                        goto InteropCall;
 0405                    if (b < bp + prefix.Length - 1 && *(b + 1) >= 0x80)
 406                        goto InteropCall;
 0407                    return false;
 408                }
 409
 410                // The match may be affected by special character. Verify that the following character is regular ASCII.
 411
 0412                if (source.Length < prefix.Length)
 413                {
 0414                    int charB = *b;
 415
 0416                    if (charB >= 0x80 || HighCharTable[charB])
 417                        goto InteropCall;
 0418                    return false;
 419                }
 420
 0421                if (source.Length > prefix.Length)
 422                {
 0423                    int charA = *a;
 0424                    if (charA >= 0x80  || HighCharTable[charA])
 425                        goto InteropCall;
 426                }
 427
 0428                if (matchLengthPtr != null)
 429                {
 0430                    *matchLengthPtr = prefix.Length; // non-linguistic match doesn't change UTF-16 length
 431                }
 0432                return true;
 433
 434            InteropCall:
 435#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 436                if (GlobalizationMode.Hybrid)
 437                    return NativeStartsWith(prefix, source, options);
 438#endif
 0439                return Interop.Globalization.StartsWith(_sortHandle, bp, prefix.Length, ap, source.Length, options, matc
 440            }
 441        }
 442
 443        private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions
 444        {
 0445            Debug.Assert(!GlobalizationMode.Invariant);
 446
 0447            Debug.Assert(!prefix.IsEmpty);
 0448            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 449
 0450            int length = Math.Min(source.Length, prefix.Length);
 451
 0452            fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferen
 0453            fixed (char* bp = &MemoryMarshal.GetReference(prefix))
 454            {
 0455                char* a = ap;
 0456                char* b = bp;
 457
 0458                while (length != 0)
 459                {
 0460                    int charA = *a;
 0461                    int charB = *b;
 462
 0463                    if (charA >= 0x80 || charB >= 0x80 || HighCharTable[charA] || HighCharTable[charB])
 464                        goto InteropCall;
 465
 0466                    if (charA == charB)
 467                    {
 0468                        a++; b++;
 0469                        length--;
 0470                        continue;
 471                    }
 472
 473                    // The match may be affected by special character. Verify that the following character is regular AS
 0474                    if (a < ap + source.Length - 1 && *(a + 1) >= 0x80)
 475                        goto InteropCall;
 0476                    if (b < bp + prefix.Length - 1 && *(b + 1) >= 0x80)
 477                        goto InteropCall;
 0478                    return false;
 479                }
 480
 481                // The match may be affected by special character. Verify that the following character is regular ASCII.
 482
 0483                if (source.Length < prefix.Length)
 484                {
 0485                    int charB = *b;
 486
 0487                    if (charB >= 0x80 || HighCharTable[charB])
 488                        goto InteropCall;
 0489                    return false;
 490                }
 491
 0492                if (source.Length > prefix.Length)
 493                {
 0494                    int charA = *a;
 495
 0496                    if (charA >= 0x80 || HighCharTable[charA])
 497                        goto InteropCall;
 498                }
 499
 0500                if (matchLengthPtr != null)
 501                {
 0502                    *matchLengthPtr = prefix.Length; // non-linguistic match doesn't change UTF-16 length
 503                }
 0504                return true;
 505
 506            InteropCall:
 507#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 508                if (GlobalizationMode.Hybrid)
 509                    return NativeStartsWith(prefix, source, options);
 510#endif
 0511                return Interop.Globalization.StartsWith(_sortHandle, bp, prefix.Length, ap, source.Length, options, matc
 512            }
 513        }
 514
 515        // this method sets '*matchLengthPtr' (if not nullptr) only on success
 516        private unsafe bool IcuEndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options, in
 517        {
 0518            Debug.Assert(!GlobalizationMode.Invariant);
 0519            Debug.Assert(!GlobalizationMode.UseNls);
 520
 0521            Debug.Assert(!suffix.IsEmpty);
 0522            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 523
 0524            if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
 525            {
 0526                if ((options & CompareOptions.IgnoreCase) != 0)
 0527                    return EndsWithOrdinalIgnoreCaseHelper(source, suffix, options, matchLengthPtr);
 528                else
 0529                    return EndsWithOrdinalHelper(source, suffix, options, matchLengthPtr);
 530            }
 531            else
 0532            {
 533#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 534                if (GlobalizationMode.Hybrid)
 535                    return NativeEndsWith(suffix, source, options);
 536#endif
 0537                fixed (char* pSource = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be 
 0538                fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix))
 539                {
 0540                    return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, o
 541                }
 542            }
 543        }
 544
 545        private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, Compar
 546        {
 0547            Debug.Assert(!GlobalizationMode.Invariant);
 548
 0549            Debug.Assert(!suffix.IsEmpty);
 0550            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 551
 0552            int length = Math.Min(source.Length, suffix.Length);
 553
 0554            fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferen
 0555            fixed (char* bp = &MemoryMarshal.GetReference(suffix))
 556            {
 0557                char* a = ap + source.Length - 1;
 0558                char* b = bp + suffix.Length - 1;
 559
 0560                while (length != 0)
 561                {
 0562                    int charA = *a;
 0563                    int charB = *b;
 564
 0565                    if (charA >= 0x80 || charB >= 0x80 || HighCharTable[charA] || HighCharTable[charB])
 566                        goto InteropCall;
 567
 0568                    if (charA == charB)
 569                    {
 0570                        a--; b--;
 0571                        length--;
 0572                        continue;
 573                    }
 574
 575                    // uppercase both chars - notice that we need just one compare per char
 0576                    if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
 0577                    if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
 578
 0579                    if (charA == charB)
 580                    {
 0581                        a--; b--;
 0582                        length--;
 0583                        continue;
 584                    }
 585
 586                    // The match may be affected by special character. Verify that the preceding character is regular AS
 0587                    if (a > ap && *(a - 1) >= 0x80)
 588                        goto InteropCall;
 0589                    if (b > bp && *(b - 1) >= 0x80)
 590                        goto InteropCall;
 0591                    return false;
 592                }
 593
 594                // The match may be affected by special character. Verify that the preceding character is regular ASCII.
 595
 0596                if (source.Length < suffix.Length)
 597                {
 0598                    int charB = *b;
 599
 0600                    if (charB >= 0x80 || HighCharTable[charB])
 601                        goto InteropCall;
 0602                    return false;
 603                }
 604
 0605                if (source.Length > suffix.Length)
 606                {
 0607                    int charA = *a;
 608
 0609                    if (charA >= 0x80 || HighCharTable[charA])
 610                        goto InteropCall;
 611                }
 612
 0613                if (matchLengthPtr != null)
 614                {
 0615                    *matchLengthPtr = suffix.Length; // non-linguistic match doesn't change UTF-16 length
 616                }
 0617                return true;
 618
 619            InteropCall:
 620#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 621                if (GlobalizationMode.Hybrid)
 622                    return NativeEndsWith(suffix, source, options);
 623#endif
 0624                return Interop.Globalization.EndsWith(_sortHandle, bp, suffix.Length, ap, source.Length, options, matchL
 625            }
 626        }
 627
 628        private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions o
 629        {
 0630            Debug.Assert(!GlobalizationMode.Invariant);
 631
 0632            Debug.Assert(!suffix.IsEmpty);
 0633            Debug.Assert(_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options));
 634
 0635            int length = Math.Min(source.Length, suffix.Length);
 636
 0637            fixed (char* ap = &MemoryMarshal.GetReference(source)) // could be null (or otherwise unable to be dereferen
 0638            fixed (char* bp = &MemoryMarshal.GetReference(suffix))
 639            {
 0640                char* a = ap + source.Length - 1;
 0641                char* b = bp + suffix.Length - 1;
 642
 0643                while (length != 0)
 644                {
 0645                    int charA = *a;
 0646                    int charB = *b;
 647
 0648                    if (charA >= 0x80 || charB >= 0x80 || HighCharTable[charA] || HighCharTable[charB])
 649                        goto InteropCall;
 650
 0651                    if (charA == charB)
 652                    {
 0653                        a--; b--;
 0654                        length--;
 0655                        continue;
 656                    }
 657
 658                    // The match may be affected by special character. Verify that the preceding character is regular AS
 0659                    if (a > ap && *(a - 1) >= 0x80)
 660                        goto InteropCall;
 0661                    if (b > bp && *(b - 1) >= 0x80)
 662                        goto InteropCall;
 0663                    return false;
 664                }
 665
 666                // The match may be affected by special character. Verify that the preceding character is regular ASCII.
 667
 0668                if (source.Length < suffix.Length)
 669                {
 0670                    int charB = *b;
 671
 0672                    if (charB >= 0x80 || HighCharTable[charB])
 673                        goto InteropCall;
 0674                    return false;
 675                }
 676
 0677                if (source.Length > suffix.Length)
 678                {
 0679                    int charA = *a;
 680
 0681                    if (charA >= 0x80 || HighCharTable[charA])
 682                        goto InteropCall;
 683                }
 684
 0685                if (matchLengthPtr != null)
 686                {
 0687                    *matchLengthPtr = suffix.Length; // non-linguistic match doesn't change UTF-16 length
 688                }
 0689                return true;
 690
 691            InteropCall:
 692#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 693                if (GlobalizationMode.Hybrid)
 694                    return NativeEndsWith(suffix, source, options);
 695#endif
 0696                return Interop.Globalization.EndsWith(_sortHandle, bp, suffix.Length, ap, source.Length, options, matchL
 697            }
 698        }
 699
 700        private unsafe SortKey IcuCreateSortKey(string source, CompareOptions options)
 701        {
 0702            ArgumentNullException.ThrowIfNull(source);
 703
 0704            Debug.Assert(!GlobalizationMode.Invariant);
 0705            Debug.Assert(!GlobalizationMode.UseNls);
 706
 0707            if ((options & ValidCompareMaskOffFlags) != 0)
 708            {
 0709                throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
 710            }
 711
 712#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 713            if (GlobalizationMode.Hybrid)
 714            {
 715                AssertComparisonSupported(options);
 716            }
 717#endif
 718
 719            byte[] keyData;
 0720            fixed (char* pSource = source)
 721            {
 722                int sortKeyLength;
 723#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 724                if (GlobalizationMode.Hybrid)
 725                {
 726                    sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length
 727                }
 728                else
 729#endif
 730                {
 0731                    sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, optio
 732                }
 0733                keyData = new byte[sortKeyLength];
 734
 0735                fixed (byte* pSortKey = keyData)
 736                {
 737#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 738                    if (GlobalizationMode.Hybrid)
 739                    {
 740                        if (Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, pSortK
 741                        {
 742                            throw new ArgumentException(SR.Arg_ExternalException);
 743                        }
 744                    }
 745                    else
 746#endif
 747                    {
 0748                        if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLengt
 749                        {
 0750                            throw new ArgumentException(SR.Arg_ExternalException);
 751                        }
 752                    }
 753                }
 754            }
 755
 0756            return new SortKey(this, source, options, keyData);
 757        }
 758
 759        private unsafe int IcuGetSortKey(ReadOnlySpan<char> source, Span<byte> destination, CompareOptions options)
 760        {
 0761            Debug.Assert(!GlobalizationMode.Invariant);
 0762            Debug.Assert(!GlobalizationMode.UseNls);
 0763            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 764
 765#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 766            if (GlobalizationMode.Hybrid)
 767            {
 768                AssertComparisonSupported(options);
 769            }
 770#endif
 771
 772            // It's ok to pass nullptr (for empty buffers) to ICU's sort key routines.
 773
 774            int actualSortKeyLength;
 775
 0776            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0777            fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
 778            {
 779#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 780                if (GlobalizationMode.Hybrid)
 781                {
 782                    actualSortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.
 783                }
 784                else
 785#endif
 786                {
 0787                    actualSortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pDest, d
 788                }
 789            }
 790
 791            // The check below also handles errors due to negative values / overflow being returned.
 792
 0793            if ((uint)actualSortKeyLength > (uint)destination.Length)
 794            {
 0795                if (actualSortKeyLength > destination.Length)
 796                {
 0797                    ThrowHelper.ThrowArgumentException_DestinationTooShort();
 798                }
 799                else
 800                {
 0801                    throw new ArgumentException(SR.Arg_ExternalException);
 802                }
 803            }
 804
 0805            return actualSortKeyLength;
 806        }
 807
 808        private unsafe int IcuGetSortKeyLength(ReadOnlySpan<char> source, CompareOptions options)
 809        {
 0810            Debug.Assert(!GlobalizationMode.Invariant);
 0811            Debug.Assert(!GlobalizationMode.UseNls);
 0812            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 813
 814#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 815            if (GlobalizationMode.Hybrid)
 816            {
 817                AssertComparisonSupported(options);
 818            }
 819#endif
 820
 821            // It's ok to pass nullptr (for empty buffers) to ICU's sort key routines.
 822
 0823            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 824            {
 825#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 826                if (GlobalizationMode.Hybrid)
 827                {
 828                    return Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Length, null, 0
 829                }
 830                else
 831#endif
 832                {
 0833                    return Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options);
 834                }
 835            }
 836        }
 837
 838        private static bool IcuIsSortable(ReadOnlySpan<char> text)
 839        {
 0840            Debug.Assert(!GlobalizationMode.Invariant);
 0841            Debug.Assert(!GlobalizationMode.UseNls);
 0842            Debug.Assert(!text.IsEmpty);
 843
 844            do
 845            {
 0846                if (Rune.DecodeFromUtf16(text, out Rune result, out int charsConsumed) != OperationStatus.Done)
 847                {
 0848                    return false; // found an unpaired surrogate somewhere in the text
 849                }
 850
 0851                UnicodeCategory category = Rune.GetUnicodeCategory(result);
 0852                if (category == UnicodeCategory.PrivateUse || category == UnicodeCategory.OtherNotAssigned)
 853                {
 0854                    return false; // can't sort private use or unassigned code points
 855                }
 856
 0857                text = text.Slice(charsConsumed);
 0858            } while (!text.IsEmpty);
 859
 0860            return true; // saw no unsortable data in the buffer
 861        }
 862
 863        private unsafe int IcuGetHashCodeOfString(ReadOnlySpan<char> source, CompareOptions options)
 864        {
 0865            Debug.Assert(!GlobalizationMode.Invariant);
 0866            Debug.Assert(!GlobalizationMode.UseNls);
 0867            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 868
 869#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 870            if (GlobalizationMode.Hybrid)
 871            {
 872                AssertComparisonSupported(options);
 873            }
 874#endif
 875
 876            // according to ICU User Guide the performance of ucol_getSortKey is worse when it is called with null outpu
 877            // the solution is to try to fill the sort key in a temporary buffer of size equal 4 x string length
 878            // (The ArrayPool used to have a limit on the length of buffers it would cache; this code was avoiding
 879            // exceeding that limit to avoid a per-operation allocation, and the performance implications here
 880            // were not re-evaluated when the limit was lifted.)
 0881            int sortKeyLength = checked((source.Length > 1024 * 1024 / 4) ? 0 : 4 * source.Length);
 882
 0883            byte[]? borrowedArray = null;
 0884            Span<byte> sortKey = (uint)sortKeyLength <= 1024
 0885                ? stackalloc byte[1024]
 0886                : (borrowedArray = ArrayPool<byte>.Shared.Rent(sortKeyLength));
 887
 0888            fixed (char* pSource = &MemoryMarshal.GetNonNullPinnableReference(source))
 0889            {
 0890                fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey))
 891                {
 892#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 893                    if (GlobalizationMode.Hybrid)
 894                    {
 895                        sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, source.Le
 896                    }
 897                    else
 898#endif
 899                    {
 0900                        sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, 
 901                    }
 902                }
 903
 0904                if (sortKeyLength > sortKey.Length) // slow path for big strings
 905                {
 0906                    if (borrowedArray != null)
 907                    {
 0908                        ArrayPool<byte>.Shared.Return(borrowedArray);
 909                    }
 910
 0911                    sortKey = (borrowedArray = ArrayPool<byte>.Shared.Rent(sortKeyLength));
 912
 0913                    fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey))
 914                    {
 915#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
 916                        if (GlobalizationMode.Hybrid)
 917                        {
 918                            sortKeyLength = Interop.Globalization.GetSortKeyNative(m_name, m_name.Length, pSource, sourc
 919                        }
 920                        else
 921#endif
 922                        {
 0923                            sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortK
 924                        }
 925                    }
 926                }
 927            }
 928
 0929            if (sortKeyLength == 0 || sortKeyLength > sortKey.Length) // internal error (0) or a bug (2nd call failed) i
 930            {
 0931                throw new ArgumentException(SR.Arg_ExternalException);
 932            }
 933
 0934            int hash = Marvin.ComputeHash32(sortKey.Slice(0, sortKeyLength), Marvin.DefaultSeed);
 935
 0936            if (borrowedArray != null)
 937            {
 0938                ArrayPool<byte>.Shared.Return(borrowedArray);
 939            }
 940
 0941            return hash;
 942        }
 943
 944        private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)
 945        {
 946            if ((options & CompareOptions.IgnoreCase) != 0)
 947            {
 948                return CompareOptions.OrdinalIgnoreCase;
 949            }
 950            else
 951            {
 952                return CompareOptions.Ordinal;
 953            }
 954        }
 955
 956        private static bool CanUseAsciiOrdinalForOptions(CompareOptions options)
 957        {
 958            // Unlike the other Ignore options, IgnoreSymbols impacts ASCII characters (e.g. ').
 0959            return (options & CompareOptions.IgnoreSymbols) == 0;
 960        }
 961
 962        private SortVersion IcuGetSortVersion()
 963        {
 0964            Debug.Assert(!GlobalizationMode.Invariant);
 0965            Debug.Assert(!GlobalizationMode.UseNls);
 966
 0967            int sortVersion = Interop.Globalization.GetSortVersion(_sortHandle);
 0968            return new SortVersion(sortVersion, LCID, new Guid(sortVersion, 0, 0, 0, 0, 0, 0,
 0969                                                             (byte) (LCID >> 24),
 0970                                                             (byte) ((LCID  & 0x00FF0000) >> 16),
 0971                                                             (byte) ((LCID  & 0x0000FF00) >> 8),
 0972                                                             (byte) (LCID  & 0xFF)));
 973        }
 974
 975        private static class SortHandleCache
 976        {
 977            // in most scenarios there is a limited number of cultures with limited number of sort options
 978            // so caching the sort handles and not freeing them is OK, see https://github.com/dotnet/coreclr/pull/25117 
 0979            private static readonly Dictionary<string, IntPtr> s_sortNameToSortHandleCache = new Dictionary<string, IntP
 980
 981            internal static IntPtr GetCachedSortHandle(string sortName)
 982            {
 0983                lock (s_sortNameToSortHandleCache)
 984                {
 0985                    if (!s_sortNameToSortHandleCache.TryGetValue(sortName, out IntPtr result))
 986                    {
 0987                        Interop.Globalization.ResultCode resultCode = Interop.Globalization.GetSortHandle(sortName, out 
 988
 0989                        if (resultCode == Interop.Globalization.ResultCode.OutOfMemory)
 0990                            throw new OutOfMemoryException();
 0991                        else if (resultCode != Interop.Globalization.ResultCode.Success)
 0992                            throw new ExternalException(SR.Arg_ExternalException);
 993
 994                        try
 995                        {
 0996                            s_sortNameToSortHandleCache.Add(sortName, result);
 0997                        }
 0998                        catch
 999                        {
 01000                            Interop.Globalization.CloseSortHandle(result);
 1001
 01002                            throw;
 1003                        }
 1004                    }
 1005
 01006                    return result;
 1007                }
 01008            }
 1009        }
 1010
 1011        private static ReadOnlySpan<bool> HighCharTable => // 0x80
 01012        [
 01013            true, /* 0x0, 0x0 */
 01014            true, /* 0x1, .*/
 01015            true, /* 0x2, .*/
 01016            true, /* 0x3, .*/
 01017            true, /* 0x4, .*/
 01018            true, /* 0x5, .*/
 01019            true, /* 0x6, .*/
 01020            true, /* 0x7, .*/
 01021            true, /* 0x8, .*/
 01022            false, /* 0x9,   */
 01023            true, /* 0xA,  */
 01024            false, /* 0xB, .*/
 01025            false, /* 0xC, .*/
 01026            true, /* 0xD,  */
 01027            true, /* 0xE, .*/
 01028            true, /* 0xF, .*/
 01029            true, /* 0x10, .*/
 01030            true, /* 0x11, .*/
 01031            true, /* 0x12, .*/
 01032            true, /* 0x13, .*/
 01033            true, /* 0x14, .*/
 01034            true, /* 0x15, .*/
 01035            true, /* 0x16, .*/
 01036            true, /* 0x17, .*/
 01037            true, /* 0x18, .*/
 01038            true, /* 0x19, .*/
 01039            true, /* 0x1A, */
 01040            true, /* 0x1B, .*/
 01041            true, /* 0x1C, .*/
 01042            true, /* 0x1D, .*/
 01043            true, /* 0x1E, .*/
 01044            true, /* 0x1F, .*/
 01045            false, /*0x20,  */
 01046            false, /*0x21, !*/
 01047            false, /*0x22, "*/
 01048            false, /*0x23,  #*/
 01049            false, /*0x24,  $*/
 01050            false, /*0x25,  %*/
 01051            false, /*0x26,  &*/
 01052            false,  /*0x27, '*/
 01053            false, /*0x28, (*/
 01054            false, /*0x29, )*/
 01055            false, /*0x2A **/
 01056            false, /*0x2B, +*/
 01057            false, /*0x2C, ,*/
 01058            false,  /*0x2D, -*/
 01059            false, /*0x2E, .*/
 01060            false, /*0x2F, /*/
 01061            false, /*0x30, 0*/
 01062            false, /*0x31, 1*/
 01063            false, /*0x32, 2*/
 01064            false, /*0x33, 3*/
 01065            false, /*0x34, 4*/
 01066            false, /*0x35, 5*/
 01067            false, /*0x36, 6*/
 01068            false, /*0x37, 7*/
 01069            false, /*0x38, 8*/
 01070            false, /*0x39, 9*/
 01071            false, /*0x3A, :*/
 01072            false, /*0x3B, ;*/
 01073            false, /*0x3C, <*/
 01074            false, /*0x3D, =*/
 01075            false, /*0x3E, >*/
 01076            false, /*0x3F, ?*/
 01077            false, /*0x40, @*/
 01078            false, /*0x41, A*/
 01079            false, /*0x42, B*/
 01080            false, /*0x43, C*/
 01081            false, /*0x44, D*/
 01082            false, /*0x45, E*/
 01083            false, /*0x46, F*/
 01084            false, /*0x47, G*/
 01085            false, /*0x48, H*/
 01086            false, /*0x49, I*/
 01087            false, /*0x4A, J*/
 01088            false, /*0x4B, K*/
 01089            false, /*0x4C, L*/
 01090            false, /*0x4D, M*/
 01091            false, /*0x4E, N*/
 01092            false, /*0x4F, O*/
 01093            false, /*0x50, P*/
 01094            false, /*0x51, Q*/
 01095            false, /*0x52, R*/
 01096            false, /*0x53, S*/
 01097            false, /*0x54, T*/
 01098            false, /*0x55, U*/
 01099            false, /*0x56, V*/
 01100            false, /*0x57, W*/
 01101            false, /*0x58, X*/
 01102            false, /*0x59, Y*/
 01103            false, /*0x5A, Z*/
 01104            false, /*0x5B, [*/
 01105            false, /*0x5C, \*/
 01106            false, /*0x5D, ]*/
 01107            false, /*0x5E, ^*/
 01108            false, /*0x5F, _*/
 01109            false, /*0x60, `*/
 01110            false, /*0x61, a*/
 01111            false, /*0x62, b*/
 01112            false, /*0x63, c*/
 01113            false, /*0x64, d*/
 01114            false, /*0x65, e*/
 01115            false, /*0x66, f*/
 01116            false, /*0x67, g*/
 01117            false, /*0x68, h*/
 01118            false, /*0x69, i*/
 01119            false, /*0x6A, j*/
 01120            false, /*0x6B, k*/
 01121            false, /*0x6C, l*/
 01122            false, /*0x6D, m*/
 01123            false, /*0x6E, n*/
 01124            false, /*0x6F, o*/
 01125            false, /*0x70, p*/
 01126            false, /*0x71, q*/
 01127            false, /*0x72, r*/
 01128            false, /*0x73, s*/
 01129            false, /*0x74, t*/
 01130            false, /*0x75, u*/
 01131            false, /*0x76, v*/
 01132            false, /*0x77, w*/
 01133            false, /*0x78, x*/
 01134            false, /*0x79, y*/
 01135            false, /*0x7A, z*/
 01136            false, /*0x7B, {*/
 01137            false, /*0x7C, |*/
 01138            false, /*0x7D, }*/
 01139            false, /*0x7E, ~*/
 01140            true, /*0x7F, */
 01141        ];
 1142    }
 1143}
 1144

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Invariant.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Runtime.InteropServices;
 7
 8namespace System.Globalization
 9{
 10    public partial class CompareInfo
 11    {
 12        private SortKey InvariantCreateSortKey(string source, CompareOptions options)
 13        {
 014            ArgumentNullException.ThrowIfNull(source);
 15
 016            if ((options & ValidCompareMaskOffFlags) != 0)
 17            {
 018                throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
 19            }
 20
 21            byte[] keyData;
 022            if (source.Length == 0)
 23            {
 024                keyData = [];
 25            }
 26            else
 27            {
 28                // In the invariant mode, all string comparisons are done as ordinal so when generating the sort keys we
 029                keyData = new byte[source.Length * sizeof(char)];
 30
 031                if ((options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0)
 32                {
 033                    InvariantCreateSortKeyOrdinalIgnoreCase(source, keyData);
 34                }
 35                else
 36                {
 037                    InvariantCreateSortKeyOrdinal(source, keyData);
 38                }
 39            }
 40
 041            return new SortKey(this, source, options, keyData);
 42        }
 43
 44        private static void InvariantCreateSortKeyOrdinal(ReadOnlySpan<char> source, Span<byte> sortKey)
 45        {
 046            Debug.Assert(sortKey.Length >= source.Length * sizeof(char));
 47
 048            for (int i = 0; i < source.Length; i++)
 49            {
 50                // convert machine-endian to big-endian
 051                BinaryPrimitives.WriteUInt16BigEndian(sortKey, (ushort)source[i]);
 052                sortKey = sortKey.Slice(sizeof(ushort));
 53            }
 054        }
 55
 56        private static void InvariantCreateSortKeyOrdinalIgnoreCase(ReadOnlySpan<char> source, Span<byte> sortKey)
 57        {
 058            Debug.Assert(sortKey.Length >= source.Length * sizeof(char));
 59
 060            for (int i = 0; i < source.Length; i++)
 61            {
 062                char c = source[i];
 063                if (char.IsHighSurrogate(c) && i < source.Length - 1)
 64                {
 065                    char cl = source[i + 1];
 066                    if (char.IsLowSurrogate(cl))
 67                    {
 068                        SurrogateCasing.ToUpper(c, cl, out char hr, out char lr);
 069                        Span<byte> tmp = sortKey.Slice(0, 2 * sizeof(ushort)); // help with bounds check elimination
 070                        BinaryPrimitives.WriteUInt16BigEndian(tmp, hr);
 071                        BinaryPrimitives.WriteUInt16BigEndian(tmp.Slice(sizeof(ushort)), lr);
 072                        sortKey = sortKey.Slice(2 * sizeof(ushort));
 073                        i++;
 074                        continue;
 75                    }
 76                }
 77
 78                // convert machine-endian to big-endian
 079                BinaryPrimitives.WriteUInt16BigEndian(sortKey, (ushort)InvariantModeCasing.ToUpper(c));
 080                sortKey = sortKey.Slice(sizeof(ushort));
 81            }
 082        }
 83
 84        private static int InvariantGetSortKey(ReadOnlySpan<char> source, Span<byte> destination, CompareOptions options
 85        {
 086            Debug.Assert(GlobalizationMode.Invariant);
 087            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 88
 89            // Make sure the destination buffer is large enough to hold the source projection.
 90            // Using unsigned arithmetic below also checks for buffer overflow since the incoming
 91            // length is always a non-negative signed integer.
 92
 093            if ((uint)destination.Length < (uint)source.Length * sizeof(char))
 94            {
 095                ThrowHelper.ThrowArgumentException_DestinationTooShort();
 96            }
 97
 098            if ((options & CompareOptions.IgnoreCase) == 0)
 99            {
 0100                InvariantCreateSortKeyOrdinal(source, destination);
 101            }
 102            else
 103            {
 0104                InvariantCreateSortKeyOrdinalIgnoreCase(source, destination);
 105            }
 106
 0107            return source.Length * sizeof(char);
 108        }
 109
 110        private static int InvariantGetSortKeyLength(ReadOnlySpan<char> source, CompareOptions options)
 111        {
 0112            Debug.Assert(GlobalizationMode.Invariant);
 0113            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 114
 115            // In invariant mode, sort keys are simply a byte projection of the source input,
 116            // optionally with casing modifications. We need to make sure we don't overflow
 117            // while computing the length.
 118
 0119            int byteLength = source.Length * sizeof(char);
 120
 0121            if (byteLength < 0)
 122            {
 0123                throw new ArgumentException(
 0124                    paramName: nameof(source),
 0125                    message: SR.ArgumentOutOfRange_GetByteCountOverflow);
 126            }
 127
 0128            return byteLength;
 129        }
 130
 131        private static int InvariantGetHashCode(ReadOnlySpan<char> source, CompareOptions options)
 132        {
 0133            if ((options & CompareOptions.IgnoreCase) == 0)
 134            {
 0135                return string.GetHashCode(source);
 136            }
 137
 0138            return string.GetHashCodeOrdinalIgnoreCase(source);
 139        }
 140    }
 141}
 142

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Nls.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Runtime.InteropServices;
 8
 9namespace System.Globalization
 10{
 11    public partial class CompareInfo
 12    {
 13        private void NlsInitSortHandle()
 14        {
 015            Debug.Assert(GlobalizationMode.UseNls);
 016            _sortHandle = NlsGetSortHandle(_sortName);
 017        }
 18
 19        internal static unsafe IntPtr NlsGetSortHandle(string cultureName)
 20        {
 021            if (GlobalizationMode.Invariant)
 22            {
 023                return IntPtr.Zero;
 24            }
 25
 26            IntPtr handle;
 027            int ret = Interop.Kernel32.LCMapStringEx(cultureName, Interop.Kernel32.LCMAP_SORTHANDLE, null, 0, &handle, I
 028            if (ret > 0)
 29            {
 30                // Even if we can get the sort handle, it is not guaranteed to work when Windows compatibility shim is a
 31                // e.g. Windows 7 compatibility mode. We need to ensure it is working before using it.
 32                // otherwise the whole framework app will not start.
 033                int hashValue = 0;
 034                char a = 'a';
 035                ret = Interop.Kernel32.LCMapStringEx(null, Interop.Kernel32.LCMAP_HASH, &a, 1, &hashValue, sizeof(int), 
 036                if (ret > 1)
 37                {
 038                    return handle;
 39                }
 40            }
 41
 042            return IntPtr.Zero;
 43        }
 44
 45        private static unsafe int FindStringOrdinal(
 46            uint dwFindStringOrdinalFlags,
 47            ReadOnlySpan<char> source,
 48            ReadOnlySpan<char> value,
 49            bool bIgnoreCase)
 50        {
 051            Debug.Assert(!GlobalizationMode.Invariant);
 052            Debug.Assert(!source.IsEmpty);
 053            Debug.Assert(!value.IsEmpty);
 54
 055            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 056            fixed (char* pValue = &MemoryMarshal.GetReference(value))
 57            {
 058                Debug.Assert(pSource != null);
 059                Debug.Assert(pValue != null);
 60
 061                int ret = Interop.Kernel32.FindStringOrdinal(
 062                            dwFindStringOrdinalFlags,
 063                            pSource,
 064                            source.Length,
 065                            pValue,
 066                            value.Length,
 067                            bIgnoreCase ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
 68
 069                Debug.Assert(ret >= -1 && ret <= source.Length);
 70
 71                // SetLastError is only performed under debug builds.
 072                Debug.Assert(ret >= 0 || Marshal.GetLastPInvokeError() == Interop.Errors.ERROR_SUCCESS);
 73
 074                return ret;
 75            }
 76        }
 77
 78        internal static int NlsIndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, 
 79        {
 080            Debug.Assert(!GlobalizationMode.Invariant);
 081            Debug.Assert(GlobalizationMode.UseNls);
 82
 083            Debug.Assert(source.Length != 0);
 084            Debug.Assert(value.Length != 0);
 85
 086            uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
 087            return FindStringOrdinal(positionFlag, source, value, ignoreCase);
 88        }
 89
 90        internal static int NlsLastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignor
 91        {
 92            Debug.Assert(!GlobalizationMode.Invariant);
 93            Debug.Assert(GlobalizationMode.UseNls);
 94
 95            Debug.Assert(source != null);
 96            Debug.Assert(value != null);
 97
 98            int offset = startIndex - count + 1;
 99            int result = FindStringOrdinal(FIND_FROMEND, source.AsSpan(offset, count), value, ignoreCase);
 100            if (result >= 0)
 101            {
 102                result += offset;
 103            }
 104            return result;
 105        }
 106
 107        private unsafe int NlsGetHashCodeOfString(ReadOnlySpan<char> source, CompareOptions options)
 108        {
 0109            Debug.Assert(!GlobalizationMode.Invariant);
 0110            Debug.Assert(GlobalizationMode.UseNls);
 0111            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 112
 113            // LCMapStringEx doesn't support passing cchSrc = 0, so if given a null or empty input
 114            // we'll normalize it to an empty null-terminated string and pass -1 to indicate that
 115            // the underlying OS function should read until it encounters the null terminator.
 116
 0117            int sourceLength = source.Length;
 0118            if (sourceLength == 0)
 119            {
 0120                source = string.Empty;
 0121                sourceLength = -1;
 122            }
 123
 0124            uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
 125
 0126            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 127            {
 0128                int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0129                                                  flags,
 0130                                                  pSource, sourceLength /* in chars */,
 0131                                                  null, 0,
 0132                                                  null, null, _sortHandle);
 0133                if (sortKeyLength == 0)
 134                {
 0135                    throw new ArgumentException(SR.Arg_ExternalException);
 136                }
 137
 138                // Note in calls to LCMapStringEx below, the input buffer is specified in wchars (and wchar count),
 139                // but the output buffer is specified in bytes (and byte count). This is because when generating
 140                // sort keys, LCMapStringEx treats the output buffer as containing opaque binary data.
 141                // See https://learn.microsoft.com/windows/desktop/api/winnls/nf-winnls-lcmapstringex.
 142
 0143                byte[]? borrowedArr = null;
 0144                Span<byte> span = (uint)sortKeyLength <= 512 ?
 0145                    stackalloc byte[512] :
 0146                    (borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
 147
 0148                fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
 149                {
 0150                    if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0151                                                      flags,
 0152                                                      pSource, sourceLength /* in chars */,
 0153                                                      pSortKey, sortKeyLength,
 0154                                                      null, null, _sortHandle) != sortKeyLength)
 155                    {
 0156                        throw new ArgumentException(SR.Arg_ExternalException);
 157                    }
 158                }
 159
 0160                int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
 161
 162                // Return the borrowed array if necessary.
 0163                if (borrowedArr != null)
 164                {
 0165                    ArrayPool<byte>.Shared.Return(borrowedArr);
 166                }
 167
 0168                return hash;
 169            }
 170        }
 171
 172        internal static unsafe int NlsCompareStringOrdinalIgnoreCase(ref char string1, int count1, ref char string2, int
 173        {
 0174            Debug.Assert(!GlobalizationMode.Invariant);
 0175            Debug.Assert(GlobalizationMode.UseNls);
 176
 0177            Debug.Assert(count1 > 0);
 0178            Debug.Assert(count2 > 0);
 179
 0180            fixed (char* char1 = &string1)
 0181            fixed (char* char2 = &string2)
 182            {
 0183                Debug.Assert(char1 != null);
 0184                Debug.Assert(char2 != null);
 185
 186                // Use the OS to compare and then convert the result to expected value by subtracting 2
 0187                int result = Interop.Kernel32.CompareStringOrdinal(char1, count1, char2, count2, bIgnoreCase: true);
 0188                if (result == 0)
 189                {
 0190                    throw new ArgumentException(SR.Arg_ExternalException);
 191                }
 0192                return result - 2;
 193            }
 194        }
 195
 196        private unsafe int NlsCompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions optio
 197        {
 0198            Debug.Assert(!GlobalizationMode.Invariant);
 0199            Debug.Assert(GlobalizationMode.UseNls);
 0200            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 201
 0202            string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
 203
 204            // CompareStringEx may try to dereference the first character of its input, even if an explicit
 205            // length of 0 is specified. To work around potential AVs we'll always ensure zero-length inputs
 206            // are normalized to a null-terminated empty string.
 207
 0208            if (string1.IsEmpty)
 209            {
 0210                string1 = string.Empty;
 211            }
 212
 0213            if (string2.IsEmpty)
 214            {
 0215                string2 = string.Empty;
 216            }
 217
 0218            fixed (char* pLocaleName = localeName)
 0219            fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
 0220            fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
 221            {
 0222                Debug.Assert(*pString1 >= 0); // assert that we can always dereference this
 0223                Debug.Assert(*pString2 >= 0); // assert that we can always dereference this
 224
 0225                int result = Interop.Kernel32.CompareStringEx(
 0226                                    pLocaleName,
 0227                                    (uint)GetNativeCompareFlags(options),
 0228                                    pString1,
 0229                                    string1.Length,
 0230                                    pString2,
 0231                                    string2.Length,
 0232                                    null,
 0233                                    null,
 0234                                    _sortHandle);
 235
 0236                if (result == 0)
 237                {
 0238                    throw new ArgumentException(SR.Arg_ExternalException);
 239                }
 240
 241                // Map CompareStringEx return value to -1, 0, 1.
 0242                return result - 2;
 243            }
 244        }
 245
 246        private unsafe int FindString(
 247                    uint dwFindNLSStringFlags,
 248                    ReadOnlySpan<char> lpStringSource,
 249                    ReadOnlySpan<char> lpStringValue,
 250                    int* pcchFound)
 251        {
 0252            Debug.Assert(!GlobalizationMode.Invariant);
 0253            Debug.Assert(!lpStringValue.IsEmpty);
 254
 0255            string? localeName = _sortHandle != IntPtr.Zero ? null : _sortName;
 256
 257            // FindNLSStringEx disallows passing an explicit 0 for cchSource or cchValue.
 258            // The caller should've already checked that 'lpStringValue' isn't empty,
 259            // but it's possible for 'lpStringSource' to be empty. In this case we'll
 260            // substitute an empty null-terminated string and pass -1 so that the NLS
 261            // function uses the implicit string length.
 262
 0263            int lpStringSourceLength = lpStringSource.Length;
 0264            if (lpStringSourceLength == 0)
 265            {
 0266                lpStringSource = string.Empty;
 0267                lpStringSourceLength = -1;
 268            }
 269
 0270            fixed (char* pLocaleName = localeName)
 0271            fixed (char* pSource = &MemoryMarshal.GetReference(lpStringSource))
 0272            fixed (char* pValue = &MemoryMarshal.GetReference(lpStringValue))
 273            {
 0274                Debug.Assert(pSource != null && pValue != null);
 275
 0276                int result = Interop.Kernel32.FindNLSStringEx(
 0277                                    pLocaleName,
 0278                                    dwFindNLSStringFlags,
 0279                                    pSource,
 0280                                    lpStringSourceLength,
 0281                                    pValue,
 0282                                    lpStringValue.Length,
 0283                                    pcchFound,
 0284                                    null,
 0285                                    null,
 0286                                    _sortHandle);
 287
 0288                Debug.Assert(result >= -1 && result <= lpStringSource.Length);
 289
 290                // SetLastError is only performed under debug builds.
 0291                Debug.Assert(result >= 0 || Marshal.GetLastPInvokeError() == Interop.Errors.ERROR_SUCCESS);
 292
 0293                return result;
 294            }
 295        }
 296
 297        private unsafe int NlsIndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, 
 298        {
 0299            Debug.Assert(!GlobalizationMode.Invariant);
 0300            Debug.Assert(GlobalizationMode.UseNls);
 301
 0302            Debug.Assert(target.Length != 0);
 303
 0304            uint positionFlag = fromBeginning ? (uint)FIND_FROMSTART : FIND_FROMEND;
 0305            return FindString(positionFlag | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
 306        }
 307
 308        private unsafe bool NlsStartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options, 
 309        {
 0310            Debug.Assert(!GlobalizationMode.Invariant);
 0311            Debug.Assert(GlobalizationMode.UseNls);
 312
 0313            Debug.Assert(!prefix.IsEmpty);
 0314            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 315
 0316            int idx = FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, prefix, matchLengthPtr)
 0317            if (idx >= 0)
 318            {
 0319                if (matchLengthPtr != null)
 320                {
 0321                    *matchLengthPtr += idx; // account for chars we skipped at the front of the string
 322                }
 0323                return true;
 324            }
 325
 0326            return false;
 327        }
 328
 329        private unsafe bool NlsEndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options, in
 330        {
 0331            Debug.Assert(!GlobalizationMode.Invariant);
 0332            Debug.Assert(GlobalizationMode.UseNls);
 333
 0334            Debug.Assert(!suffix.IsEmpty);
 0335            Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
 336
 0337            int idx = FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, suffix, pcchFound: null);
 0338            if (idx >= 0)
 339            {
 0340                if (matchLengthPtr != null)
 341                {
 0342                    *matchLengthPtr = source.Length - idx; // all chars from idx to the end of the string are consumed
 343                }
 0344                return true;
 345            }
 346
 0347            return false;
 348        }
 349
 350        private const uint LCMAP_SORTKEY = 0x00000400;
 351
 352        private const int FIND_STARTSWITH = 0x00100000;
 353        private const int FIND_ENDSWITH = 0x00200000;
 354        private const int FIND_FROMSTART = 0x00400000;
 355        private const int FIND_FROMEND = 0x00800000;
 356
 357        private unsafe SortKey NlsCreateSortKey(string source, CompareOptions options)
 358        {
 0359            ArgumentNullException.ThrowIfNull(source);
 360
 0361            Debug.Assert(!GlobalizationMode.Invariant);
 0362            Debug.Assert(GlobalizationMode.UseNls);
 363
 0364            if ((options & ValidCompareMaskOffFlags) != 0)
 365            {
 0366                throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
 367            }
 368
 369            byte[] keyData;
 0370            uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
 371
 372            // LCMapStringEx doesn't support passing cchSrc = 0, so if given an empty string
 373            // we'll instead pass -1 to indicate a null-terminated empty string.
 374
 0375            int sourceLength = source.Length;
 0376            if (sourceLength == 0)
 377            {
 0378                sourceLength = -1;
 379            }
 380
 0381            fixed (char* pSource = source)
 382            {
 0383                int sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0384                                            flags,
 0385                                            pSource, sourceLength,
 0386                                            null, 0,
 0387                                            null, null, _sortHandle);
 0388                if (sortKeyLength == 0)
 389                {
 0390                    throw new ArgumentException(SR.Arg_ExternalException);
 391                }
 392
 0393                keyData = new byte[sortKeyLength];
 394
 0395                fixed (byte* pBytes = keyData)
 396                {
 0397                    if (Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0398                                            flags,
 0399                                            pSource, sourceLength,
 0400                                            pBytes, keyData.Length,
 0401                                            null, null, _sortHandle) != sortKeyLength)
 402                    {
 0403                        throw new ArgumentException(SR.Arg_ExternalException);
 404                    }
 405                }
 406            }
 407
 0408            return new SortKey(this, source, options, keyData);
 409        }
 410
 411        private unsafe int NlsGetSortKey(ReadOnlySpan<char> source, Span<byte> destination, CompareOptions options)
 412        {
 0413            Debug.Assert(!GlobalizationMode.Invariant);
 0414            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 415
 416            // LCMapStringEx doesn't allow cchDest = 0 unless we're trying to query
 417            // the total number of bytes necessary.
 418
 0419            if (destination.IsEmpty)
 420            {
 0421                ThrowHelper.ThrowArgumentException_DestinationTooShort();
 422            }
 423
 0424            uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
 425
 426            // LCMapStringEx doesn't support passing cchSrc = 0, so if given an empty span
 427            // we'll instead normalize to a null-terminated empty string and pass -1 as
 428            // the length to indicate that the implicit null terminator should be used.
 429
 0430            int sourceLength = source.Length;
 0431            if (sourceLength == 0)
 432            {
 0433                source = string.Empty;
 0434                sourceLength = -1;
 435            }
 436
 437            int actualSortKeyLength;
 438
 0439            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0440            fixed (byte* pSortKey = &MemoryMarshal.GetReference(destination))
 441            {
 0442                Debug.Assert(pSource != null);
 0443                Debug.Assert(pSortKey != null);
 444
 0445                actualSortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0446                                                                     flags,
 0447                                                                     pSource, sourceLength,
 0448                                                                     pSortKey, destination.Length,
 0449                                                                     null, null, _sortHandle);
 450            }
 451
 0452            if (actualSortKeyLength <= 0)
 453            {
 0454                Debug.Assert(actualSortKeyLength == 0, "LCMapStringEx should never return a negative value.");
 455
 456                // This could fail for a variety of reasons, including NLS being unable
 457                // to allocate a temporary buffer large enough to hold intermediate state,
 458                // or the destination buffer being too small.
 459
 0460                if (Marshal.GetLastPInvokeError() == Interop.Errors.ERROR_INSUFFICIENT_BUFFER)
 461                {
 0462                    ThrowHelper.ThrowArgumentException_DestinationTooShort();
 463                }
 464                else
 465                {
 0466                    throw new ArgumentException(SR.Arg_ExternalException);
 467                }
 468            }
 469
 0470            Debug.Assert(actualSortKeyLength <= destination.Length);
 0471            return actualSortKeyLength;
 472        }
 473
 474        private unsafe int NlsGetSortKeyLength(ReadOnlySpan<char> source, CompareOptions options)
 475        {
 0476            Debug.Assert(!GlobalizationMode.Invariant);
 0477            Debug.Assert((options & ValidCompareMaskOffFlags) == 0);
 478
 0479            uint flags = LCMAP_SORTKEY | (uint)GetNativeCompareFlags(options);
 480
 481            // LCMapStringEx doesn't support passing cchSrc = 0, so if given an empty span
 482            // we'll instead normalize to a null-terminated empty string and pass -1 as
 483            // the length to indicate that the implicit null terminator should be used.
 484
 0485            int sourceLength = source.Length;
 0486            if (sourceLength == 0)
 487            {
 0488                source = string.Empty;
 0489                sourceLength = -1;
 490            }
 491
 492            int sortKeyLength;
 493
 0494            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 495            {
 0496                Debug.Assert(pSource != null);
 0497                sortKeyLength = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _sortName,
 0498                                                               flags,
 0499                                                               pSource, sourceLength,
 0500                                                               null, 0,
 0501                                                               null, null, _sortHandle);
 502            }
 503
 0504            if (sortKeyLength <= 0)
 505            {
 0506                Debug.Assert(sortKeyLength == 0, "LCMapStringEx should never return a negative value.");
 507
 508                // This could fail for a variety of reasons, including NLS being unable
 509                // to allocate a temporary buffer large enough to hold intermediate state.
 510
 0511                throw new ArgumentException(SR.Arg_ExternalException);
 512            }
 513
 0514            return sortKeyLength;
 515        }
 516
 517        private static unsafe bool NlsIsSortable(ReadOnlySpan<char> text)
 518        {
 0519            Debug.Assert(!GlobalizationMode.Invariant);
 0520            Debug.Assert(GlobalizationMode.UseNls);
 0521            Debug.Assert(!text.IsEmpty);
 522
 0523            fixed (char* pText = &MemoryMarshal.GetReference(text))
 524            {
 0525                return Interop.Kernel32.IsNLSDefinedString(Interop.Kernel32.COMPARE_STRING, 0, IntPtr.Zero, pText, text.
 526            }
 527        }
 528
 529        private const int COMPARE_OPTIONS_ORDINAL = 0x40000000;       // Ordinal
 530        private const int NORM_IGNORECASE = 0x00000001;       // Ignores case.  (use LINGUISTIC_IGNORECASE instead)
 531        private const int NORM_IGNOREKANATYPE = 0x00010000;       // Does not differentiate between Hiragana and Katakan
 532        private const int NORM_IGNORENONSPACE = 0x00000002;       // Ignores nonspacing. This flag also removes Japanese
 533        private const int NORM_IGNORESYMBOLS = 0x00000004;       // Ignores symbols.
 534        private const int NORM_IGNOREWIDTH = 0x00020000;       // Does not differentiate between a single-byte character
 535        private const int NORM_LINGUISTIC_CASING = 0x08000000;       // use linguistic rules for casing
 536        private const int SORT_STRINGSORT = 0x00001000;       // Treats punctuation the same as symbols.
 537        private const int SORT_DIGITSASNUMBERS = 0x00000008;       // Treat digits as numbers during sorting, for exampl
 538
 539        private static int GetNativeCompareFlags(CompareOptions options)
 540        {
 541            // Use "linguistic casing" by default (load the culture's casing exception tables)
 0542            int nativeCompareFlags = NORM_LINGUISTIC_CASING;
 543
 0544            if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; }
 0545            if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; }
 0546            if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; }
 0547            if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; }
 0548            if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; }
 0549            if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; }
 0550            if ((options & CompareOptions.NumericOrdering) != 0) { nativeCompareFlags |= SORT_DIGITSASNUMBERS; }
 551
 552            // TODO: Can we try for GetNativeCompareFlags to never
 553            // take Ordinal or OrdinalIgnoreCase.  This value is not part of Win32, we just handle it special
 554            // in some places.
 555            // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag
 0556            if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; }
 557
 0558            Debug.Assert(((options & ~(CompareOptions.IgnoreCase |
 0559                                          CompareOptions.IgnoreKanaType |
 0560                                          CompareOptions.IgnoreNonSpace |
 0561                                          CompareOptions.IgnoreSymbols |
 0562                                          CompareOptions.IgnoreWidth |
 0563                                          CompareOptions.NumericOrdering |
 0564                                          CompareOptions.StringSort)) == 0) ||
 0565                             (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags
 566
 0567            return nativeCompareFlags;
 568        }
 569
 570        private unsafe SortVersion NlsGetSortVersion()
 571        {
 0572            Debug.Assert(!GlobalizationMode.Invariant);
 0573            Debug.Assert(GlobalizationMode.UseNls);
 574
 0575            Interop.Kernel32.NlsVersionInfoEx nlsVersion = default;
 0576            nlsVersion.dwNLSVersionInfoSize = sizeof(Interop.Kernel32.NlsVersionInfoEx);
 0577            Interop.Kernel32.GetNLSVersionEx(Interop.Kernel32.COMPARE_STRING, _sortName, &nlsVersion);
 0578            return new SortVersion(
 0579                        nlsVersion.dwNLSVersion,
 0580                        nlsVersion.dwEffectiveId == 0 ? LCID : nlsVersion.dwEffectiveId,
 0581                        nlsVersion.guidCustomVersion);
 582        }
 583    }
 584}
 585

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Text;
 6using System.Text.Unicode;
 7
 8namespace System.Globalization
 9{
 10    public partial class CompareInfo
 11    {
 12        /// <summary>
 13        /// Determines whether a UTF-8 string starts with a specific prefix.
 14        /// </summary>
 15        /// <param name="source">The UTF-8 string to search within.</param>
 16        /// <param name="prefix">The prefix to attempt to match at the start of <paramref name="source"/>.</param>
 17        /// <param name="options">The <see cref="CompareOptions"/> to use during the match.</param>
 18        /// <returns>
 19        /// <see langword="true"/> if <paramref name="prefix"/> occurs at the start of <paramref name="source"/>;
 20        /// otherwise, <see langword="false"/>.
 21        /// </returns>
 22        /// <exception cref="ArgumentException">
 23        /// <paramref name="options"/> contains an unsupported combination of flags.
 24        /// </exception>
 25        internal bool IsPrefixUtf8(ReadOnlySpan<byte> source, ReadOnlySpan<byte> prefix, CompareOptions options = Compar
 26        {
 27            // The empty UTF-8 string is trivially a prefix of every other string. For compat with
 28            // earlier versions of the Framework we'll early-exit here before validating the
 29            // 'options' argument.
 30
 031            if (prefix.IsEmpty)
 32            {
 033                return true;
 34            }
 35
 036            if ((options & ValidIndexMaskOffFlags) == 0)
 37            {
 38                // Common case: caller is attempting to perform a linguistic search.
 39                // Pass the flags down to NLS or ICU unless we're running in invariant
 40                // mode, at which point we normalize the flags to Ordinal[IgnoreCase].
 41
 042                if (!GlobalizationMode.Invariant)
 43                {
 044                    return StartsWithCoreUtf8(source, prefix, options);
 45                }
 46
 047                if ((options & CompareOptions.IgnoreCase) == 0)
 48                {
 049                    return source.StartsWith(prefix);
 50                }
 51
 052                return source.StartsWithOrdinalIgnoreCaseUtf8(prefix);
 53            }
 54            else
 55            {
 56                // Less common case: caller is attempting to perform non-linguistic comparison,
 57                // or an invalid combination of flags was supplied.
 58
 059                if (options == CompareOptions.Ordinal)
 60                {
 061                    return source.StartsWith(prefix);
 62                }
 63
 064                if (options == CompareOptions.OrdinalIgnoreCase)
 65                {
 066                    return source.StartsWithOrdinalIgnoreCaseUtf8(prefix);
 67                }
 68
 069                ThrowCompareOptionsCheckFailed(options);
 70
 71                return false; // make the compiler happy;
 72            }
 73        }
 74
 75        private unsafe bool StartsWithCoreUtf8(ReadOnlySpan<byte> source, ReadOnlySpan<byte> prefix, CompareOptions opti
 76        {
 77            // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way
 78
 79            // Convert source using stackalloc for <= 256 characters and ArrayPool otherwise
 80
 81            char[]? sourceUtf16Array;
 82            scoped Span<char> sourceUtf16;
 083            int sourceMaxCharCount = Encoding.UTF8.GetMaxCharCount(source.Length);
 84
 085            if ((uint)sourceMaxCharCount <= 256)
 86            {
 087                sourceUtf16Array = null;
 088                sourceUtf16 = stackalloc char[256];
 89            }
 90            else
 91            {
 092                sourceUtf16Array = ArrayPool<char>.Shared.Rent(sourceMaxCharCount);
 093                sourceUtf16 = sourceUtf16Array.AsSpan(0, sourceMaxCharCount);
 94            }
 95
 096            OperationStatus sourceStatus = Utf8.ToUtf16PreservingReplacement(source, sourceUtf16, out _, out int sourceU
 97
 098            if (sourceStatus != OperationStatus.Done)
 99            {
 0100                if (sourceUtf16Array != null)
 101                {
 102                    // Return rented buffers if necessary
 0103                    ArrayPool<char>.Shared.Return(sourceUtf16Array);
 104                }
 105
 0106                return false;
 107            }
 0108            sourceUtf16 = sourceUtf16.Slice(0, sourceUtf16Length);
 109
 110            // Convert prefix using stackalloc for <= 256 characters and ArrayPool otherwise
 111
 112            char[]? prefixUtf16Array;
 113            scoped Span<char> prefixUtf16;
 0114            int prefixMaxCharCount = Encoding.UTF8.GetMaxCharCount(prefix.Length);
 115
 0116            if ((uint)prefixMaxCharCount < 256)
 117            {
 0118                prefixUtf16Array = null;
 0119                prefixUtf16 = stackalloc char[256];
 120            }
 121            else
 122            {
 0123                prefixUtf16Array = ArrayPool<char>.Shared.Rent(prefixMaxCharCount);
 0124                prefixUtf16 = prefixUtf16Array.AsSpan(0, prefixMaxCharCount);
 125            }
 126
 0127            OperationStatus prefixStatus = Utf8.ToUtf16PreservingReplacement(prefix, prefixUtf16, out _, out int prefixU
 128
 0129            if (prefixStatus != OperationStatus.Done)
 130            {
 131                // Return rented buffers if necessary
 132
 0133                if (prefixUtf16Array != null)
 134                {
 0135                    ArrayPool<char>.Shared.Return(prefixUtf16Array);
 136                }
 137
 0138                if (sourceUtf16Array != null)
 139                {
 0140                    ArrayPool<char>.Shared.Return(sourceUtf16Array);
 141                }
 142
 0143                return false;
 144            }
 0145            prefixUtf16 = prefixUtf16.Slice(0, prefixUtf16Length);
 146
 147            // Actual operation
 148
 0149            bool result = StartsWithCore(sourceUtf16, prefixUtf16, options, matchLengthPtr: null);
 150
 151            // Return rented buffers if necessary
 152
 0153            if (prefixUtf16Array != null)
 154            {
 0155                ArrayPool<char>.Shared.Return(prefixUtf16Array);
 156            }
 157
 0158            if (sourceUtf16Array != null)
 159            {
 0160                ArrayPool<char>.Shared.Return(sourceUtf16Array);
 161            }
 162
 0163            return result;
 164        }
 165    }
 166}
 167

Methods/Properties

.cctor()
.ctor(System.Globalization.CultureInfo)
GetCompareInfo(System.Int32,System.Reflection.Assembly)
GetCompareInfo(System.String,System.Reflection.Assembly)
GetCompareInfo(System.Int32)
GetCompareInfo(System.String)
IsSortable(System.Char)
IsSortable(System.String)
IsSortable(System.ReadOnlySpan`1<System.Char>)
IsSortable(System.Text.Rune)
InitSort(System.Globalization.CultureInfo)
OnDeserializing(System.Runtime.Serialization.StreamingContext)
System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(System.Object)
OnDeserialized(System.Runtime.Serialization.StreamingContext)
OnDeserialized()
OnSerializing(System.Runtime.Serialization.StreamingContext)
Name()
Compare(System.String,System.String)
Compare(System.String,System.String,System.Globalization.CompareOptions)
CompareOptionIgnoreCase(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
Compare(System.String,System.Int32,System.Int32,System.String,System.Int32,System.Int32)
Compare(System.String,System.Int32,System.String,System.Int32,System.Globalization.CompareOptions)
Compare(System.String,System.Int32,System.String,System.Int32)
Compare(System.String,System.Int32,System.Int32,System.String,System.Int32,System.Int32,System.Globalization.CompareOptions)
Compare(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
CheckCompareOptionsForCompare(System.Globalization.CompareOptions)
ThrowCompareOptionsCheckFailed(System.Globalization.CompareOptions)
CompareStringCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IsPrefix(System.String,System.String,System.Globalization.CompareOptions)
IsPrefix(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IsPrefix(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32&)
StartsWithCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
IsPrefix(System.String,System.String)
IsSuffix(System.String,System.String,System.Globalization.CompareOptions)
IsSuffix(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IsSuffix(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32&)
IsSuffix(System.String,System.String)
EndsWithCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
IndexOf(System.String,System.Char)
IndexOf(System.String,System.String)
IndexOf(System.String,System.Char,System.Globalization.CompareOptions)
IndexOf(System.String,System.String,System.Globalization.CompareOptions)
IndexOf(System.String,System.Char,System.Int32)
IndexOf(System.String,System.String,System.Int32)
IndexOf(System.String,System.Char,System.Int32,System.Globalization.CompareOptions)
IndexOf(System.String,System.String,System.Int32,System.Globalization.CompareOptions)
IndexOf(System.String,System.Char,System.Int32,System.Int32)
IndexOf(System.String,System.String,System.Int32,System.Int32)
IndexOf(System.String,System.Char,System.Int32,System.Int32,System.Globalization.CompareOptions)
IndexOf(System.String,System.String,System.Int32,System.Int32,System.Globalization.CompareOptions)
IndexOf(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IndexOf(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32&)
IndexOf(System.ReadOnlySpan`1<System.Char>,System.Text.Rune,System.Globalization.CompareOptions)
IndexOf(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Int32*,System.Globalization.CompareOptions,System.Boolean)
IndexOfCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*,System.Boolean)
LastIndexOf(System.String,System.Char)
LastIndexOf(System.String,System.String)
LastIndexOf(System.String,System.Char,System.Globalization.CompareOptions)
LastIndexOf(System.String,System.String,System.Globalization.CompareOptions)
LastIndexOf(System.String,System.Char,System.Int32)
LastIndexOf(System.String,System.String,System.Int32)
LastIndexOf(System.String,System.Char,System.Int32,System.Globalization.CompareOptions)
LastIndexOf(System.String,System.String,System.Int32,System.Globalization.CompareOptions)
LastIndexOf(System.String,System.Char,System.Int32,System.Int32)
LastIndexOf(System.String,System.String,System.Int32,System.Int32)
LastIndexOf(System.String,System.Char,System.Int32,System.Int32,System.Globalization.CompareOptions)
LastIndexOf(System.String,System.String,System.Int32,System.Int32,System.Globalization.CompareOptions)
LastIndexOf(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
LastIndexOf(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32&)
LastIndexOf(System.ReadOnlySpan`1<System.Char>,System.Text.Rune,System.Globalization.CompareOptions)
GetSortKey(System.String,System.Globalization.CompareOptions)
GetSortKey(System.String)
CreateSortKeyCore(System.String,System.Globalization.CompareOptions)
GetSortKey(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Globalization.CompareOptions)
GetSortKeyCore(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Globalization.CompareOptions)
GetSortKeyLength(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
GetSortKeyLengthCore(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
Equals(System.Object)
GetHashCode()
GetHashCode(System.String,System.Globalization.CompareOptions)
GetHashCode(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
GetHashCodeOfStringCore(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
ToString()
Version()
LCID()
.cctor()
CreateNonSpecialAsciiChars()
IcuInitSortHandle(System.String)
GetIsAsciiEqualityOrdinal(System.String)
IcuCompareString(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IcuIndexOfCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*,System.Boolean)
IndexOfOrdinalIgnoreCaseHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*,System.Boolean)
IndexOfOrdinalHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*,System.Boolean)
IcuStartsWith(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
StartsWithOrdinalIgnoreCaseHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
StartsWithOrdinalHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
IcuEndsWith(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
EndsWithOrdinalIgnoreCaseHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
EndsWithOrdinalHelper(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
IcuCreateSortKey(System.String,System.Globalization.CompareOptions)
IcuGetSortKey(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Globalization.CompareOptions)
IcuGetSortKeyLength(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
IcuIsSortable(System.ReadOnlySpan`1<System.Char>)
IcuGetHashCodeOfString(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
CanUseAsciiOrdinalForOptions(System.Globalization.CompareOptions)
IcuGetSortVersion()
.cctor()
GetCachedSortHandle(System.String)
HighCharTable()
InvariantCreateSortKey(System.String,System.Globalization.CompareOptions)
InvariantCreateSortKeyOrdinal(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>)
InvariantCreateSortKeyOrdinalIgnoreCase(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>)
InvariantGetSortKey(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Globalization.CompareOptions)
InvariantGetSortKeyLength(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
InvariantGetHashCode(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
NlsInitSortHandle()
NlsGetSortHandle(System.String)
FindStringOrdinal(System.UInt32,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Boolean)
NlsIndexOfOrdinalCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Boolean,System.Boolean)
NlsGetHashCodeOfString(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
NlsCompareStringOrdinalIgnoreCase(System.Char&,System.Int32,System.Char&,System.Int32)
NlsCompareString(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
FindString(System.UInt32,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Int32*)
NlsIndexOfCore(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*,System.Boolean)
NlsStartsWith(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
NlsEndsWith(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions,System.Int32*)
NlsCreateSortKey(System.String,System.Globalization.CompareOptions)
NlsGetSortKey(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Globalization.CompareOptions)
NlsGetSortKeyLength(System.ReadOnlySpan`1<System.Char>,System.Globalization.CompareOptions)
NlsIsSortable(System.ReadOnlySpan`1<System.Char>)
GetNativeCompareFlags(System.Globalization.CompareOptions)
NlsGetSortVersion()
IsPrefixUtf8(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>,System.Globalization.CompareOptions)
StartsWithCoreUtf8(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>,System.Globalization.CompareOptions)