Skip to content

Commit 94942c5

Browse files
authored
[String] Fix corner case in comparison fast-path. (#20937)
When in a post-binary-prefix-scan fast-path, we need to make sure we are comparing a full-segment scalar, otherwise we miss situations where a combining end-of-segment scalar would be reordered with a prior combining scalar in the same segment under normalization in one string but not the other. This was hidden by the fact that many combining scalars are not NFC_QC=maybe, but those which are not present in any precomposed form have NFC_QC=yes. Added tests.
1 parent 0d9d4a0 commit 94942c5

File tree

2 files changed

+8
-1
lines changed

2 files changed

+8
-1
lines changed

stdlib/public/core/StringComparison.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ internal func _stringCompare(
136136
let nfcQC = leftScalar._isNFCQCYes && rightScalar._isNFCQCYes
137137
let isSegmentEnd = left.hasNormalizationBoundary(before: idx + leftLen)
138138
&& right.hasNormalizationBoundary(before: idx + rightLen)
139-
if _fastPath(nfcQC && isSegmentEnd) {
139+
let isSegmentStart = leftScalar._hasNormalizationBoundaryBefore
140+
&& rightScalar._hasNormalizationBoundaryBefore
141+
if _fastPath(nfcQC && isSegmentEnd && isSegmentStart) {
140142
return expecting == _lexicographicalCompare(leftScalar, rightScalar)
141143
}
142144
}

validation-test/stdlib/String.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,7 @@ struct ComparisonTestCase {
20642064
expectEqual(pair.0, pair.1)
20652065
}
20662066
}
2067+
expectEqualSequence(strings, opaqueStrings)
20672068
#endif
20682069
}
20692070

@@ -2079,6 +2080,7 @@ struct ComparisonTestCase {
20792080
let expectedResult: _Ordering = string1 < string2 ? .less : (string1 > string2 ? .greater : .equal)
20802081
let opaqueResult: _Ordering = opaqueString < string2 ? .less : (opaqueString > string2 ? .greater : .equal)
20812082

2083+
expectEqual(string1, opaqueString)
20822084
expectEqual(opaqueResult, expectedResult)
20832085
}
20842086
#endif
@@ -2133,6 +2135,9 @@ let comparisonTestCases = [
21332135
ComparisonTestCase(["\u{f90b}", "\u{5587}"], .equal),
21342136

21352137
ComparisonTestCase(["a\u{1D160}a", "a\u{1D158}\u{1D1C7}"], .less),
2138+
2139+
ComparisonTestCase(["a\u{305}\u{315}", "a\u{315}\u{305}"], .equal),
2140+
ComparisonTestCase(["a\u{315}bz", "a\u{315}\u{305}az"], .greater),
21362141

21372142
ComparisonTestCase(["\u{212b}", "\u{00c5}"], .equal),
21382143
ComparisonTestCase([

0 commit comments

Comments
 (0)