Skip to content

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

Open
HarnageaGabriel wants to merge 1 commit into
dotnet:mainfrom
HarnageaGabriel:fix-isprefix-ignoresymbols
Open

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

Conversation

@HarnageaGabriel

Copy link
Copy Markdown

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
Author

@dotnet-policy-service agree

1 similar comment
@HarnageaGabriel

Copy link
Copy Markdown
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.*"

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