Skip to content

Commit c2f5618

Browse files
author
Pushkar Kulkarni
committed
Fix for a crash in String.substring(with:) [SR-2483]
1 parent cc8f43a commit c2f5618

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

Foundation/NSString.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,49 @@ extension NSString {
346346
let start = _storage.utf16.startIndex
347347
let min = start.advanced(by: range.location)
348348
let max = start.advanced(by: range.location + range.length)
349-
return String(_storage.utf16[min..<max])!
349+
if let substr = String(_storage.utf16[min..<max]) {
350+
return substr
351+
}
352+
//If we come here, then the range has created unpaired surrogates on either end.
353+
//An unpaired surrogate is replaced by OXFFFD - the Unicode Replacement Character.
354+
//The CRLF ("\r\n") sequence is also treated like a surrogate pair, but its constinuent
355+
//characters "\r" and "\n" can exist outside the pair!
356+
357+
let replacementCharacter = String(describing: UnicodeScalar(0xFFFD)!)
358+
let CR: UInt16 = 13 //carriage return
359+
let LF: UInt16 = 10 //new line
360+
361+
//make sure the range is of non-zero length
362+
guard range.length > 0 else { return "" }
363+
364+
//if the range is pointing to a single unpaired surrogate
365+
if range.length == 1 {
366+
switch _storage.utf16[min] {
367+
case CR: return "\r"
368+
case LF: return "\n"
369+
default: return replacementCharacter
370+
}
371+
}
372+
373+
//set the prefix and suffix characters
374+
let prefix = _storage.utf16[min] == LF ? "\n" : replacementCharacter
375+
let suffix = _storage.utf16[max.advanced(by: -1)] == CR ? "\r" : replacementCharacter
376+
377+
//if the range breaks a surrogate pair at the beginning of the string
378+
if let substrSuffix = String(_storage.utf16[min.advanced(by: 1)..<max]) {
379+
return prefix + substrSuffix
380+
}
381+
382+
//if the range breaks a surrogate pair at the end of the string
383+
if let substrPrefix = String(_storage.utf16[min..<max.advanced(by: -1)]) {
384+
return substrPrefix + suffix
385+
}
386+
387+
//the range probably breaks surrogate pairs at both the ends
388+
guard min.advanced(by: 1) <= max.advanced(by: -1) else { return prefix + suffix }
389+
390+
let substr = String(_storage.utf16[min.advanced(by: 1)..<max.advanced(by: -1)])!
391+
return prefix + substr + suffix
350392
} else {
351393
let buff = UnsafeMutablePointer<unichar>.allocate(capacity: range.length)
352394
getCharacters(buff, range: range)

TestFoundation/TestNSString.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class TestNSString : XCTestCase {
9696
("test_reflection", { _ in test_reflection }),
9797
("test_replacingOccurrences", test_replacingOccurrences),
9898
("test_getLineStart", test_getLineStart),
99+
("test_substringWithRange", test_substringWithRange),
99100
]
100101
}
101102

@@ -1063,6 +1064,43 @@ class TestNSString : XCTestCase {
10631064
XCTAssertTrue(testString.hasPrefix(""))
10641065
XCTAssertTrue(testString.hasSuffix(""))
10651066
}
1067+
1068+
func test_substringWithRange() {
1069+
let trivial = NSString(string: "swift.org")
1070+
XCTAssertEqual(trivial.substring(with: NSMakeRange(0, 5)), "swift")
1071+
1072+
let surrogatePairSuffix = NSString(string: "Hurray🎉")
1073+
XCTAssertEqual(surrogatePairSuffix.substring(with: NSMakeRange(0, 7)), "Hurray�")
1074+
1075+
let surrogatePairPrefix = NSString(string: "🐱Cat")
1076+
XCTAssertEqual(surrogatePairPrefix.substring(with: NSMakeRange(1, 4)), "�Cat")
1077+
1078+
let singleChar = NSString(string: "😹")
1079+
XCTAssertEqual(singleChar.substring(with: NSMakeRange(0,1)), "")
1080+
1081+
let crlf = NSString(string: "\r\n")
1082+
XCTAssertEqual(crlf.substring(with: NSMakeRange(0,1)), "\r")
1083+
XCTAssertEqual(crlf.substring(with: NSMakeRange(1,1)), "\n")
1084+
XCTAssertEqual(crlf.substring(with: NSMakeRange(1,0)), "")
1085+
1086+
let bothEnds1 = NSString(string: "😺😺")
1087+
XCTAssertEqual(bothEnds1.substring(with: NSMakeRange(1,2)), "��")
1088+
1089+
let s1 = NSString(string: "😺\r\n")
1090+
XCTAssertEqual(s1.substring(with: NSMakeRange(1,2)), "\r")
1091+
1092+
let s2 = NSString(string: "\r\n😺")
1093+
XCTAssertEqual(s2.substring(with: NSMakeRange(1,2)), "\n")
1094+
1095+
let s3 = NSString(string: "😺cats😺")
1096+
XCTAssertEqual(s3.substring(with: NSMakeRange(1,6)), "�cats�")
1097+
1098+
let s4 = NSString(string: "😺cats\r\n")
1099+
XCTAssertEqual(s4.substring(with: NSMakeRange(1,6)), "�cats\r")
1100+
1101+
let s5 = NSString(string: "\r\ncats😺")
1102+
XCTAssertEqual(s5.substring(with: NSMakeRange(1,6)), "\ncats�")
1103+
}
10661104
}
10671105

10681106
struct ComparisonTest {

0 commit comments

Comments
 (0)