Skip to content

Commit 149ad71

Browse files
author
Lance Parker
authored
Merge pull request #14709 from milseman/comparison_finance_reform
[String] Bug fix for opaque slicing in comparison
2 parents 642cbba + ecfb6c9 commit 149ad71

File tree

2 files changed

+78
-54
lines changed

2 files changed

+78
-54
lines changed

stdlib/public/core/StringComparison.swift

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,49 @@ import SwiftShims
1616
//memcmp fast path for comparing ascii strings. rdar://problem/37473470
1717
@inline(never) // @outlined
1818
@effects(readonly)
19-
@_versioned internal
19+
@_versioned // @opaque
20+
internal
2021
func _compareUnicode(
2122
_ lhs: _StringGuts._RawBitPattern, _ rhs: _StringGuts._RawBitPattern
2223
) -> Int {
2324
let left = _StringGuts(rawBits: lhs)
2425
let right = _StringGuts(rawBits: rhs)
25-
return left._compare(right)
26+
27+
if _slowPath(!left._isContiguous || !right._isContiguous) {
28+
if !left._isContiguous {
29+
return left._asOpaque()._compareOpaque(right).rawValue
30+
} else {
31+
return right._asOpaque()._compareOpaque(left).flipped.rawValue
32+
}
33+
}
34+
35+
return left._compareContiguous(right)
2636
}
2737

2838
@inline(never) // @outlined
2939
@effects(readonly)
30-
@_versioned internal
40+
@_versioned // @opaque
41+
internal
3142
func _compareUnicode(
3243
_ lhs: _StringGuts._RawBitPattern, _ leftRange: Range<Int>,
3344
_ rhs: _StringGuts._RawBitPattern, _ rightRange: Range<Int>
3445
) -> Int {
3546
let left = _StringGuts(rawBits: lhs)
3647
let right = _StringGuts(rawBits: rhs)
37-
return left._compare(leftRange, right, rightRange)
48+
49+
if _slowPath(!left._isContiguous || !right._isContiguous) {
50+
if !left._isContiguous {
51+
return left._asOpaque()[leftRange]._compareOpaque(
52+
right, rightRange
53+
).rawValue
54+
} else {
55+
return right._asOpaque()[rightRange]._compareOpaque(
56+
left, leftRange
57+
).flipped.rawValue
58+
}
59+
}
60+
61+
return left._compareContiguous(leftRange, right, rightRange)
3862
}
3963

