[stdlib] String.debugDescription: Fix quoting behavior

`String.debugDescription` currently fails to protect the contents of
the string from combining with the opening or closing `”` characters
or one of the characters of a quoted scalar:

```swift
let s = “\u{301}A\n\u{302}B\u{70F}”
print(s.debugDescription)
// ⟹ “́A\n̂B܏”  (characters: “́, A, \, n̂, B, ܏”)
```

This can make debug output difficult to read, as string contents are
allowed to spread over and pollute neighboring meta-characters.

This change fixes this by force-quoting the problematic scalars in
these cases:

```swift
let s = “\u{301}A\n\u{302}B\u{70F}”
print(s.debugDescription)
// ⟹ “\u{301}A\n\u{302}B\u{70F}”
```

Of course, Unicode scalars that don’t engage in such behavior are
still allowed to pass through unchanged:

```swift
let s = “Cafe\u{301}”
print(s.debugDescription)
// ⟹ “Café”
```
This commit is contained in:
Karoy Lorentey
2023-01-15 22:45:11 -08:00
parent 7a0bcfa09d
commit 1241df3fab
3 changed files with 95 additions and 9 deletions

View File

@@ -29,16 +29,60 @@ PrintTests.test("Printable") {
let us1: UnicodeScalar = "\\"
expectPrinted("\\", us1)
expectEqual("\"\\\\\"", us1.description)
expectEqual("\\", us1.description)
expectDebugPrinted("\"\\\\\"", us1)
let us2: UnicodeScalar = ""
expectPrinted("", us2)
expectEqual("\"\"", us2.description)
expectEqual("", us2.description)
expectDebugPrinted("\"\\u{3042}\"", us2)
}
PrintTests.test("Printable") {
PrintTests.test("TrickyQuoting") {
guard #available(SwiftStdlib 5.9, *) else { return }
// U+301: COMBINING ACUTE ACCENT (Grapheme_Cluster_Break = Extend)
let s1 = "\u{301}Foo"
expectPrinted(s1, s1)
expectDebugPrinted("\"\\u{0301}Foo\"", s1)
// U+302: COMBINING CIRCUMFLEX ACCENT (Grapheme_Cluster_Break = Extend)
let s2 = "\u{301}\u{302}Foo"
expectPrinted(s2, s2)
expectDebugPrinted("\"\\u{0301}\\u{0302}Foo\"", s2)
let s3 = "Foo\n\u{301}\u{302}Foo"
expectPrinted(s3, s3)
expectDebugPrinted("\"Foo\\n\\u{0301}\\u{0302}Foo\"", s3)
// U+200D: ZERO WIDTH JOINER (Grapheme_Cluster_Break = ZWJ)
let s4 = "\u{200d}Foo"
expectPrinted(s4, s4)
expectDebugPrinted("\"\\u{200D}Foo\"", s4)
// U+110BD: KAITHI NUMBER SIGN (Grapheme_Cluster_Break = Prepend)
let s5 = "Foo\u{110BD}"
expectPrinted(s5, s5)
expectDebugPrinted("\"Foo\\u{000110BD}\"", s5)
// U+070F: SYRIAC ABBREVIATION MARK (Grapheme_Cluster_Break = Prepend)
let s6 = "Foo\u{070F}\u{110BD}"
expectPrinted(s6, s6)
expectDebugPrinted("\"Foo\\u{070F}\\u{000110BD}\"", s6)
let s7 = "Foo\u{301}\u{070F}\u{110BD}"
expectPrinted(s7, s7)
expectDebugPrinted("\"Foo\u{301}\\u{070F}\\u{000110BD}\"", s7)
let s8 = "Foo\u{301}\u{302}\u{070F}\u{110BD}Foo"
expectPrinted(s8, s8)
expectDebugPrinted("\"Foo\u{0301}\u{0302}\u{070F}\u{110BD}Foo\"", s8)
let s9 = "Foo\u{301}"
expectPrinted(s9, s9)
expectDebugPrinted("\"Foo\u{0301}\"", s9)
}
PrintTests.test("Optional") {
expectPrinted("Optional(\"meow\")", String?("meow"))
}