Skip to content

Commit f01171a

Browse files
committed
Use prefix and suffix when getting the first and last n children.
Offsetting indices only works for collections which implement `BidirectionalCollection`, while `suffix(_:)` is available for all collections. This prevent crashes when e.g. generating a `LogEntry` for instances of `Set`. This addresses <rdar://problem/39791397>.
1 parent 063a4b0 commit f01171a

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

PlaygroundLogger/PlaygroundLogger/LogEntry+Reflection.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,11 @@ extension Mirror {
222222
numberOfChildren = count
223223
}
224224

225-
let start = children.startIndex
226-
let max = children.index(start, offsetBy: numberOfChildren)
227-
228-
return superclassEntries + children[start..<max].map(logEntry(forChild:))
225+
return superclassEntries + children.prefix(numberOfChildren).map(logEntry(forChild:))
229226
}
230227

231228
func logEntries(forLastChildren count: Int) -> [LogEntry] {
232-
let max = children.endIndex
233-
let start = children.index(max, offsetBy: -count)
234-
235-
return children[start..<max].map(logEntry(forChild:))
229+
return children.suffix(count).map(logEntry(forChild:))
236230
}
237231

238232
// Ensure that our children are loggable (i.e. their depth is not prohibited by our current policy).

PlaygroundLogger/PlaygroundLoggerTests/LogEntryTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,42 @@ class LogEntryTests: XCTestCase {
3030
XCTAssertEqual(totalChildrenCount, 0)
3131
XCTAssert(children.isEmpty)
3232
}
33+
34+
func testLargeSet() throws {
35+
let set = Set(1...1000)
36+
37+
let logEntry = try LogEntry(describing: set, name: "set", policy: .default)
38+
39+
guard case let .structured(name, _, _, totalChildrenCount, children, disposition) = logEntry else {
40+
XCTFail("Expected a structured log entry")
41+
return
42+
}
43+
44+
XCTAssertEqual(name, "set")
45+
XCTAssertEqual(totalChildrenCount, 1000)
46+
XCTAssertEqual(children.count, 101)
47+
48+
for (index, childEntry) in children.enumerated() {
49+
if index == 80 {
50+
// We expect the 81st child to be a gap based on the default logging policy for containers.
51+
guard case .gap = childEntry else {
52+
XCTFail("Expected this entry to be a gap entry!")
53+
return
54+
}
55+
}
56+
else {
57+
// We expect all other children to be opaque entries representing the Ints in the set.
58+
guard case let .opaque(_, _, _, _, representation) = childEntry else {
59+
XCTFail("Expected this entry to be an opaque entry!")
60+
return
61+
}
62+
63+
// We don't know the precise value, due to hashing in the set.
64+
// But we *do* know that the value should be an Int64, so check that at least.
65+
XCTAssert(representation is Int64)
66+
}
67+
}
68+
69+
XCTAssertEqual(disposition, .membershipContainer)
70+
}
3371
}

0 commit comments

Comments
 (0)