From af4654d8b48e7b3745acd84027808da061d3cc28 Mon Sep 17 00:00:00 2001 From: HarnageaGabriel Date: Mon, 17 Aug 2026 12:30:51 +0200 Subject: [PATCH] Fix CompareInfo.IsPrefix/IsSuffix with IgnoreSymbols not ignoring leading/trailing symbols CanIgnoreAllCollationElements walked raw ICU collation elements and required each to equal zero. Collation-element iteration (ucol_next) does not apply the collator's alternate handling (UCOL_SHIFTED), so a character made ignorable by CompareOptions.IgnoreSymbols still produces a non-zero raw element, causing ComplexStartsWith/EndsWith to reject an otherwise-matching affix whenever symbols were skipped at the boundary. Compare the skipped substring against an empty string via ucol_strcoll instead, which honors the collator's configured strength and alternate handling like every other comparison in this file. Fixes #118521 --- .../CompareInfo/CompareInfoTests.IsPrefix.cs | 3 +-- .../CompareInfo/CompareInfoTests.IsSuffix.cs | 1 + .../pal_collation.c | 27 +++++-------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs index d645cf4a70913c..4877231501c1c3 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs @@ -87,8 +87,7 @@ public static IEnumerable IsPrefix_TestData() else { yield return new object[] { s_hungarianCompare, "dzsdzsfoobar", "ddzsf", CompareOptions.None, false, 0 }; - // Bug in ICU (non-Apple) implementation, correct result should be true (https://github.com/dotnet/runtime/issues/118521) - yield return new object[] { s_invariantCompare, "''Tests", "Tests", CompareOptions.IgnoreSymbols, PlatformDetection.IsHybridGlobalizationOnApplePlatform ? true : false, 0 }; + yield return new object[] { s_invariantCompare, "''Tests", "Tests", CompareOptions.IgnoreSymbols, true, 7 }; yield return new object[] { s_frenchCompare, "\u0153", "oe", CompareOptions.None, false, 0 }; if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs index 80c911a5a4a69b..eb5acdaa31a671 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs @@ -81,6 +81,7 @@ public static IEnumerable IsSuffix_TestData() // Ignore symbols yield return new object[] { s_invariantCompare, "More Test's", "Tests", CompareOptions.IgnoreSymbols, true, 6 }; + yield return new object[] { s_invariantCompare, "Tests''", "Tests", CompareOptions.IgnoreSymbols, true, 7 }; yield return new object[] { s_invariantCompare, "More Test's", "Tests", CompareOptions.None, false, 0 }; // NULL character diff --git a/src/native/libs/System.Globalization.Native/pal_collation.c b/src/native/libs/System.Globalization.Native/pal_collation.c index f9c6ef7f80b287..5c674cd919b037 100644 --- a/src/native/libs/System.Globalization.Native/pal_collation.c +++ b/src/native/libs/System.Globalization.Native/pal_collation.c @@ -407,29 +407,14 @@ static UCollator* CloneCollatorWithOptions(const UCollator* pCollator, int32_t o return pClonedCollator; } -// Returns TRUE if all the collation elements in str are completely ignorable +// Returns TRUE if str is completely ignorable by the collator. static int CanIgnoreAllCollationElements(const UCollator* pColl, const UChar* lpStr, int32_t length) { - int result = true; - UErrorCode err = U_ZERO_ERROR; - UCollationElements* pCollElem = ucol_openElements(pColl, lpStr, length, &err); - - if (U_SUCCESS(err)) - { - int32_t curCollElem = UCOL_NULLORDER; - while ((curCollElem = ucol_next(pCollElem, &err)) != UCOL_NULLORDER) - { - if (curCollElem != UCOL_IGNORABLE) - { - result = false; - break; - } - } - - ucol_closeElements(pCollElem); - } - - return U_SUCCESS(err) ? result : false; + // Collation element iterators expose raw elements and do not apply shifted + // alternate handling. Compare against an empty string so all collator + // options, including IgnoreSymbols, are honored. + UChar emptyString = 0; + return ucol_strcoll(pColl, lpStr, length, &emptyString, 0) == UCOL_EQUAL; } static void CreateSortHandle(SortHandle** ppSortHandle)