Skip to content

Commit f98ff6a

Browse files
author
Enrico Granata
authored
Merge pull request #3461 from egranata/print-for-debugger
Implement stringForPrintObject(_:Any) in the Swift standard library; this is meant to be a replacement for DumpForDebugger.swift in the LLDB code base. Having this code in the standard library is meant to solve a number of reliability and performance issues that are related to parsing this code as a debugger expression in every inferior process.
2 parents 2a545ea + 5be80e4 commit f98ff6a

File tree

4 files changed

+351
-1
lines changed

4 files changed

+351
-1
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ set(SWIFTLIB_ESSENTIAL
4242
ContiguousArrayBuffer.swift
4343
CString.swift
4444
CTypes.swift
45+
DebuggerSupport.swift
4546
EmptyCollection.swift
4647
ErrorType.swift
4748
Existential.swift
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
//===--- PrintForDebugger.swift -------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public enum _DebuggerSupport {
14+
internal enum CollectionStatus {
15+
case NotACollection
16+
case CollectionOfElements
17+
case CollectionOfPairs
18+
case Element
19+
case Pair
20+
case ElementOfPair
21+
22+
internal var isCollection: Bool {
23+
return self != .NotACollection
24+
}
25+
26+
internal func getChildStatus(child: Mirror) -> CollectionStatus {
27+
let disposition = child.displayStyle ?? .struct
28+
29+
if disposition == .collection { return .CollectionOfElements }
30+
if disposition == .dictionary { return .CollectionOfPairs }
31+
if disposition == .set { return .CollectionOfElements }
32+
33+
if self == .CollectionOfElements { return .Element }
34+
if self == .CollectionOfPairs { return .Pair }
35+
if self == .Pair { return .ElementOfPair }
36+
37+
return .NotACollection
38+
}
39+
}
40+
41+
internal static func asObjectIdentifier(_ value: Any) -> ObjectIdentifier? {
42+
if let ao = value as? AnyObject {
43+
return ObjectIdentifier(ao)
44+
} else {
45+
return nil
46+
}
47+
}
48+
49+
internal static func asNumericValue(_ value: Any) -> Int {
50+
if let ao = value as? AnyObject {
51+
return unsafeBitCast(ao, to: Int.self)
52+
} else {
53+
return 0
54+
}
55+
}
56+
57+
internal static func asStringRepresentation(
58+
value: Any?,
59+
mirror: Mirror,
60+
count: Int
61+
) -> String? {
62+
let ds = mirror.displayStyle ?? .`struct`
63+
switch ds {
64+
case .optional:
65+
if count > 0 {
66+
return "\(mirror.subjectType)"
67+
}
68+
else {
69+
if let x = value {
70+
return String(reflecting: x)
71+
}
72+
}
73+
case .collection:
74+
fallthrough
75+
case .dictionary:
76+
fallthrough
77+
case .set:
78+
fallthrough
79+
case .tuple:
80+
return "\(Int(mirror.children.count)) elements"
81+
case .`struct`:
82+
fallthrough
83+
case .`enum`:
84+
if let x = value {
85+
if let cdsc = (x as? CustomDebugStringConvertible) {
86+
return cdsc.debugDescription
87+
}
88+
if let csc = (x as? CustomStringConvertible) {
89+
return csc.description
90+
}
91+
}
92+
if count > 0 {
93+
return "\(mirror.subjectType)"
94+
}
95+
case .`class`:
96+
if let x = value {
97+
if let cdsc = (x as? CustomDebugStringConvertible) {
98+
return cdsc.debugDescription
99+
}
100+
if let csc = (x as? CustomStringConvertible) {
101+
return csc.description
102+
}
103+
// for a Class with no custom summary, mimic the Foundation default
104+
return "<\(x.dynamicType): 0x\(String(asNumericValue(x), radix: 16, uppercase: false))>"
105+
} else {
106+
// but if I can't provide a value, just use the type anyway
107+
return "\(mirror.subjectType)"
108+
}
109+
}
110+
if let x = value {
111+
return String(reflecting: x)
112+
}
113+
return nil
114+
}
115+
116+
internal static func ivarCount(mirror: Mirror) -> Int {
117+
let count = Int(mirror.children.count)
118+
if let sc = mirror.superclassMirror {
119+
return ivarCount(mirror: sc) + count
120+
} else {
121+
return count
122+
}
123+
}
124+
125+
126+
internal static func shouldExpand(
127+
mirror: Mirror,
128+
collectionStatus: CollectionStatus,
129+
isRoot: Bool
130+
) -> Bool {
131+
if isRoot || collectionStatus.isCollection { return true }
132+
let count = Int(mirror.children.count)
133+
if count > 0 { return true }
134+
if let sc = mirror.superclassMirror {
135+
return ivarCount(mirror: sc) > 0
136+
} else {
137+
return true
138+
}
139+
}
140+
141+
internal static func printForDebuggerImpl<StreamType : OutputStream>(
142+
value: Any?,
143+
mirror: Mirror,
144+
name: String?,
145+
indent: Int,
146+
maxDepth: Int,
147+
isRoot: Bool,
148+
parentCollectionStatus: CollectionStatus,
149+
refsAlreadySeen: inout Set<ObjectIdentifier>,
150+
maxItemCounter: inout Int,
151+
targetStream: inout StreamType
152+
) {
153+
if maxItemCounter <= 0 {
154+
return
155+
}
156+
157+
if !shouldExpand(mirror: mirror,
158+
collectionStatus: parentCollectionStatus,
159+
isRoot: isRoot) {
160+
return
161+
}
162+
163+
maxItemCounter -= 1
164+
165+
for _ in 0..<indent {
166+
print(" ", terminator: "", to: &targetStream)
167+
}
168+
169+
// do not expand classes with no custom Mirror
170+
// yes, a type can lie and say it's a class when it's not since we only
171+
// check the displayStyle - but then the type would have a custom Mirror
172+
// anyway, so there's that...
173+
var willExpand = true
174+
if let ds = mirror.displayStyle {
175+
if ds == .`class` {
176+
if let x = value {
177+
if !(x is CustomReflectable) {
178+
willExpand = false
179+
}
180+
}
181+
}
182+
}
183+
184+
let count = Int(mirror.children.count)
185+
let bullet = isRoot && (count == 0 || willExpand == false) ? ""
186+
: count == 0 ? "- "
187+
: maxDepth <= 0 ? "" : ""
188+
print("\(bullet)", terminator: "", to: &targetStream)
189+
190+
let collectionStatus = parentCollectionStatus.getChildStatus(child: mirror)
191+
192+
if let nam = name {
193+
print("\(nam) : ", terminator: "", to: &targetStream)
194+
}
195+
196+
if let str = asStringRepresentation(value: value, mirror: mirror, count: count) {
197+
print("\(str)", terminator: "", to: &targetStream)
198+
}
199+
200+
if (maxDepth <= 0) || !willExpand {
201+
print("", to: &targetStream)
202+
return
203+
}
204+
205+
if let x = value {
206+
if let valueIdentifier = asObjectIdentifier(x) {
207+
if refsAlreadySeen.contains(valueIdentifier) {
208+
print(" { ... }", to: &targetStream)
209+
return
210+
} else {
211+
refsAlreadySeen.insert(valueIdentifier)
212+
}
213+
}
214+
}
215+
216+
print("", to: &targetStream)
217+
218+
var printedElements = 0
219+
220+
if let sc = mirror.superclassMirror {
221+
printForDebuggerImpl(
222+
value: nil,
223+
mirror: sc,
224+
name: "super",
225+
indent: indent + 2,
226+
maxDepth: maxDepth - 1,
227+
isRoot: false,
228+
parentCollectionStatus: .NotACollection,
229+
refsAlreadySeen: &refsAlreadySeen,
230+
maxItemCounter: &maxItemCounter,
231+
targetStream: &targetStream)
232+
}
233+
234+
for (optionalName,child) in mirror.children {
235+
let childName = optionalName ?? "\(printedElements)"
236+
if maxItemCounter <= 0 {
237+
for _ in 0..<(indent+4) {
238+
print(" ", terminator: "", to: &targetStream)
239+
}
240+
let remainder = count - printedElements
241+
print("(\(remainder)", terminator: "", to: &targetStream)
242+
if printedElements > 0 {
243+
print(" more", terminator: "", to: &targetStream)
244+
}
245+
if remainder == 1 {
246+
print(" child)", to: &targetStream)
247+
} else {
248+
print(" children)", to: &targetStream)
249+
}
250+
return
251+
}
252+
253+
printForDebuggerImpl(
254+
value: child,
255+
mirror: Mirror(reflecting: child),
256+
name: childName,
257+
indent: indent + 2,
258+
maxDepth: maxDepth - 1,
259+
isRoot: false,
260+
parentCollectionStatus: collectionStatus,
261+
refsAlreadySeen: &refsAlreadySeen,
262+
maxItemCounter: &maxItemCounter,
263+
targetStream: &targetStream)
264+
printedElements += 1
265+
}
266+
}
267+
268+
public static func stringForPrintObject(_ value: Any) -> String {
269+
var maxItemCounter = Int.max
270+
var refs = Set<ObjectIdentifier>()
271+
var targetStream = ""
272+
273+
printForDebuggerImpl(
274+
value: value,
275+
mirror: Mirror(reflecting: value),
276+
name: nil,
277+
indent: 0,
278+
maxDepth: maxItemCounter,
279+
isRoot: true,
280+
parentCollectionStatus: .NotACollection,
281+
refsAlreadySeen: &refs,
282+
maxItemCounter: &maxItemCounter,
283+
targetStream: &targetStream)
284+
285+
return targetStream
286+
}
287+
}
288+

