Skip to content

Commit 130a174

Browse files
committed
Address exclusivity warnings in JSONEncoder
An idiom used in JSONEncoder is violating Swift's rules about exclusive access to memory. The 'with(pushedKey:)' family of mutating functions on EncodingContainers modify 'self' by pushing a key, executing a passed-in closure, and popping the key. However, when the passed-in closure refers to 'self' this violates the rules for exclusive access since the closure reads self while with(pushedKey:) has an already in-progress modification of 'self'. return self.with(pushedKey: _JSONKey(index: self.count - 1)) { // Referring to 'self.codingPath' violates rules for exclusive access. ... } The compiler is warning about this. To address these warnings, change the callback to with(pushKey:) to accept an additional parameter: the modified encoding container. This enables the closure to safely refer to it without accessing via the captured `self` and avoids the violation of exclusive access to memory. This also makes it explicit that in the body the body of the closure the value of .codingPath read is the mutated one and not the original.
1 parent c12f650 commit 130a174

File tree

1 file changed

+10
-10
lines changed

1 file changed

+10
-10
lines changed

Foundation/JSONEncoder.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,10 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
318318
/// Performs the given closure with the given key pushed onto the end of the current coding path.
319319
///
320320
/// - parameter key: The key to push. May be nil for unkeyed containers.
321-
/// - parameter work: The work to perform with the key in the path.
322-
fileprivate mutating func with<T>(pushedKey key: CodingKey, _ work: () throws -> T) rethrows -> T {
321+
/// - parameter work: The work to perform with the key in the path. The closure parameter is the container with the pushed path.
322+
fileprivate mutating func with<T>(pushedKey key: CodingKey, _ work: (inout _JSONKeyedEncodingContainer) throws -> T) rethrows -> T {
323323
self.codingPath.append(key)
324-
let ret: T = try work()
324+
let ret: T = try work(&self)
325325
self.codingPath.removeLast()
326326
return ret
327327
}
@@ -367,7 +367,7 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
367367
self.container[key.stringValue._bridgeToObjectiveC()] = dictionary
368368

369369
return self.with(pushedKey: key) {
370-
let container = _JSONKeyedEncodingContainer<NestedKey>(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary)
370+
let container = _JSONKeyedEncodingContainer<NestedKey>(referencing: $0.encoder, codingPath: $0.codingPath, wrapping: dictionary)
371371
return KeyedEncodingContainer(container)
372372
}
373373
}
@@ -377,7 +377,7 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
377377
self.container[key.stringValue._bridgeToObjectiveC()] = array
378378

379379
return self.with(pushedKey: key) {
380-
return _JSONUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array)
380+
return _JSONUnkeyedEncodingContainer(referencing: $0.encoder, codingPath: $0.codingPath, wrapping: array)
381381
}
382382
}
383383

@@ -421,10 +421,10 @@ fileprivate struct _JSONUnkeyedEncodingContainer : UnkeyedEncodingContainer {
421421
/// Performs the given closure with the given key pushed onto the end of the current coding path.
422422
///
423423
/// - parameter key: The key to push. May be nil for unkeyed containers.
424-
/// - parameter work: The work to perform with the key in the path.
425-
fileprivate mutating func with<T>(pushedKey key: CodingKey, _ work: () throws -> T) rethrows -> T {
424+
/// - parameter work: The work to perform with the key in the path. The closure parameter is the container with the pushed path.
425+
fileprivate mutating func with<T>(pushedKey key: CodingKey, _ work: (inout _JSONUnkeyedEncodingContainer) throws -> T) rethrows -> T {
426426
self.codingPath.append(key)
427-
let ret: T = try work()
427+
let ret: T = try work(&self)
428428
self.codingPath.removeLast()
429429
return ret
430430
}
@@ -471,7 +471,7 @@ fileprivate struct _JSONUnkeyedEncodingContainer : UnkeyedEncodingContainer {
471471

472472
// self.count - 1 to accommodate the fact that we just pushed a container.
473473
return self.with(pushedKey: _JSONKey(index: self.count - 1)) {
474-
let container = _JSONKeyedEncodingContainer<NestedKey>(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary)
474+
let container = _JSONKeyedEncodingContainer<NestedKey>(referencing: $0.encoder, codingPath: $0.codingPath, wrapping: dictionary)
475475
return KeyedEncodingContainer(container)
476476
}
477477
}
@@ -482,7 +482,7 @@ fileprivate struct _JSONUnkeyedEncodingContainer : UnkeyedEncodingContainer {
482482

483483
// self.count - 1 to accommodate the fact that we just pushed a container.
484484
return self.with(pushedKey: _JSONKey(index: self.count - 1)) {
485-
return _JSONUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array)
485+
return _JSONUnkeyedEncodingContainer(referencing: $0.encoder, codingPath: $0.codingPath, wrapping: array)
486486
}
487487
}
488488

0 commit comments

Comments
 (0)