Skip to content

Commit 4cca9a6

Browse files
authored
Merge pull request swiftlang#63646 from lorentey/fix-string-bitcast
[stdlib] Fix potentially undefined behavior in StringObject.nativeStorage and document Builtin.unsafeBitCast
2 parents 484f847 + 3f5dfea commit 4cca9a6

File tree

7 files changed

+160
-61
lines changed

7 files changed

+160
-61
lines changed

stdlib/public/core/Builtin.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,23 @@ func _canBeClass<T>(_: T.Type) -> Int8 {
7272
/// `unsafeBitCast(_:to:)` with class or pointer types; doing so may
7373
/// introduce undefined behavior.
7474
///
75-
/// - Warning: Calling this function breaks the guarantees of the Swift type
76-
/// system; use with extreme care.
75+
/// Warning: Calling this function breaks the guarantees of the Swift type
76+
/// system; use with extreme care.
7777
///
78-
/// - Parameters:
78+
/// Warning: Casting from an integer or a pointer type to a reference type
79+
/// is undefined behavior. It may result in incorrect code in any future
80+
/// compiler release. To convert a bit pattern to a reference type:
81+
/// 1. convert the bit pattern to an UnsafeRawPointer.
82+
/// 2. create an unmanaged reference using Unmanaged.fromOpaque()
83+
/// 3. obtain a managed reference using Unmanaged.takeUnretainedValue()
84+
/// The programmer must ensure that the resulting reference has already been
85+
/// manually retained.
86+
///
87+
/// Parameters:
7988
/// - x: The instance to cast to `type`.
8089
/// - type: The type to cast `x` to. `type` and the type of `x` must have the
8190
/// same size of memory representation and compatible memory layout.
82-
/// - Returns: A new instance of type `U`, cast from `x`.
91+
/// Returns: A new instance of type `U`, cast from `x`.
8392
@inlinable // unsafe-performance
8493
@_transparent
8594
public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U {

stdlib/public/core/StringGuts.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ extension _StringGuts {
123123
// Whether this string has breadcrumbs
124124
internal var hasBreadcrumbs: Bool {
125125
return hasSharedStorage
126-
|| (hasNativeStorage && _object.nativeStorage.hasBreadcrumbs)
126+
|| (hasNativeStorage && _object.withNativeStorage { $0.hasBreadcrumbs })
127127
}
128128
}
129129

@@ -249,12 +249,11 @@ extension _StringGuts {
249249
) -> Int? {
250250
#if _runtime(_ObjC)
251251
// Currently, foreign means NSString
252-
if let res = _cocoaStringCopyUTF8(_object.cocoaObject,
253-
into: UnsafeMutableRawBufferPointer(start: mbp.baseAddress,
254-
count: mbp.count)) {
255-
return res
252+
let res = _object.withCocoaObject {
253+
_cocoaStringCopyUTF8($0, into: UnsafeMutableRawBufferPointer(mbp))
256254
}
257-
255+
if let res { return res }
256+
258257
// If the NSString contains invalid UTF8 (e.g. unpaired surrogates), we
259258
// can get nil from cocoaStringCopyUTF8 in situations where a character by
260259
// character loop would get something more useful like repaired contents

stdlib/public/core/StringGutsRangeReplaceable.swift

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,42 @@
1313
// COW helpers
1414
extension _StringGuts {
1515
internal var nativeCapacity: Int? {
16+
@inline(never)
17+
@_effects(releasenone)
18+
get {
1619
guard hasNativeStorage else { return nil }
17-
return _object.nativeStorage.capacity
20+
return _object.withNativeStorage { $0.capacity }
21+
}
1822
}
1923

2024
internal var nativeUnusedCapacity: Int? {
25+
@inline(never)
26+
@_effects(releasenone)
27+
get {
2128
guard hasNativeStorage else { return nil }
22-
return _object.nativeStorage.unusedCapacity
29+
return _object.withNativeStorage { $0.unusedCapacity }
30+
}
2331
}
2432

2533
// If natively stored and uniquely referenced, return the storage's total
2634
// capacity. Otherwise, nil.
2735
internal var uniqueNativeCapacity: Int? {
28-
@inline(__always) mutating get {
36+
@inline(never)
37+
@_effects(releasenone)
38+
mutating get {
2939
guard isUniqueNative else { return nil }
30-
return _object.nativeStorage.capacity
40+
return _object.withNativeStorage { $0.capacity }
3141
}
3242
}
3343

3444
// If natively stored and uniquely referenced, return the storage's spare
3545
// capacity. Otherwise, nil.
3646
internal var uniqueNativeUnusedCapacity: Int? {
37-
@inline(__always) mutating get {
47+
@inline(never)
48+
@_effects(releasenone)
49+
mutating get {
3850
guard isUniqueNative else { return nil }
39-
return _object.nativeStorage.unusedCapacity
51+
return _object.withNativeStorage { $0.unusedCapacity }
4052
}
4153
}
4254

@@ -54,6 +66,20 @@ extension _StringGuts {
5466

5567
// Range-replaceable operation support
5668
extension _StringGuts {
69+
@inline(__always)
70+
internal mutating func updateNativeStorage<R>(
71+
_ body: (__StringStorage) -> R
72+
) -> R {
73+
let (r, cf) = self._object.withNativeStorage {
74+
let r = body($0)
75+
let cf = $0._countAndFlags
76+
return (r, cf)
77+
}
78+
// We need to pick up new count/flags from the modified storage.
79+
self._object._setCountAndFlags(to: cf)
80+
return r
81+
}
82+
5783
@inlinable
5884
internal init(_initialCapacity capacity: Int) {
5985
self.init()
@@ -243,11 +269,7 @@ extension _StringGuts {
243269
internal mutating func appendInPlace(
244270
_ other: UnsafeBufferPointer<UInt8>, isASCII: Bool
245271
) {
246-
self._object.nativeStorage.appendInPlace(other, isASCII: isASCII)
247-
248-
// We re-initialize from the modified storage to pick up new count, flags,
249-
// etc.
250-
self = _StringGuts(self._object.nativeStorage)
272+
updateNativeStorage { $0.appendInPlace(other, isASCII: isASCII) }
251273
}
252274

253275
@inline(never) // slow-path
@@ -256,11 +278,7 @@ extension _StringGuts {
256278
_internalInvariant(self.uniqueNativeUnusedCapacity != nil)
257279

258280
var iter = Substring(other).utf8.makeIterator()
259-
self._object.nativeStorage.appendInPlace(&iter, isASCII: other.isASCII)
260-
261-
// We re-initialize from the modified storage to pick up new count, flags,
262-
// etc.
263-
self = _StringGuts(self._object.nativeStorage)
281+
updateNativeStorage { $0.appendInPlace(&iter, isASCII: other.isASCII) }
264282
}
265283

266284
internal mutating func clear() {
@@ -270,8 +288,7 @@ extension _StringGuts {
270288
}
271289

272290
// Reset the count
273-
_object.nativeStorage.clear()
274-
self = _StringGuts(_object.nativeStorage)
291+
updateNativeStorage { $0.clear() }
275292
}
276293

277294
internal mutating func remove(from lower: Index, to upper: Index) {
@@ -281,10 +298,7 @@ extension _StringGuts {
281298
_internalInvariant(lowerOffset <= upperOffset && upperOffset <= self.count)
282299

283300
if isUniqueNative {
284-
_object.nativeStorage.remove(from: lowerOffset, to: upperOffset)
285-
// We re-initialize from the modified storage to pick up new count, flags,
286-
// etc.
287-
self = _StringGuts(self._object.nativeStorage)
301+
updateNativeStorage { $0.remove(from: lowerOffset, to: upperOffset) }
288302
return
289303
}
290304

@@ -412,8 +426,9 @@ extension _StringGuts {
412426

413427
let start = bounds.lowerBound._encodedOffset
414428
let end = bounds.upperBound._encodedOffset
415-
_object.nativeStorage.replace(from: start, to: end, with: codeUnits)
416-
self = _StringGuts(_object.nativeStorage)
429+
updateNativeStorage {
430+
$0.replace(from: start, to: end, with: codeUnits)
431+
}
417432
return Range(_uncheckedBounds: (start, start + codeUnits.count))
418433
}
419434

@@ -435,9 +450,10 @@ extension _StringGuts {
435450

436451
let start = bounds.lowerBound._encodedOffset
437452
let end = bounds.upperBound._encodedOffset
438-
_object.nativeStorage.replace(
439-
from: start, to: end, with: codeUnits, replacementCount: replCount)
440-
self = _StringGuts(_object.nativeStorage)
453+
updateNativeStorage {
454+
$0.replace(
455+
from: start, to: end, with: codeUnits, replacementCount: replCount)
456+
}
441457
return Range(_uncheckedBounds: (start, start + replCount))
442458
}
443459

stdlib/public/core/StringObject.swift

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,17 @@ internal struct _StringObject {
172172
_internalInvariant(!isSmall)
173173
return CountAndFlags(rawUnchecked: _countAndFlagsBits)
174174
}
175+
176+
// FIXME: This ought to be the setter for property `_countAndFlags`.
177+
@_alwaysEmitIntoClient
178+
internal mutating func _setCountAndFlags(to value: CountAndFlags) {
179+
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
180+
self._count = value.count
181+
self._flags = value.flags
182+
#else
183+
self._countAndFlagsBits = value._storage
184+
#endif
185+
}
175186
}
176187

177188
// Raw
@@ -894,6 +905,13 @@ extension _StringObject {
894905
discriminatedObjectRawBits & Nibbles.largeAddressMask)
895906
}
896907

908+
@inline(__always)
909+
@_alwaysEmitIntoClient
910+
internal var largeAddress: UnsafeRawPointer {
911+
UnsafeRawPointer(bitPattern: largeAddressBits)
912+
._unsafelyUnwrappedUnchecked
913+
}
914+
897915
@inlinable @inline(__always)
898916
internal var nativeUTF8Start: UnsafePointer<UInt8> {
899917
_internalInvariant(largeFastIsTailAllocated)
@@ -915,11 +933,12 @@ extension _StringObject {
915933
_internalInvariant(largeFastIsShared)
916934
#if _runtime(_ObjC)
917935
if largeIsCocoa {
918-
return stableCocoaUTF8Pointer(cocoaObject)._unsafelyUnwrappedUnchecked
936+
return withCocoaObject {
937+
stableCocoaUTF8Pointer($0)._unsafelyUnwrappedUnchecked
938+
}
919939
}
920940
#endif
921-
922-
return sharedStorage.start
941+
return withSharedStorage { $0.start }
923942
}
924943

925944
@usableFromInline
@@ -940,7 +959,24 @@ extension _StringObject {
940959
return _unsafeUncheckedDowncast(storage, to: __StringStorage.self)
941960
#else
942961
_internalInvariant(hasNativeStorage)
943-
return Builtin.reinterpretCast(largeAddressBits)
962+
let unmanaged = Unmanaged<__StringStorage>.fromOpaque(largeAddress)
963+
return unmanaged.takeUnretainedValue()
964+
#endif
965+
}
966+
967+
/// Call `body` with the native storage object of `self`, without retaining
968+
/// it.
969+
@inline(__always)
970+
internal func withNativeStorage<R>(
971+
_ body: (__StringStorage) -> R
972+
) -> R {
973+
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
974+
// FIXME: Do this properly.
975+
return body(nativeStorage)
976+
#else
977+
_internalInvariant(hasNativeStorage)
978+
let unmanaged = Unmanaged<__StringStorage>.fromOpaque(largeAddress)
979+
return unmanaged._withUnsafeGuaranteedRef { body($0) }
944980
#endif
945981
}
946982

@@ -954,7 +990,23 @@ extension _StringObject {
954990
#else
955991
_internalInvariant(largeFastIsShared && !largeIsCocoa)
956992
_internalInvariant(hasSharedStorage)
957-
return Builtin.reinterpretCast(largeAddressBits)
993+
let unmanaged = Unmanaged<__SharedStringStorage>.fromOpaque(largeAddress)
994+
return unmanaged.takeUnretainedValue()
995+
#endif
996+
}
997+
998+
@inline(__always)
999+
internal func withSharedStorage<R>(
1000+
_ body: (__SharedStringStorage) -> R
1001+
) -> R {
1002+
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
1003+
// FIXME: Do this properly.
1004+
return body(sharedStorage)
1005+
#else
1006+
_internalInvariant(largeFastIsShared && !largeIsCocoa)
1007+
_internalInvariant(hasSharedStorage)
1008+
let unmanaged = Unmanaged<__SharedStringStorage>.fromOpaque(largeAddress)
1009+
return unmanaged._withUnsafeGuaranteedRef { body($0) }
9581010
#endif
9591011
}
9601012

@@ -967,7 +1019,24 @@ extension _StringObject {
9671019
return object
9681020
#else
9691021
_internalInvariant(largeIsCocoa && !isImmortal)
970-
return Builtin.reinterpretCast(largeAddressBits)
1022+
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
1023+
return unmanaged.takeUnretainedValue()
1024+
#endif
1025+
}
1026+
1027+
/// Call `body` with the bridged Cocoa object in `self`, without retaining
1028+
/// it.
1029+
@inline(__always)
1030+
internal func withCocoaObject<R>(
1031+
_ body: (AnyObject) -> R
1032+
) -> R {
1033+
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
1034+
// FIXME: Do this properly.
1035+
return body(cocoaObject)
1036+
#else
1037+
_internalInvariant(largeIsCocoa && !isImmortal)
1038+
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
1039+
return unmanaged._withUnsafeGuaranteedRef { body($0) }
9711040
#endif
9721041
}
9731042

@@ -976,7 +1045,8 @@ extension _StringObject {
9761045
@inline(__always)
9771046
internal var owner: AnyObject? {
9781047
guard self.isMortal else { return nil }
979-
return Builtin.reinterpretCast(largeAddressBits)
1048+
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
1049+
return unmanaged.takeUnretainedValue()
9801050
}
9811051
}
9821052

@@ -1055,7 +1125,8 @@ extension _StringObject {
10551125
@inline(__always)
10561126
internal var objCBridgeableObject: AnyObject {
10571127
_internalInvariant(hasObjCBridgeableObject)
1058-
return Builtin.reinterpretCast(largeAddressBits)
1128+
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
1129+
return unmanaged.takeUnretainedValue()
10591130
}
10601131

10611132
// Whether the object provides fast UTF-8 contents that are nul-terminated
@@ -1219,7 +1290,8 @@ extension _StringObject {
12191290
}
12201291
}
12211292
if _countAndFlags.isNativelyStored {
1222-
let anyObj = Builtin.reinterpretCast(largeAddressBits) as AnyObject
1293+
let unmanaged = Unmanaged<AnyObject>.fromOpaque(largeAddress)
1294+
let anyObj = unmanaged.takeUnretainedValue()
12231295
_internalInvariant(anyObj is __StringStorage)
12241296
}
12251297
}

stdlib/public/core/StringStorage.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -735,10 +735,11 @@ extension _StringGuts {
735735

736736
let mutPtr: UnsafeMutablePointer<_StringBreadcrumbs?>
737737
if hasNativeStorage {
738-
mutPtr = _object.nativeStorage._breadcrumbsAddress
738+
mutPtr = _object.withNativeStorage { $0._breadcrumbsAddress }
739739
} else {
740-
mutPtr = UnsafeMutablePointer(
741-
Builtin.addressof(&_object.sharedStorage._breadcrumbs))
740+
mutPtr = _object.withSharedStorage {
741+
UnsafeMutablePointer(Builtin.addressof(&$0._breadcrumbs))
742+
}
742743
}
743744

744745
if let breadcrumbs = _stdlib_atomicAcquiringLoadARCRef(object: mutPtr) {

stdlib/public/core/StringUTF8View.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,10 +541,10 @@ extension String.UTF8View {
541541

542542
#if _runtime(_ObjC)
543543
// Currently, foreign means NSString
544-
if let count = _cocoaStringUTF8Count(
545-
_guts._object.cocoaObject,
546-
range: i._encodedOffset ..< j._encodedOffset
547-
) {
544+
let count = _guts._object.withCocoaObject {
545+
_cocoaStringUTF8Count($0, range: i._encodedOffset ..< j._encodedOffset)
546+
}
547+
if let count {
548548
// _cocoaStringUTF8Count gave us the scalar aligned count, but we still
549549
// need to compensate for sub-scalar indexing, e.g. if `i` is in the
550550
// middle of a two-byte UTF8 scalar.

0 commit comments

Comments
 (0)