Skip to content

Commit b3c59ac

Browse files
authored
[Debug] Escape LLDB syntax in debugDescription (#75306)
When using `@DebugDescription`, only allow use of [LLDB Summary Strings](https://lldb.llvm.org/use/variable.html#summary-strings) syntax from `lldbDescription` properties. When `@DebugDescription` is applied to existing `debugDescription` properties, escape any `$`, as the output of `debugDescription` is never interpreted by LLDB. Depends on #75305
1 parent b93d9b6 commit b3c59ac

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

lib/Macros/Sources/SwiftMacros/DebugDescriptionMacro.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import SwiftSyntax
1414
import SwiftSyntaxMacros
1515
import SwiftDiagnostics
16+
import _StringProcessing
1617

1718
public enum DebugDescriptionMacro {}
1819
public enum _DebugDescriptionPropertyMacro {}
@@ -188,12 +189,20 @@ extension _DebugDescriptionPropertyMacro: PeerMacro {
188189
return []
189190
}
190191

192+
// LLDB syntax is not allowed in debugDescription/description.
193+
let allowLLDBSyntax = onlyBinding.name == "lldbDescription"
194+
191195
// Iterate the string's segments, and convert property expressions into LLDB variable references.
192196
var summarySegments: [String] = []
193197
for segment in descriptionString.segments {
194198
switch segment {
195199
case let .stringSegment(segment):
196-
summarySegments.append(segment.content.text)
200+
var literal = segment.content.text
201+
if !allowLLDBSyntax {
202+
// To match debugDescription/description, escape `$` characters. LLDB must treat them as a literals they are.
203+
literal = literal.escapedForLLDB()
204+
}
205+
summarySegments.append(literal)
197206
case let .expressionSegment(segment):
198207
guard let onlyLabeledExpr = segment.expressions.only, onlyLabeledExpr.label == nil else {
199208
// This catches `appendInterpolation` overrides.
@@ -503,6 +512,28 @@ extension String {
503512
}
504513
}
505514

515+
extension String {
516+
fileprivate func escapedForLLDB() -> String {
517+
guard #available(macOS 13, *) else {
518+
guard self.firstIndex(of: "$") != nil else {
519+
return self
520+
}
521+
522+
var result = ""
523+
for char in self {
524+
if char == "$" {
525+
result.append("\\$")
526+
} else {
527+
result.append(char)
528+
}
529+
}
530+
return result
531+
}
532+
533+
return self.replacing("$", with: "\\$")
534+
}
535+
}
536+
506537
extension Array where Element == String {
507538
/// Convert an ArrayExprSyntax consisting of StringLiteralExprSyntax to an Array<String>.
508539
fileprivate init?(expr: ExprSyntax) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// REQUIRES: swift_swift_parser
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -enable-experimental-feature DebugDescriptionMacro -plugin-path %swift-plugin-dir -dump-macro-expansions > %t/expansions-dump.txt 2>&1
5+
// RUN: %FileCheck %s < %t/expansions-dump.txt
6+
7+
@DebugDescription
8+
struct MyStruct {
9+
var name: String = "thirty"
10+
var debugDescription: String { "${\(self.name)}" }
11+
}
12+
// CHECK: static let _lldb_summary = (
13+
// CHECK: /* version */ 1 as UInt8,
14+
// CHECK: /* record size */ 32 as UInt8,
15+
// CHECK: /* "main.MyStruct" */ 14 as UInt8, 109 as UInt8, 97 as UInt8, 105 as UInt8, 110 as UInt8, 46 as UInt8, 77 as UInt8, 121 as UInt8, 83 as UInt8, 116 as UInt8, 114 as UInt8, 117 as UInt8, 99 as UInt8, 116 as UInt8, 0 as UInt8,
16+
// CHECK: /* "\${${var.name}}" */ 16 as UInt8, 92 as UInt8, 36 as UInt8, 123 as UInt8, 36 as UInt8, 123 as UInt8, 118 as UInt8, 97 as UInt8, 114 as UInt8, 46 as UInt8, 110 as UInt8, 97 as UInt8, 109 as UInt8, 101 as UInt8, 125 as UInt8, 125 as UInt8, 0 as UInt8
17+
// CHECK: )
18+
19+
@DebugDescription
20+
class MyClass {
21+
var name: String = "thirty"
22+
var lldbDescription: String { "${var.name}" }
23+
}
24+
// CHECK: static let _lldb_summary = (
25+
// CHECK: /* version */ 1 as UInt8,
26+
// CHECK: /* record size */ 27 as UInt8,
27+
// CHECK: /* "main.MyClass" */ 13 as UInt8, 109 as UInt8, 97 as UInt8, 105 as UInt8, 110 as UInt8, 46 as UInt8, 77 as UInt8, 121 as UInt8, 67 as UInt8, 108 as UInt8, 97 as UInt8, 115 as UInt8, 115 as UInt8, 0 as UInt8,
28+
// CHECK: /* "${var.name}" */ 12 as UInt8, 36 as UInt8, 123 as UInt8, 118 as UInt8, 97 as UInt8, 114 as UInt8, 46 as UInt8, 110 as UInt8, 97 as UInt8, 109 as UInt8, 101 as UInt8, 125 as UInt8, 0 as UInt8
29+
// CHECK: )
30+

0 commit comments

Comments
 (0)