stdlib/public/core/GroupInfo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
"Process.swift",
142142
"Tuple.swift",
143143
"NewtypeWrapper.swift",
144-
"UnsafeBitMap.swift"
144+
"UnsafeBitMap.swift",
145+
"DebuggerSupport.swift"
145146
]
146147
}

test/1_stdlib/DebuggerSupport.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
6+
struct StructWithMembers {
7+
var a = 1
8+
var b = "Hello World"
9+
}
10+
11+
class ClassWithMembers {
12+
var a = 1
13+
var b = "Hello World"
14+
}
15+
16+
class ClassWithMirror: CustomReflectable {
17+
var customMirror: Mirror {
18+
return Mirror(self, children: ["a" : 1, "b" : "Hello World"])
19+
}
20+
}
21+
22+
let StringForPrintObjectTests = TestSuite("StringForPrintObject")
23+
StringForPrintObjectTests.test("StructWithMembers") {
24+
let printed = _DebuggerSupport.stringForPrintObject(StructWithMembers())
25+
expectEqual(printed, "▿ StructWithMembers\n - a : 1\n - b : \"Hello World\"\n")
26+
}
27+
28+
#if _runtime(_ObjC)
29+
StringForPrintObjectTests.test("ClassWithMembers") {
30+
let printed = _DebuggerSupport.stringForPrintObject(ClassWithMembers())
31+
expectTrue(printed.hasPrefix("<ClassWithMembers: 0x"))
32+
}
33+
#endif
34+
35+
StringForPrintObjectTests.test("ClassWithMirror") {
36+
let printed = _DebuggerSupport.stringForPrintObject(ClassWithMirror())
37+
expectEqual(printed, "▿ ClassWithMirror\n - a : 1\n - b : \"Hello World\"\n")
38+
}
39+
40+
StringForPrintObjectTests.test("Array") {
41+
let printed = _DebuggerSupport.stringForPrintObject([1,2,3,4])
42+
expectEqual(printed, "▿ 4 elements\n - 0 : 1\n - 1 : 2\n - 2 : 3\n - 3 : 4\n")
43+
}
44+
45+
StringForPrintObjectTests.test("Dictionary") {
46+
let printed = _DebuggerSupport.stringForPrintObject([1:2])
47+
expectEqual(printed, "▿ 1 elements\n ▿ 0 : 2 elements\n - .0 : 1\n - .1 : 2\n")
48+
}
49+
50+
StringForPrintObjectTests.test("NilOptional") {
51+
let printed = _DebuggerSupport.stringForPrintObject(nil as Int?)
52+
expectEqual(printed, "nil\n")
53+
}
54+
55+
StringForPrintObjectTests.test("SomeOptional") {
56+
let printed = _DebuggerSupport.stringForPrintObject(3 as Int?)
57+
expectEqual(printed, "▿ Optional<Int>\n - some : 3\n")
58+
}
59+
60+
runAllTests()

0 commit comments

Comments
 (0)