Skip to content

Commit ce96f1e

Browse files
committed
[stdlib] _modify: Use defer to ensure invariants are restored when yield throws
_modify mustn’t leave Collection storage in an inconsistent state when the code to which we’re yielding happens to throw. In practice this means that any cleanup code must happen in defer blocks before the yield. Fix Dictionary to do just that and add tests to cover this functionality (as well as some other aspects of _modify).
1 parent 1c9bb72 commit ce96f1e

File tree

6 files changed

+276
-38
lines changed

6 files changed

+276
-38
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -794,8 +794,8 @@ extension Dictionary {
794794
}
795795
}
796796
_modify {
797+
defer { _fixLifetime(self) }
797798
yield &_variant[key]
798-
_fixLifetime(self)
799799
}
800800
}
801801
}
@@ -898,8 +898,8 @@ extension Dictionary {
898898
native._insert(at: bucket, key: key, value: value)
899899
}
900900
let address = native._values + bucket.offset
901+
defer { _fixLifetime(self) }
901902
yield &address.pointee
902-
_fixLifetime(self)
903903
}
904904
}
905905

@@ -1272,14 +1272,10 @@ extension Dictionary {
12721272
return Values(_dictionary: self)
12731273
}
12741274
_modify {
1275-
var values: Values
1276-
do {
1277-
var temp = _Variant(dummy: ())
1278-
swap(&temp, &_variant)
1279-
values = Values(_variant: temp)
1280-
}
1275+
var values = Values(_variant: _Variant(dummy: ()))
1276+
swap(&values._variant, &_variant)
1277+
defer { self._variant = values._variant }
12811278
yield &values
1282-
self._variant = values._variant
12831279
}
12841280
}
12851281

@@ -1462,8 +1458,8 @@ extension Dictionary {
14621458
let native = _variant.ensureUniqueNative()
14631459
let bucket = native.validatedBucket(for: position)
14641460
let address = native._values + bucket.offset
1461+
defer { _fixLifetime(self) }
14651462
yield &address.pointee
1466-
_fixLifetime(self)
14671463
}
14681464
}
14691465

@@ -1846,8 +1842,8 @@ extension Dictionary.Index {
18461842
}
18471843
let dummy = _HashTable.Index(bucket: _HashTable.Bucket(offset: 0), age: 0)
18481844
_variant = .native(dummy)
1845+
defer { _variant = .cocoa(cocoa) }
18491846
yield &cocoa
1850-
_variant = .cocoa(cocoa)
18511847
}
18521848
}
18531849
#endif

stdlib/public/core/DictionaryVariant.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ extension Dictionary._Variant {
9595
_modify {
9696
var native = _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
9797
self = .init(dummy: ())
98+
defer { object = .init(native: native._storage) }
9899
yield &native
99-
object = .init(native: native._storage)
100100
}
101101
}
102102

stdlib/public/core/NativeDictionary.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -402,24 +402,32 @@ extension _NativeDictionary {
402402
// Move the old value out of storage, wrapping it into an optional
403403
// before yielding it.
404404
var value: Value? = (_values + bucket.offset).move()
405-
yield &value
406-
if let value = value {
407-
// **Mutation.** Initialize storage to new value.
408-
(_values + bucket.offset).initialize(to: value)
409-
} else {
410-
// **Removal.** We've already deinitialized the value; deinitialize
411-
// the key too and register the removal.
412-
(_keys + bucket.offset).deinitialize(count: 1)
413-
_delete(at: bucket)
405+
defer {
406+
// This is in a defer block because yield might throw, and we need to
407+
// preserve Dictionary's storage invariants when that happens.
408+
if let value = value {
409+
// **Mutation.** Initialize storage to new value.
410+
(_values + bucket.offset).initialize(to: value)
411+
} else {
412+
// **Removal.** We've already deinitialized the value; deinitialize
413+
// the key too and register the removal.
414+
(_keys + bucket.offset).deinitialize(count: 1)
415+
_delete(at: bucket)
416+
}
414417
}
418+
yield &value
415419
} else {
416420
var value: Value? = nil
417-
yield &value
418-
if let value = value {
419-
// **Insertion.** Insert the new entry at the correct place. Note
420-
// that `mutatingFind` already ensured that we have enough capacity.
421-
_insert(at: bucket, key: key, value: value)
421+
defer {
422+
// This is in a defer block because yield might throw, and we need to
423+
// preserve Dictionary invariants when that happens.
424+
if let value = value {
425+
// **Insertion.** Insert the new entry at the correct place. Note
426+
// that `mutatingFind` already ensured that we have enough capacity.
427+
_insert(at: bucket, key: key, value: value)
428+
}
422429
}
430+
yield &value
423431
}
424432
}
425433
}

stdlib/public/core/Set.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,8 +1367,8 @@ extension Set.Index {
13671367
}
13681368
let dummy = _HashTable.Index(bucket: _HashTable.Bucket(offset: 0), age: 0)
13691369
_variant = .native(dummy)
1370+
defer { _variant = .cocoa(cocoa) }
13701371
yield &cocoa
1371-
_variant = .cocoa(cocoa)
13721372
}
13731373
}
13741374
#endif

stdlib/public/core/SetVariant.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ extension Set._Variant {
9292
_modify {
9393
var native = _NativeSet<Element>(object.unflaggedNativeInstance)
9494
self = .init(dummy: ())
95+
defer {
96+
// This is in a defer block because yield might throw, and we need to
97+
// preserve Set's storage invariants when that happens.
98+
object = .init(native: native._storage)
99+
}
95100
yield &native
96-
object = .init(native: native._storage)
97101
}
98102
}
99103

0 commit comments

Comments
 (0)