Skip to content

Commit 92523c3

Browse files
fortmarekKolos65
authored andcommitted
fix: fix thread-safety crash in Matcher when tests run in parallel
The `Matcher` singleton's `matchers` array is accessed without synchronization. Under Swift Testing's parallel execution, concurrent `register()` writes and `comparator()` reads from multiple test `init()` methods cause data races that crash the test process. Observed crash sites: - `Matcher.register<A>(_:)` — concurrent appends to the array - `closure #1 in Parameter.eraseToGenericValue()` — concurrent reads via `Matcher.comparator(for:)` while another thread appends Add an NSLock to synchronize all matchers array access. The lock is applied at the leaf level (append and snapshot-read) to avoid re-entrancy in the Sequence comparator path which calls `comparator(by:)` → `comparator(for: T.Element.self)` → `comparator(by:)`.
1 parent e969f84 commit 92523c3

File tree

1 file changed

+5
-4
lines changed

1 file changed

+5
-4
lines changed

Sources/Mockable/Matcher/Matcher.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class Matcher {
3434

3535
// MARK: Private Properties
3636

37-
private var matchers: [MatcherType] = []
37+
private let matchers = LockedValue<[MatcherType]>([])
3838

3939
#if swift(>=6)
4040
nonisolated(unsafe) private static var `default` = Matcher()
@@ -133,7 +133,7 @@ public class Matcher {
133133
extension Matcher {
134134
private func register<T>(_ valueType: T.Type, match: @escaping Comparator<T>) {
135135
let mirror = Mirror(reflecting: valueType)
136-
matchers.append((mirror, match as Any))
136+
matchers.withValue { $0.append((mirror, match as Any)) }
137137
}
138138

139139
private func register<T>(_ valueType: T.Type.Type) {
@@ -143,7 +143,7 @@ extension Matcher {
143143
private func register<T>(_ valueType: T.Type) where T: Equatable {
144144
let mirror = Mirror(reflecting: valueType)
145145
let comparator = comparator(for: T.self)
146-
matchers.append((mirror, comparator as Any))
146+
matchers.withValue { $0.append((mirror, comparator as Any)) }
147147
}
148148
}
149149

@@ -181,7 +181,8 @@ extension Matcher {
181181

182182
extension Matcher {
183183
private func comparator(by mirror: Mirror) -> Any? {
184-
matchers.reversed().first { matcher -> Bool in
184+
let snapshot = matchers.withValue { $0 }
185+
return snapshot.reversed().first { matcher -> Bool in
185186
matcher.mirror.subjectType == mirror.subjectType
186187
}?.comparator
187188
}

0 commit comments

Comments
 (0)