Skip to content

Fix CompareInfo.IsPrefix/IsSuffix with CompareOptions.IgnoreSymbols not ignoring leading/trailing symbols - #132397

Merged
tarekgh merged 1 commit into
dotnet:mainfrom
HarnageaGabriel:fix-isprefix-ignoresymbols
Aug 18, 2026
Merged

Fix CompareInfo.IsPrefix/IsSuffix with CompareOptions.IgnoreSymbols not ignoring leading/trailing symbols#132397
tarekgh merged 1 commit into
dotnet:mainfrom
HarnageaGabriel:fix-isprefix-ignoresymbols

Conversation

@HarnageaGabriel

Copy link
Copy Markdown
Contributor

Fixes #118521

Root cause

ComplexStartsWith/ComplexEndsWith in pal_collation.c use ICU string search to locate the requested affix, then call CanIgnoreAllCollationElements to check whether any skipped text before/after the match is entirely ignorable under the active CompareOptions.

CanIgnoreAllCollationElements walked the raw collation elements returned by ucol_next (via ucol_openElements) and required each one to equal UCOL_IGNORABLE (0). Raw collation-element iteration does not apply the collator's UCOL_ALTERNATE_HANDLING setting — so when CompareOptions.IgnoreSymbols sets UCOL_ALTERNATE_HANDLING = UCOL_SHIFTED on the collator, a symbol character that should now be ignorable (e.g. ') still produces a non-zero raw collation element. This made CanIgnoreAllCollationElements incorrectly report the skipped text as "not ignorable," so:

CultureInfo.InvariantCulture.CompareInfo.IsPrefix("''Tests", "Tests", CompareOptions.IgnoreSymbols)

returned false instead of true on ICU (non-Windows NLS, non-Apple-hybrid) platforms — those other implementations already used a different code path that isn't affected by this quirk, which is why the existing test file already had a platform-conditional branch documenting this exact case as a known ICU-backend bug pointing at this issue.

I verified this against a live ICU instance: raw ucol_next elements for symbol characters made ignorable via IgnoreSymbols remain non-zero, while ucol_strcoll against those same characters correctly honors the collator's strength/alternate-handling and reports equality with an empty string.

Fix

CanIgnoreAllCollationElements now compares the skipped substring against an empty string using ucol_strcoll (the same high-level comparison entry point already used elsewhere in this file, e.g. GlobalizationNative_CompareString), instead of manually iterating and masking raw collation elements. This honors the collator's configured strength, alternate handling (IgnoreSymbols), and locale tailoring uniformly, rather than re-implementing that logic against the low-level iterator API. A non-null empty UChar buffer is passed (rather than NULL) to avoid a documented old-ICU null-input issue that this file already works around elsewhere (ICU-9396).

This helper is shared by both ComplexStartsWith and ComplexEndsWith, so the fix resolves the symmetric IsSuffix case as well (e.g. "Tests''".EndsWith("Tests", CompareOptions.IgnoreSymbols)), for which I added a regression test.

Changes

  • src/native/libs/System.Globalization.Native/pal_collation.c: replaced the raw collation-element loop in CanIgnoreAllCollationElements with an ucol_strcoll-against-empty-string check.
  • src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs: removed the platform-conditional "known ICU bug" branch for ''Tests/Tests and set the expected result to true (matched length 7) unconditionally.
  • src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs: added the symmetric Tests''/Tests regression case.

Validation

I don't have a full native ICU build/toolchain available in this environment, so I wasn't able to run the managed test suite against a compiled libSystem.Globalization.Native. I did directly probe ICU's C collation API (usearch_first/usearch_last, raw ucol_next vs ucol_strcoll) to confirm the root cause and the fix's behavior character-by-character for the affected cases. The change is narrowly scoped to the single shared helper function and mirrors an existing, already-used API call pattern in the same file.

…ding/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 dotnet#118521
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-globalization
See info in area-owners.md if you want to be subscribed.

@HarnageaGabriel

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

1 similar comment
@HarnageaGabriel

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

@tarekgh

tarekgh commented Aug 17, 2026

Copy link
Copy Markdown
Member

@EgorBot -windows_x64 -linux_x64 --filter "StringSearch.*"

@tarekgh

tarekgh commented Aug 17, 2026

Copy link
Copy Markdown
Member

@EgorBot -windows_x64 -linux_x64 --filter "System.Globalization.Tests.StringSearch.*"

@tarekgh

tarekgh commented Aug 18, 2026

Copy link
Copy Markdown
Member

This change is a nice improvement because it also removes an existing ICU-vs-NLS divergence, not just the IgnoreSymbols case in the linked issue.

Since CanIgnoreAllCollationElements now honors the collator's configured strength and alternate handling, the leftover-text check also becomes correct for IgnoreNonSpace when a leading/trailing nonspacing mark is skipped. NLS already returned true for those; ICU was returning false.

Comparing the current runtime (ICU, pre-fix) against NLS on the same inputs with InvariantCulture:

Case ICU (pre-fix) NLS (reference) ICU (after fix)
IsPrefix("''Tests", "Tests", IgnoreSymbols) False True True
IsSuffix("Tests''", "Tests", IgnoreSymbols) False True True
IsPrefix("\u0308abc", "abc", IgnoreNonSpace) False True True
IsSuffix("abc\u0308", "abc", IgnoreNonSpace) True True True

So the fix moves ICU toward the NLS behavior in all of these cases. The IgnoreNonSpace leading-mark case isn't currently covered by the IsPrefix/IsSuffix test data, so nothing regresses, but it might be worth adding a regression row for it to lock in the behavior.

I also checked that the change stays precise and doesn't over-ignore: a combining mark is still not ignorable under IgnoreSymbols, and a real letter is never ignorable under IgnoreNonSpace.

@tarekgh

tarekgh commented Aug 18, 2026

Copy link
Copy Markdown
Member

/ba-g the failures are not related

@tarekgh

tarekgh commented Aug 18, 2026

Copy link
Copy Markdown
Member

The benchmarks not showing any regression either. We should be good to go.

@tarekgh tarekgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @HarnageaGabriel for providing the fix.

@tarekgh tarekgh added this to the 12.0.0 milestone Aug 18, 2026
@tarekgh
tarekgh merged commit 1f22605 into dotnet:main Aug 18, 2026
156 of 163 checks passed
@HarnageaGabriel
HarnageaGabriel deleted the fix-isprefix-ignoresymbols branch August 18, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Globalization community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CompareInfo.IsPrefix with CompareOptions.IgnoreSymbols does not ignore leading symbols

2 participants