Skip to content

Commit 91efe3d

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 5f820dd commit 91efe3d

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
@@ -80,4 +80,42 @@ class LogEntryTests: XCTestCase {
8080
let encoder = LogEncoder()
8181
try logEntry.encode(with: encoder, format: .current)
8282
}
83+
84+
func testLargeSet() throws {
85+
let set = Set(1...1000)
86+
87+
let logEntry = try LogEntry(describing: set, name: "set", policy: .default)
88+
89+
guard case let .structured(name, _, _, totalChildrenCount, children, disposition) = logEntry else {
90+
XCTFail("Expected a structured log entry")
91+
return
92+
}
93+
94+
XCTAssertEqual(name, "set")
95+
XCTAssertEqual(totalChildrenCount, 1000)
96+
XCTAssertEqual(children.count, 101)
97+
98+
for (index, childEntry) in children.enumerated() {
99+
if index == 80 {
100+
// We expect the 81st child to be a gap based on the default logging policy for containers.
101+
guard case .gap = childEntry else {
102+
XCTFail("Expected this entry to be a gap entry!")
103+
return
104+
}
105+
}
106+
else {
107+
// We expect all other children to be opaque entries representing the Ints in the set.
108+
guard case let .opaque(_, _, _, _, representation) = childEntry else {
109+
XCTFail("Expected this entry to be an opaque entry!")
110+
return
111+
}
112+
113+
// We don't know the precise value, due to hashing in the set.
114+
// But we *do* know that the value should be an Int64, so check that at least.
115+
XCTAssert(representation is Int64)
116+
}
117+
}
118+
119+
XCTAssertEqual(disposition, .membershipContainer)
120+
}
83121
}

0 commit comments

Comments
 (0)