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 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.CompilerServices;
8 
9 using Internal.Runtime.CompilerServices;
10 
11 namespace System
12 {
13     public partial class String
14     {
15         //
16         //Native Static Methods
17         //
18 
FastCompareStringHelper(uint* strAChars, int countA, uint* strBChars, int countB)19         private static unsafe int FastCompareStringHelper(uint* strAChars, int countA, uint* strBChars, int countB)
20         {
21             int count = (countA < countB) ? countA : countB;
22 
23 #if BIT64
24             long diff = (long)((byte*)strAChars - (byte*)strBChars);
25 #else
26             int diff = (int)((byte*)strAChars - (byte*)strBChars);
27 #endif
28 
29 #if BIT64
30             int alignmentA = (int)((long)strAChars) & (sizeof(IntPtr) - 1);
31             int alignmentB = (int)((long)strBChars) & (sizeof(IntPtr) - 1);
32 
33             if (alignmentA == alignmentB)
34             {
35                 if ((alignmentA == 2 || alignmentA == 6) && (count >= 1))
36                 {
37                     char* ptr2 = (char*)strBChars;
38 
39                     if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0)
40                         return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2);
41 
42                     strBChars = (uint*)(++ptr2);
43                     count -= 1;
44                     alignmentA = (alignmentA == 2 ? 4 : 0);
45                 }
46 
47                 if ((alignmentA == 4) && (count >= 2))
48                 {
49                     uint* ptr2 = (uint*)strBChars;
50 
51                     if ((*((uint*)((byte*)ptr2 + diff)) - *ptr2) != 0)
52                     {
53                         char* chkptr1 = (char*)((byte*)strBChars + diff);
54                         char* chkptr2 = (char*)strBChars;
55 
56                         if (*chkptr1 != *chkptr2)
57                             return ((int)*chkptr1 - (int)*chkptr2);
58                         return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1));
59                     }
60                     strBChars = ++ptr2;
61                     count -= 2;
62                     alignmentA = 0;
63                 }
64 
65                 if ((alignmentA == 0))
66                 {
67                     while (count >= 4)
68                     {
69                         long* ptr2 = (long*)strBChars;
70 
71                         if ((*((long*)((byte*)ptr2 + diff)) - *ptr2) != 0)
72                         {
73                             if ((*((uint*)((byte*)ptr2 + diff)) - *(uint*)ptr2) != 0)
74                             {
75                                 char* chkptr1 = (char*)((byte*)strBChars + diff);
76                                 char* chkptr2 = (char*)strBChars;
77 
78                                 if (*chkptr1 != *chkptr2)
79                                     return ((int)*chkptr1 - (int)*chkptr2);
80                                 return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1));
81                             }
82                             else
83                             {
84                                 char* chkptr1 = (char*)((uint*)((byte*)strBChars + diff) + 1);
85                                 char* chkptr2 = (char*)((uint*)strBChars + 1);
86 
87                                 if (*chkptr1 != *chkptr2)
88                                     return ((int)*chkptr1 - (int)*chkptr2);
89                                 return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1));
90                             }
91                         }
92                         strBChars = (uint*)(++ptr2);
93                         count -= 4;
94                     }
95                 }
96 
97                 {
98                     char* ptr2 = (char*)strBChars;
99                     while ((count -= 1) >= 0)
100                     {
101                         if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0)
102                             return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2);
103                         ++ptr2;
104                     }
105                 }
106             }
107             else
108 #endif // BIT64
109             {
110 #if BIT64
111                 if (Math.Abs(alignmentA - alignmentB) == 4)
112                 {
113                     if ((alignmentA == 2) || (alignmentB == 2))
114                     {
115                         char* ptr2 = (char*)strBChars;
116 
117                         if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0)
118                             return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2);
119                         strBChars = (uint*)(++ptr2);
120                         count -= 1;
121                     }
122                 }
123 #endif // BIT64
124 
125                 // Loop comparing a DWORD at a time.
126                 // Reads are potentially unaligned
127                 while ((count -= 2) >= 0)
128                 {
129                     if ((*((uint*)((byte*)strBChars + diff)) - *strBChars) != 0)
130                     {
131                         char* ptr1 = (char*)((byte*)strBChars + diff);
132                         char* ptr2 = (char*)strBChars;
133                         if (*ptr1 != *ptr2)
134                             return ((int)*ptr1 - (int)*ptr2);
135                         return ((int)*(ptr1 + 1) - (int)*(ptr2 + 1));
136                     }
137                     ++strBChars;
138                 }
139 
140                 int c;
141                 if (count == -1)
142                     if ((c = *((char*)((byte*)strBChars + diff)) - *((char*)strBChars)) != 0)
143                         return c;
144             }
145 
146             return countA - countB;
147         }
148 
149         //
150         //
151         // NATIVE INSTANCE METHODS
152         //
153         //
154 
155         //
156         // Search/Query methods
157         //
158 
159         //
160         // Common worker for the various Equality methods. The caller must have already ensured that
161         // both strings are non-null and that their lengths are equal. Ther caller should also have
162         // done the Object.ReferenceEquals() fastpath check as we won't repeat it here.
163         //
EqualsHelper(String strA, String strB)164         private static unsafe bool EqualsHelper(String strA, String strB)
165         {
166             Debug.Assert(strA != null);
167             Debug.Assert(strB != null);
168             Debug.Assert(strA.Length == strB.Length);
169 
170             int length = strA.Length;
171             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
172             {
173                 char* a = ap;
174                 char* b = bp;
175 
176 #if BIT64
177                 // Single int read aligns pointers for the following long reads
178                 // PERF: No length check needed as there is always an int32 worth of string allocated
179                 //       This read can also include the null terminator which both strings will have
180                 if (*(int*)a != *(int*)b) return false;
181                 length -= 2; a += 2; b += 2;
182 
183                 // for 64-bit platforms we unroll by 12 and
184                 // check 3 qword at a time. This is less code
185                 // than the 32 bit case and is a shorter path length
186 
187                 while (length >= 12)
188                 {
189                     if (*(long*)a != *(long*)b) goto ReturnFalse;
190                     if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse;
191                     if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse;
192                     length -= 12; a += 12; b += 12;
193                 }
194 #else
195                 while (length >= 10)
196                 {
197                     if (*(int*)a != *(int*)b) goto ReturnFalse;
198                     if (*(int*)(a + 2) != *(int*)(b + 2)) goto ReturnFalse;
199                     if (*(int*)(a + 4) != *(int*)(b + 4)) goto ReturnFalse;
200                     if (*(int*)(a + 6) != *(int*)(b + 6)) goto ReturnFalse;
201                     if (*(int*)(a + 8) != *(int*)(b + 8)) goto ReturnFalse;
202                     length -= 10; a += 10; b += 10;
203                 }
204 #endif
205 
206                 // This depends on the fact that the String objects are
207                 // always zero terminated and that the terminating zero is not included
208                 // in the length. For odd string sizes, the last compare will include
209                 // the zero terminator.
210                 while (length > 0)
211                 {
212                     if (*(int*)a != *(int*)b) goto ReturnFalse;
213                     length -= 2; a += 2; b += 2;
214                 }
215 
216                 return true;
217 
218             ReturnFalse:
219                 return false;
220             }
221         }
222 
StartsWithOrdinalHelper(String str, String startsWith)223         private static unsafe bool StartsWithOrdinalHelper(String str, String startsWith)
224         {
225             Debug.Assert(str != null);
226             Debug.Assert(startsWith != null);
227             Debug.Assert(str.Length >= startsWith.Length);
228 
229             int length = startsWith.Length;
230 
231             fixed (char* ap = &str._firstChar) fixed (char* bp = &startsWith._firstChar)
232             {
233                 char* a = ap;
234                 char* b = bp;
235 
236 #if BIT64
237                 // Single int read aligns pointers for the following long reads
238                 // No length check needed as this method is called when length >= 2
239                 Debug.Assert(length >= 2);
240                 if (*(int*)a != *(int*)b) goto ReturnFalse;
241                 length -= 2; a += 2; b += 2;
242 
243                 while (length >= 12)
244                 {
245                     if (*(long*)a != *(long*)b) goto ReturnFalse;
246                     if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse;
247                     if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse;
248                     length -= 12; a += 12; b += 12;
249                 }
250 #else
251                 while (length >= 10)
252                 {
253                     if (*(int*)a != *(int*)b) goto ReturnFalse;
254                     if (*(int*)(a + 2) != *(int*)(b + 2)) goto ReturnFalse;
255                     if (*(int*)(a + 4) != *(int*)(b + 4)) goto ReturnFalse;
256                     if (*(int*)(a + 6) != *(int*)(b + 6)) goto ReturnFalse;
257                     if (*(int*)(a + 8) != *(int*)(b + 8)) goto ReturnFalse;
258                     length -= 10; a += 10; b += 10;
259                 }
260 #endif
261 
262                 while (length >= 2)
263                 {
264                     if (*(int*)a != *(int*)b) goto ReturnFalse;
265                     length -= 2; a += 2; b += 2;
266                 }
267 
268                 // PERF: This depends on the fact that the String objects are always zero terminated
269                 // and that the terminating zero is not included in the length. For even string sizes
270                 // this compare can include the zero terminator. Bitwise OR avoids a branch.
271                 return length == 0 | *a == *b;
272 
273             ReturnFalse:
274                 return false;
275             }
276         }
277 
CompareOrdinalHelper(String strA, String strB)278         private static unsafe int CompareOrdinalHelper(String strA, String strB)
279         {
280             Debug.Assert(strA != null);
281             Debug.Assert(strB != null);
282 
283             // NOTE: This may be subject to change if eliminating the check
284             // in the callers makes them small enough to be inlined
285             Debug.Assert(strA._firstChar == strB._firstChar,
286                 "For performance reasons, callers of this method should " +
287                 "check/short-circuit beforehand if the first char is the same.");
288 
289             int length = Math.Min(strA.Length, strB.Length);
290 
291             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
292             {
293                 char* a = ap;
294                 char* b = bp;
295 
296                 // Check if the second chars are different here
297                 // The reason we check if _firstChar is different is because
298                 // it's the most common case and allows us to avoid a method call
299                 // to here.
300                 // The reason we check if the second char is different is because
301                 // if the first two chars the same we can increment by 4 bytes,
302                 // leaving us word-aligned on both 32-bit (12 bytes into the string)
303                 // and 64-bit (16 bytes) platforms.
304 
305                 // For empty strings, the second char will be null due to padding.
306                 // The start of the string is the EE type pointer + string length,
307                 // which takes up 8 bytes on 32-bit, 12 on x64. For empty strings,
308                 // the null terminator immediately follows, leaving us with an object
309                 // 10/14 bytes in size. Since everything needs to be a multiple
310                 // of 4/8, this will get padded and zeroed out.
311 
312                 // For one-char strings the second char will be the null terminator.
313 
314                 // NOTE: If in the future there is a way to read the second char
315                 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
316                 // is exposed to mscorlib, or a future version of C# allows inline IL),
317                 // then do that and short-circuit before the fixed.
318 
319                 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
320 
321                 // Since we know that the first two chars are the same,
322                 // we can increment by 2 here and skip 4 bytes.
323                 // This leaves us 8-byte aligned, which results
324                 // on better perf for 64-bit platforms.
325                 length -= 2; a += 2; b += 2;
326 
327                 // unroll the loop
328 #if BIT64
329                 while (length >= 12)
330                 {
331                     if (*(long*)a != *(long*)b) goto DiffOffset0;
332                     if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
333                     if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
334                     length -= 12; a += 12; b += 12;
335                 }
336 #else // BIT64
337                 while (length >= 10)
338                 {
339                     if (*(int*)a != *(int*)b) goto DiffOffset0;
340                     if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
341                     if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
342                     if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
343                     if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
344                     length -= 10; a += 10; b += 10;
345                 }
346 #endif // BIT64
347 
348                 // Fallback loop:
349                 // go back to slower code path and do comparison on 4 bytes at a time.
350                 // This depends on the fact that the String objects are
351                 // always zero terminated and that the terminating zero is not included
352                 // in the length. For odd string sizes, the last compare will include
353                 // the zero terminator.
354                 while (length > 0)
355                 {
356                     if (*(int*)a != *(int*)b) goto DiffNextInt;
357                     length -= 2;
358                     a += 2;
359                     b += 2;
360                 }
361 
362                 // At this point, we have compared all the characters in at least one string.
363                 // The longer string will be larger.
364                 return strA.Length - strB.Length;
365 
366 #if BIT64
367                 DiffOffset8: a += 4; b += 4;
368                 DiffOffset4: a += 4; b += 4;
369 #else // BIT64
370             // Use jumps instead of falling through, since
371             // otherwise going to DiffOffset8 will involve
372             // 8 add instructions before getting to DiffNextInt
373             DiffOffset8: a += 8; b += 8; goto DiffOffset0;
374             DiffOffset6: a += 6; b += 6; goto DiffOffset0;
375             DiffOffset4: a += 2; b += 2;
376             DiffOffset2: a += 2; b += 2;
377 #endif // BIT64
378 
379             DiffOffset0:
380             // If we reached here, we already see a difference in the unrolled loop above
381 #if BIT64
382                 if (*(int*)a == *(int*)b)
383                 {
384                     a += 2; b += 2;
385                 }
386 #endif // BIT64
387 
388             DiffNextInt:
389                 if (*a != *b) return *a - *b;
390 
391                 DiffOffset1:
392                 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
393                 return *(a + 1) - *(b + 1);
394             }
395         }
396 
CompareOrdinalHelper(string strA, int indexA, int countA, string strB, int indexB, int countB)397         internal static unsafe int CompareOrdinalHelper(string strA, int indexA, int countA, string strB, int indexB, int countB)
398         {
399             // Argument validation should be handled by callers.
400             Debug.Assert(strA != null && strB != null);
401             Debug.Assert(indexA >= 0 && indexB >= 0);
402             Debug.Assert(countA >= 0 && countB >= 0);
403             Debug.Assert(countA <= strA.Length - indexA);
404             Debug.Assert(countB <= strB.Length - indexB);
405 
406             // Set up the loop variables.
407             fixed (char* pStrA = &strA._firstChar, pStrB = &strB._firstChar)
408             {
409                 char* strAChars = pStrA + indexA;
410                 char* strBChars = pStrB + indexB;
411                 return FastCompareStringHelper((uint*)strAChars, countA, (uint*)strBChars, countB);
412             }
413         }
414 
Compare(String strA, String strB)415         public static int Compare(String strA, String strB)
416         {
417             return Compare(strA, strB, StringComparison.CurrentCulture);
418         }
419 
Compare(String strA, String strB, bool ignoreCase)420         public static int Compare(String strA, String strB, bool ignoreCase)
421         {
422             var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
423             return Compare(strA, strB, comparisonType);
424         }
425 
426         // Provides a more flexible function for string comparision. See StringComparison
427         // for meaning of different comparisonType.
Compare(String strA, String strB, StringComparison comparisonType)428         public static int Compare(String strA, String strB, StringComparison comparisonType)
429         {
430             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
431             {
432                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
433             }
434 
435             if (object.ReferenceEquals(strA, strB))
436             {
437                 return 0;
438             }
439 
440             // They can't both be null at this point.
441             if (strA == null)
442             {
443                 return -1;
444             }
445             if (strB == null)
446             {
447                 return 1;
448             }
449 
450 
451             switch (comparisonType)
452             {
453                 case StringComparison.CurrentCulture:
454                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, 0, strA.Length, strB, 0, strB.Length, CompareOptions.None);
455 
456                 case StringComparison.CurrentCultureIgnoreCase:
457                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, 0, strA.Length, strB, 0, strB.Length, CompareOptions.IgnoreCase);
458 
459                 case StringComparison.Ordinal:
460                     // Most common case: first character is different.
461                     // Returns false for empty strings.
462                     if (strA._firstChar != strB._firstChar)
463                     {
464                         return strA._firstChar - strB._firstChar;
465                     }
466 
467                     return CompareOrdinalHelper(strA, strB);
468 
469                 case StringComparison.OrdinalIgnoreCase:
470                     return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
471 
472                 case StringComparison.InvariantCulture:
473                     return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
474 
475                 case StringComparison.InvariantCultureIgnoreCase:
476                     return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
477 
478                 default:
479                     throw new NotSupportedException(SR.NotSupported_StringComparison);
480             }
481         }
482 
483         // Provides a culture-correct string comparison. strA is compared to strB
484         // to determine whether it is lexicographically less, equal, or greater, and then a
485         // negative integer, 0, or a positive integer is returned; respectively.
486         //
Compare(String strA, String strB, CultureInfo culture, CompareOptions options)487         public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
488         {
489             if (culture == null)
490             {
491                 throw new ArgumentNullException(nameof(culture));
492             }
493 
494             return culture.CompareInfo.Compare(strA, strB, options);
495         }
496 
497         // Provides a culture-correct string comparison. strA is compared to strB
498         // to determine whether it is lexicographically less, equal, or greater, and then a
499         // negative integer, 0, or a positive integer is returned; respectively.
500         // The case-sensitive option is set by ignoreCase, and the culture is set
501         // by culture
502         //
Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)503         public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
504         {
505             CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
506             return Compare(strA, strB, culture, options);
507         }
508 
509         // Determines whether two string regions match.  The substring of strA beginning
510         // at indexA of length length is compared with the substring of strB
511         // beginning at indexB of the same length.
512         //
513 
Compare(String strA, int indexA, String strB, int indexB, int length)514         public static int Compare(String strA, int indexA, String strB, int indexB, int length)
515         {
516             // NOTE: It's important we call the boolean overload, and not the StringComparison
517             // one. The two have some subtly different behavior (see notes in the former).
518             return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
519         }
520 
521         // Determines whether two string regions match.  The substring of strA beginning
522         // at indexA of length count is compared with the substring of strB
523         // beginning at indexB of the same length.  Case sensitivity is determined by the ignoreCase boolean.
524         //
525 
Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)526         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
527         {
528             // Ideally we would just forward to the string.Compare overload that takes
529             // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
530             // That function will return early if an optimization can be applied, e.g. if
531             // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
532             // There are a couple of subtle behavior differences that prevent us from doing so
533             // however:
534             // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
535             //   since that method also returns early for nulls before validation. It shouldn't
536             //   for this overload.
537             // - Since we originally forwarded to FormatProvider for all of the argument
538             //   validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
539             //   parameter names.
540             // Therefore, we have to duplicate some of the logic here.
541 
542             int lengthA = length;
543             int lengthB = length;
544 
545             if (strA != null)
546             {
547                 lengthA = Math.Min(lengthA, strA.Length - indexA);
548             }
549 
550             if (strB != null)
551             {
552                 lengthB = Math.Min(lengthB, strB.Length - indexB);
553             }
554 
555             CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
556             return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
557         }
558 
559         // Determines whether two string regions match.  The substring of strA beginning
560         // at indexA of length length is compared with the substring of strB
561         // beginning at indexB of the same length.  Case sensitivity is determined by the ignoreCase boolean,
562         // and the culture is set by culture.
563         //
Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)564         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
565         {
566             CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
567             return Compare(strA, indexA, strB, indexB, length, culture, options);
568         }
569 
570         // Determines whether two string regions match.  The substring of strA beginning
571         // at indexA of length length is compared with the substring of strB
572         // beginning at indexB of the same length.
573         //
Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)574         public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
575         {
576             if (culture == null)
577             {
578                 throw new ArgumentNullException(nameof(culture));
579             }
580 
581             int lengthA = length;
582             int lengthB = length;
583 
584             if (strA != null)
585             {
586                 lengthA = Math.Min(lengthA, strA.Length - indexA);
587             }
588 
589             if (strB != null)
590             {
591                 lengthB = Math.Min(lengthB, strB.Length - indexB);
592             }
593 
594             return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
595         }
596 
Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)597         public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
598         {
599             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
600             {
601                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
602             }
603 
604             if (strA == null || strB == null)
605             {
606                 if (object.ReferenceEquals(strA, strB))
607                 {
608                     // They're both null
609                     return 0;
610                 }
611 
612                 return strA == null ? -1 : 1;
613             }
614 
615             // @TODO: Spec#: Figure out what to do here with the return statement above.
616             if (length < 0)
617             {
618                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
619             }
620 
621             if (indexA < 0 || indexB < 0)
622             {
623                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
624                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
625             }
626 
627             if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
628             {
629                 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
630                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
631             }
632 
633             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
634             {
635                 return 0;
636             }
637 
638             int lengthA = Math.Min(length, strA.Length - indexA);
639             int lengthB = Math.Min(length, strB.Length - indexB);
640 
641             switch (comparisonType)
642             {
643                 case StringComparison.CurrentCulture:
644                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
645 
646                 case StringComparison.CurrentCultureIgnoreCase:
647                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
648 
649                 case StringComparison.Ordinal:
650                     return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
651 
652                 case StringComparison.OrdinalIgnoreCase:
653                     return CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB);
654 
655                 case StringComparison.InvariantCulture:
656                     return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
657 
658                 case StringComparison.InvariantCultureIgnoreCase:
659                     return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
660 
661                 default:
662                     throw new ArgumentException(SR.NotSupported_StringComparison);
663             }
664         }
665 
666         // Compares strA and strB using an ordinal (code-point) comparison.
667         //
668 
CompareOrdinal(String strA, String strB)669         public static int CompareOrdinal(String strA, String strB)
670         {
671             if (object.ReferenceEquals(strA, strB))
672             {
673                 return 0;
674             }
675 
676             // They can't both be null at this point.
677             if (strA == null)
678             {
679                 return -1;
680             }
681             if (strB == null)
682             {
683                 return 1;
684             }
685 
686             // Most common case, first character is different.
687             // This will return false for empty strings.
688             if (strA._firstChar != strB._firstChar)
689             {
690                 return strA._firstChar - strB._firstChar;
691             }
692 
693             return CompareOrdinalHelper(strA, strB);
694         }
695 
696         // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)697         internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
698         {
699             // TODO: This needs to be optimized / unrolled.  It can't just use CompareOrdinalHelper(str, str)
700             // (changed to accept spans) because its implementation is based on a string layout,
701             // in a way that doesn't work when there isn't guaranteed to be a null terminator.
702 
703             int minLength = Math.Min(strA.Length, strB.Length);
704             for (int i = 0; i < minLength; i++)
705             {
706                 if (strA[i] != strB[i])
707                 {
708                     return strA[i] - strB[i];
709                 }
710             }
711 
712             return strA.Length - strB.Length;
713         }
714 
CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)715         public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
716         {
717             if (strA == null || strB == null)
718             {
719                 if (object.ReferenceEquals(strA, strB))
720                 {
721                     // They're both null
722                     return 0;
723                 }
724 
725                 return strA == null ? -1 : 1;
726             }
727 
728             // COMPAT: Checking for nulls should become before the arguments are validated,
729             // but other optimizations which allow us to return early should come after.
730 
731             if (length < 0)
732             {
733                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
734             }
735 
736             if (indexA < 0 || indexB < 0)
737             {
738                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
739                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
740             }
741 
742             int lengthA = Math.Min(length, strA.Length - indexA);
743             int lengthB = Math.Min(length, strB.Length - indexB);
744 
745             if (lengthA < 0 || lengthB < 0)
746             {
747                 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
748                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
749             }
750 
751             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
752             {
753                 return 0;
754             }
755 
756             return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
757         }
758 
759         // Compares this String to another String (cast as object), returning an integer that
760         // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
761         // if this is equal to value, or a value greater than 0 if this is greater than value.
762         //
763 
CompareTo(Object value)764         public int CompareTo(Object value)
765         {
766             if (value == null)
767             {
768                 return 1;
769             }
770 
771             string other = value as string;
772 
773             if (other == null)
774             {
775                 throw new ArgumentException(SR.Arg_MustBeString);
776             }
777 
778             return CompareTo(other); // will call the string-based overload
779         }
780 
781         // Determines the sorting relation of StrB to the current instance.
782         //
783 
CompareTo(String strB)784         public int CompareTo(String strB)
785         {
786             return string.Compare(this, strB, StringComparison.CurrentCulture);
787         }
788 
789         // Determines whether a specified string is a suffix of the the current instance.
790         //
791         // The case-sensitive and culture-sensitive option is set by options,
792         // and the default culture is used.
793         //
794 
EndsWith(String value)795         public Boolean EndsWith(String value)
796         {
797             return EndsWith(value, StringComparison.CurrentCulture);
798         }
799 
EndsWith(String value, StringComparison comparisonType)800         public Boolean EndsWith(String value, StringComparison comparisonType)
801         {
802             if ((Object)value == null)
803             {
804                 throw new ArgumentNullException(nameof(value));
805             }
806 
807             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
808             {
809                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
810             }
811 
812             if ((Object)this == (Object)value)
813             {
814                 return true;
815             }
816 
817             if (value.Length == 0)
818             {
819                 return true;
820             }
821 
822             switch (comparisonType)
823             {
824                 case StringComparison.CurrentCulture:
825                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
826 
827                 case StringComparison.CurrentCultureIgnoreCase:
828                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
829 
830                 case StringComparison.Ordinal:
831                     return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
832 
833                 case StringComparison.OrdinalIgnoreCase:
834                     return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
835 
836                 case StringComparison.InvariantCulture:
837                     return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
838 
839                 case StringComparison.InvariantCultureIgnoreCase:
840                     return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
841 
842                 default:
843                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
844             }
845         }
846 
EndsWith(String value, Boolean ignoreCase, CultureInfo culture)847         public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
848         {
849             if (null == value)
850             {
851                 throw new ArgumentNullException(nameof(value));
852             }
853 
854             if ((object)this == (object)value)
855             {
856                 return true;
857             }
858 
859             CultureInfo referenceCulture;
860             if (culture == null)
861                 referenceCulture = CultureInfo.CurrentCulture;
862             else
863                 referenceCulture = culture;
864 
865             return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
866         }
867 
EndsWith(char value)868         public bool EndsWith(char value)
869         {
870             int thisLen = Length;
871             return thisLen != 0 && this[thisLen - 1] == value;
872         }
873 
874         // Determines whether two strings match.
875 
Equals(Object obj)876         public override bool Equals(Object obj)
877         {
878             if (Object.ReferenceEquals(this, obj))
879                 return true;
880 
881             String str = obj as String;
882             if (str == null)
883                 return false;
884 
885             if (this.Length != str.Length)
886                 return false;
887 
888             return EqualsHelper(this, str);
889         }
890 
891         // Determines whether two strings match.
892 
893 
Equals(String value)894         public bool Equals(String value)
895         {
896             if (Object.ReferenceEquals(this, value))
897                 return true;
898 
899             // NOTE: No need to worry about casting to object here.
900             // If either side of an == comparison between strings
901             // is null, Roslyn generates a simple ceq instruction
902             // instead of calling string.op_Equality.
903             if (value == null)
904                 return false;
905 
906             if (this.Length != value.Length)
907                 return false;
908 
909             return EqualsHelper(this, value);
910         }
911 
912 
Equals(String value, StringComparison comparisonType)913         public bool Equals(String value, StringComparison comparisonType)
914         {
915             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
916                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
917 
918             if ((Object)this == (Object)value)
919             {
920                 return true;
921             }
922 
923             if ((Object)value == null)
924             {
925                 return false;
926             }
927 
928             switch (comparisonType)
929             {
930                 case StringComparison.CurrentCulture:
931                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, 0, this.Length, value, 0, value.Length, CompareOptions.None) == 0);
932 
933                 case StringComparison.CurrentCultureIgnoreCase:
934                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, 0, this.Length, value, 0, value.Length, CompareOptions.IgnoreCase) == 0);
935 
936                 case StringComparison.Ordinal:
937                     if (this.Length != value.Length)
938                         return false;
939                     return EqualsHelper(this, value);
940 
941                 case StringComparison.OrdinalIgnoreCase:
942                     if (this.Length != value.Length)
943                         return false;
944                     else
945                     {
946                         return CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0;
947                     }
948 
949                 case StringComparison.InvariantCulture:
950                     return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
951 
952                 case StringComparison.InvariantCultureIgnoreCase:
953                     return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
954 
955                 default:
956                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
957             }
958         }
959 
960         // Determines whether two Strings match.
961 
Equals(String a, String b)962         public static bool Equals(String a, String b)
963         {
964             if ((Object)a == (Object)b)
965             {
966                 return true;
967             }
968 
969             if ((Object)a == null || (Object)b == null || a.Length != b.Length)
970             {
971                 return false;
972             }
973 
974             return EqualsHelper(a, b);
975         }
976 
Equals(String a, String b, StringComparison comparisonType)977         public static bool Equals(String a, String b, StringComparison comparisonType)
978         {
979             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
980                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
981 
982             if ((Object)a == (Object)b)
983             {
984                 return true;
985             }
986 
987             if ((Object)a == null || (Object)b == null)
988             {
989                 return false;
990             }
991 
992             switch (comparisonType)
993             {
994                 case StringComparison.CurrentCulture:
995                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, 0, a.Length, b, 0, b.Length, CompareOptions.None) == 0);
996 
997                 case StringComparison.CurrentCultureIgnoreCase:
998                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, 0, a.Length, b, 0, b.Length, CompareOptions.IgnoreCase) == 0);
999 
1000                 case StringComparison.Ordinal:
1001                     if (a.Length != b.Length)
1002                         return false;
1003                     return EqualsHelper(a, b);
1004 
1005                 case StringComparison.OrdinalIgnoreCase:
1006                     if (a.Length != b.Length)
1007                         return false;
1008                     else
1009                     {
1010                         return CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0;
1011                     }
1012 
1013                 case StringComparison.InvariantCulture:
1014                     return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
1015 
1016                 case StringComparison.InvariantCultureIgnoreCase:
1017                     return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
1018 
1019                 default:
1020                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1021             }
1022         }
1023 
operator ==(String a, String b)1024         public static bool operator ==(String a, String b)
1025         {
1026             if (Object.ReferenceEquals(a, b))
1027                 return true;
1028             if (a == null || b == null || a.Length != b.Length)
1029                 return false;
1030             return EqualsHelper(a, b);
1031         }
1032 
operator !=(String a, String b)1033         public static bool operator !=(String a, String b)
1034         {
1035             if (Object.ReferenceEquals(a, b))
1036                 return false;
1037             if (a == null || b == null || a.Length != b.Length)
1038                 return true;
1039             return !EqualsHelper(a, b);
1040         }
1041 
1042         // Gets a hash code for this string.  If strings A and B are such that A.Equals(B), then
1043         // they will return the same hash code.
GetHashCode()1044         public override int GetHashCode()
1045         {
1046             return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
1047         }
1048 
1049         // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
1050         // that String.Equals(A, B, C), then they will return the same hash code with this comparison C.
1051         public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
1052 
1053         // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
1054         // hash table).
GetLegacyNonRandomizedHashCode()1055         internal int GetLegacyNonRandomizedHashCode()
1056         {
1057             unsafe
1058             {
1059                 fixed (char* src = &_firstChar)
1060                 {
1061                     Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
1062                     Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
1063 #if BIT64
1064                     int hash1 = 5381;
1065 #else // !BIT64 (32)
1066                     int hash1 = (5381<<16) + 5381;
1067 #endif
1068                     int hash2 = hash1;
1069 
1070 #if BIT64
1071                     int c;
1072                     char* s = src;
1073                     while ((c = s[0]) != 0)
1074                     {
1075                         hash1 = ((hash1 << 5) + hash1) ^ c;
1076                         c = s[1];
1077                         if (c == 0)
1078                             break;
1079                         hash2 = ((hash2 << 5) + hash2) ^ c;
1080                         s += 2;
1081                     }
1082 #else // !BIT64 (32)
1083                     // 32 bit machines.
1084                     int* pint = (int *)src;
1085                     int len = this.Length;
1086                     while (len > 2)
1087                     {
1088                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
1089                         hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
1090                         pint += 2;
1091                         len  -= 4;
1092                     }
1093 
1094                     if (len > 0)
1095                     {
1096                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
1097                     }
1098 #endif
1099                     return hash1 + (hash2 * 1566083941);
1100                 }
1101             }
1102         }
1103 
1104         // Determines whether a specified string is a prefix of the current instance
1105         //
StartsWith(String value)1106         public Boolean StartsWith(String value)
1107         {
1108             if ((Object)value == null)
1109             {
1110                 throw new ArgumentNullException(nameof(value));
1111             }
1112             return StartsWith(value, StringComparison.CurrentCulture);
1113         }
1114 
StartsWith(String value, StringComparison comparisonType)1115         public Boolean StartsWith(String value, StringComparison comparisonType)
1116         {
1117             if ((Object)value == null)
1118             {
1119                 throw new ArgumentNullException(nameof(value));
1120             }
1121 
1122             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
1123             {
1124                 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1125             }
1126 
1127             if ((Object)this == (Object)value)
1128             {
1129                 return true;
1130             }
1131 
1132             if (value.Length == 0)
1133             {
1134                 return true;
1135             }
1136 
1137             switch (comparisonType)
1138             {
1139                 case StringComparison.CurrentCulture:
1140                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
1141 
1142                 case StringComparison.CurrentCultureIgnoreCase:
1143                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1144 
1145                 case StringComparison.Ordinal:
1146                     if (this.Length < value.Length || _firstChar != value._firstChar)
1147                     {
1148                         return false;
1149                     }
1150                     return (value.Length == 1) ?
1151                             true :                 // First char is the same and thats all there is to compare
1152                             StartsWithOrdinalHelper(this, value);
1153 
1154                 case StringComparison.OrdinalIgnoreCase:
1155                     if (this.Length < value.Length)
1156                     {
1157                         return false;
1158                     }
1159                     return CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0;
1160 
1161                 case StringComparison.InvariantCulture:
1162                     return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
1163 
1164                 case StringComparison.InvariantCultureIgnoreCase:
1165                     return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1166 
1167                 default:
1168                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1169             }
1170         }
1171 
StartsWith(String value, Boolean ignoreCase, CultureInfo culture)1172         public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1173         {
1174             if (null == value)
1175             {
1176                 throw new ArgumentNullException(nameof(value));
1177             }
1178 
1179             if ((object)this == (object)value)
1180             {
1181                 return true;
1182             }
1183 
1184             CultureInfo referenceCulture;
1185             if (culture == null)
1186                 referenceCulture = CultureInfo.CurrentCulture;
1187             else
1188                 referenceCulture = culture;
1189 
1190             return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1191         }
1192 
StartsWith(char value)1193         public bool StartsWith(char value) => Length != 0 && _firstChar == value;
1194     }
1195 }
1196