4064
//
@@ -484,61 +508,68 @@ extension _UnmanagedString where CodeUnit == UInt8 {
484508
public extension _StringGuts {
485509
@inline(__always)
486510
public
487-
func _compare(_ other: _StringGuts) -> Int {
488-
let selfRange = Range<Int>(uncheckedBounds: (0, self.count))
489-
let otherRange = Range<Int>(uncheckedBounds: (0, other.count))
490-
return _compare(selfRange, other, otherRange)
511+
func _compareContiguous(_ other: _StringGuts) -> Int {
512+
_sanityCheck(self._isContiguous && other._isContiguous)
513+
switch (self.isASCII, other.isASCII) {
514+
case (true, true):
515+
fatalError("Should have hit the ascii comp in StringComparable.compare()")
516+
case (true, false):
517+
return self._unmanagedASCIIView._compareStringsPreLoop(
518+
other: other._unmanagedUTF16View
519+
).rawValue
520+
case (false, true):
521+
// Same compare, just invert result
522+
return other._unmanagedASCIIView._compareStringsPreLoop(
523+
other: self._unmanagedUTF16View
524+
).flipped.rawValue
525+
case (false, false):
526+
return self._unmanagedUTF16View._compareStringsPreLoop(
527+
other: other._unmanagedUTF16View
528+
).rawValue
529+
}
491530
}
492531

493532
@inline(__always)
494533
public
495-
func _compare(
534+
func _compareContiguous(
496535
_ selfRange: Range<Int>,
497536
_ other: _StringGuts,
498537
_ otherRange: Range<Int>
499538
) -> Int {
500-
if _slowPath(
501-
!self._isContiguous || !other._isContiguous
502-
) {
503-
if !self._isContiguous {
504-
return self._asOpaque()._compareOpaque(
505-
selfRange, other, otherRange
506-
).rawValue
507-
} else {
508-
return other._asOpaque()._compareOpaque(
509-
otherRange, self, selfRange
510-
).flipped.rawValue
511-
}
512-
}
513-
514-
switch (self.isASCII, other.isASCII) {
515-
case (true, true):
516-
fatalError("Should have hit the ascii comp in StringComparable.compare()")
517-
case (true, false):
518-
return self._unmanagedASCIIView[selfRange]._compareStringsPreLoop(
519-
other: other._unmanagedUTF16View[otherRange]
520-
).rawValue
521-
case (false, true):
522-
// Same compare, just invert result
523-
return other._unmanagedASCIIView[otherRange]._compareStringsPreLoop(
524-
other: self._unmanagedUTF16View[selfRange]
525-
).flipped.rawValue
526-
case (false, false):
527-
return self._unmanagedUTF16View[selfRange]._compareStringsPreLoop(
528-
other: other._unmanagedUTF16View[otherRange]
529-
).rawValue
530-
}
539+
_sanityCheck(self._isContiguous && other._isContiguous)
540+
switch (self.isASCII, other.isASCII) {
541+
case (true, true):
542+
fatalError("Should have hit the ascii comp in StringComparable.compare()")
543+
case (true, false):
544+
return self._unmanagedASCIIView[selfRange]._compareStringsPreLoop(
545+
other: other._unmanagedUTF16View[otherRange]
546+
).rawValue
547+
case (false, true):
548+
// Same compare, just invert result
549+
return other._unmanagedASCIIView[otherRange]._compareStringsPreLoop(
550+
other: self._unmanagedUTF16View[selfRange]
551+
).flipped.rawValue
552+
case (false, false):
553+
return self._unmanagedUTF16View[selfRange]._compareStringsPreLoop(
554+
other: other._unmanagedUTF16View[otherRange]
555+
).rawValue
556+
}
531557
}
532558
}
533559

534560
extension _UnmanagedOpaqueString {
535-
@inline(never)
561+
@inline(never) // @outlined
562+
@_versioned
563+
internal
564+
func _compareOpaque(_ other: _StringGuts) -> _Ordering {
565+
return self._compareOpaque(other, 0..<other.count)
566+
}
567+
568+
@inline(never) // @outlined
536569
@_versioned
537570
internal
538571
func _compareOpaque(
539-
_ selfRange: Range<Int>,
540-
_ other: _StringGuts,
541-
_ otherRange: Range<Int>
572+
_ other: _StringGuts, _ otherRange: Range<Int>
542573
) -> _Ordering {
543574
//
544575
// Do a fast Latiny comparison loop; bail if that proves insufficient.
@@ -548,11 +579,10 @@ extension _UnmanagedOpaqueString {
548579
// termination of an all-ASCII file loaded by String.init(contentsOfFile:).
549580
//
550581

551-
552-
let selfCount = selfRange.count
582+
let selfCount = self.count
553583
let otherCount = otherRange.count
554584
let count = Swift.min(selfCount, otherCount)
555-
let idx = self[selfRange]._findDiffIdx(other, otherRange)
585+
let idx = self._findDiffIdx(other, otherRange)
556586
if idx == count {
557587
return _lexicographicalCompare(selfCount, otherCount)
558588
}

validation-test/stdlib/String.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,16 +1985,14 @@ struct ComparisonTestCase {
19851985
}
19861986
}
19871987

1988-
let simpleComparisonTestCases = [
1988+
let comparisonTestCases = [
19891989
ComparisonTestCase(["a", "a"], .equal),
19901990
ComparisonTestCase(["abcdefg", "abcdefg"], .equal),
19911991
ComparisonTestCase(["", "Z", "a", "b", "c", "\u{00c5}", "á"], .less),
19921992

19931993
ComparisonTestCase(["ábcdefg", "ábcdefgh", "ábcdefghi"], .less),
19941994
ComparisonTestCase(["abcdefg", "abcdefgh", "abcdefghi"], .less),
1995-
]
19961995

1997-
let complexComparisonTestCases = [
19981996
ComparisonTestCase(["á", "\u{0061}\u{0301}"], .equal),
19991997
ComparisonTestCase(["à", "\u{0061}\u{0301}", "â", "\u{e3}", "a\u{0308}"], .less),
20001998

@@ -2066,8 +2064,6 @@ let complexComparisonTestCases = [
20662064
ComparisonTestCase(["ì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘", "ì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘"], .equal)
20672065
]
20682066

2069-
let comparisonTestCases = simpleComparisonTestCases + complexComparisonTestCases
2070-
20712067
for test in comparisonTestCases {
20722068
StringTests.test("Comparison.\(test.strings)") {
20732069
test.test()
@@ -2078,9 +2074,7 @@ for test in comparisonTestCases {
20782074
.code {
20792075
test.testOpaqueStrings()
20802076
}
2081-
}
20822077

2083-
for test in simpleComparisonTestCases {
20842078
StringTests.test("Comparison.OpaqueSubstring.\(test.strings)")
20852079
.skip(.linuxAny(reason: "NSSlowString requires ObjC interop"))
20862080
.code {

0 commit comments

Comments
 (0)