Skip to content

Commit 49eddbf

Browse files
committed
[stdlib] Fix bug in small string uninitialized init
Fix a bug and enforce the invariant that all bits between the last code unit and the descriminator in a small string should be unset.
1 parent 4faa397 commit 49eddbf

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

stdlib/public/core/SmallString.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ extension _SmallString {
131131
internal func _invariantCheck() {
132132
_internalInvariant(count <= _SmallString.capacity)
133133
_internalInvariant(isASCII == computeIsASCII())
134+
135+
// No bits should be set between the last code unit and the discriminator
136+
var copy = self
137+
withUnsafeBytes(of: &copy._storage) {
138+
_internalInvariant(
139+
$0[count..<_SmallString.capacity].allSatisfy { $0 == 0 })
140+
}
134141
}
135142
#endif // INTERNAL_CHECKS_ENABLED
136143

@@ -213,14 +220,19 @@ extension _SmallString {
213220
}
214221

215222
// Overwrite stored code units, including uninitialized. `f` should return the
216-
// new count.
223+
// new count. This will re-establish the invariant after `f` that all bits
224+
// between the last code unit and the discriminator are unset.
217225
@inline(__always)
218-
internal mutating func withMutableCapacity(
226+
fileprivate mutating func withMutableCapacity(
219227
_ f: (UnsafeMutableRawBufferPointer) throws -> Int
220228
) rethrows {
221229
let len = try withUnsafeMutableBytes(of: &self._storage) {
222230
(rawBufPtr: UnsafeMutableRawBufferPointer) -> Int in
223-
return try f(rawBufPtr)
231+
let len = try f(rawBufPtr)
232+
UnsafeMutableRawBufferPointer(
233+
rebasing: rawBufPtr[len...]
234+
).initializeMemory(as: UInt8.self, repeating: 0)
235+
return len
224236
}
225237
if len == 0 {
226238
self = _SmallString()

test/stdlib/StringCreate.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,27 @@ if #available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
8080
expectTrue(empty.isEmpty)
8181
}
8282
}
83+
84+
if #available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
85+
StringCreateTests.test("Small string unsafeUninitializedCapacity") {
86+
let str1 = "42"
87+
let str2 = String(42)
88+
expectEqual(str1, str2)
89+
90+
let str3 = String(unsafeUninitializedCapacity: 2) {
91+
$0[0] = UInt8(ascii: "4")
92+
$0[1] = UInt8(ascii: "2")
93+
return 2
94+
}
95+
expectEqual(str1, str3)
96+
97+
// Write into uninitialized space
98+
let str4 = String(unsafeUninitializedCapacity: 3) {
99+
$0[0] = UInt8(ascii: "4")
100+
$0[1] = UInt8(ascii: "2")
101+
$0[2] = UInt8(ascii: "X")
102+
return 2
103+
}
104+
expectEqual(str1, str4)
105+
}
106+
}

0 commit comments

Comments
 (0)