Skip to content

Commit 92c2c9a

Browse files
committed
Bring swapAt example forward and talk through why the annotations exist
The code examples early in the presentation are fairly short because they illustrate narrow points. Bring forward the `swapAt` example and talk through why the various `unsafe` expressions make sense. Also note why the function itself still needs to be `@unsafe` even though it's trying to be safe.
1 parent 9e763e7 commit 92c2c9a

File tree

1 file changed

+36
-10
lines changed

1 file changed

+36
-10
lines changed

proposals/nnnn-strict-memory-safety.md

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
## Introduction
1212

13-
[Memory safety](https://en.wikipedia.org/wiki/Memory_safety) is a property of programming languages and their implementations that prevents programmer errors from manifesting as [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) at runtime. Undefined behavior effectively breaks the semantic model of a language, with unpredictable results including crashes, data corruption, and otherwise-impossible program states. Such behavior can lead to hard-to-reproduce bugs as well as introduce security vulnerabilities. Various studies have shown that memory safety problems in C and C++ account for around [70% of security vulnerabilities in software](https://www.cisa.gov/sites/default/files/2023-12/The-Case-for-Memory-Safe-Roadmaps-508c.pdf).
13+
[Memory safety](https://en.wikipedia.org/wiki/Memory_safety) is a property of programming languages and their implementations that prevents programmer errors from manifesting as [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) at runtime. Undefined behavior effectively breaks the semantic model of a language, with unpredictable results including crashes, data corruption, and otherwise-impossible program states. Such behavior can lead to hard-to-reproduce bugs as well as introduce security vulnerabilities.
1414

1515
Swift provides memory safety with a combination of language affordances and runtime checking. However, Swift also deliberately includes some unsafe constructs, such as the `Unsafe` pointer types in the standard library, language features like `nonisolated(unsafe)`, and interoperability with unsafe languages like C. For most Swift developers, this is a pragmatic solution that provides an appropriate level of memory safety while not getting in the way.
1616

@@ -106,9 +106,35 @@ extension Array<Int> {
106106
}
107107
```
108108

109-
The `unsafe` keyword here indicates the presence of unsafe code within that expression. As with `try` and `await`, it can cover multiple sources of unsafety within that expression: the call to `c_library_sum_function` is unsafe, as is the use of `buffer` and `buffer.baseAddress`, yet they are all covered by one `unsafe`.
109+
The `unsafe` keyword here indicates the presence of unsafe code within that expression. As with `try` and `await`, it can cover multiple sources of unsafety within that expression: the call to `c_library_sum_function` is unsafe, as is the use of `buffer` and `buffer.baseAddress`, yet they are all covered by one `unsafe`.
110110

111-
Note that we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe`. Additionally, the `@unsafe` attribute is available for all Swift code, even if it doesn't itself enable the strict safety checking described in this proposal.
111+
Unlike `try`, `unsafe` doesn't propagate outward: we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. Similarly, the call to `withUnsafeBufferPointerSimplified` is unsafe because it involves the `UnsafeBufferPointer` type, not because it was passed a closure containing `unsafe` behavior. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe` if the signature doesn't contain any unsafe types. Additionally, the `@unsafe` attribute and `unsafe` expression is available for all Swift code, even if it doesn't itself enable the strict safety checking described in this proposal.
112+
113+
### A larger example: `swapAt` on unsafe pointers
114+
115+
The operation `UnsafeMutableBufferPointer.swapAt` swaps the values at the given two indices in the buffer. Under the proposed strict safety mode, it would look like this:
116+
117+
```swift
118+
extension UnsafeMutableBufferPointer {
119+
@unsafe public func swapAt(_ i: Element, _ j: Element) {
120+
guard i != j else { return }
121+
precondition(i >= 0 && j >= 0)
122+
precondition(unsafe i < endIndex && j < endIndex)
123+
let pi = unsafe (baseAddress! + i)
124+
let pj = unsafe (baseAddress! + j)
125+
let tmp = unsafe pi.move()
126+
unsafe pi.moveInitialize(from: pj, count: 1)
127+
unsafe pj.initialize(to: tmp)
128+
}
129+
}
130+
```
131+
132+
The `swapAt` implementation uses a mix of safe and unsafe code. The code marked with `unsafe` identifies operations that Swift cannot verify memory safety for:
133+
134+
* Performing pointer arithmetic on `baseAddress`: Swift cannot reason about the lifetime of that underlying pointer, nor whether the resulting pointer is still within the bounds of the allocation.
135+
* Moving and initializing the actual elements. The elements need to already be initialized.
136+
137+
The code itself has preconditions to ensure that the provided indices aren't out of bounds before performing the pointer arithmetic. However, there are other safety properties that cannot be checked with preconditions: that the memory associated with the pointer has been properly initialized, has a lifetime that spans the whole call, and is not being used simultaneously by any other part of the code. These safety properties are something that must be established by the *caller* of `swapAt`. Therefore, `swapAt` is marked `@unsafe` because callers of it need to reason about these properties.
112138

113139
### Incremental adoption
114140

@@ -435,13 +461,13 @@ A function marked `@unsafe` is unsafe to use, so any clients that have enabled s
435461

436462
```swift
437463
extension UnsafeMutableBufferPointer {
438-
@unsafe public func swapAt(_ i: Int, _ j: Int) {
464+
@unsafe public func swapAt(_ i: Element, _ j: Element) {
439465
guard i != j else { return }
440466
precondition(i >= 0 && j >= 0)
441467
precondition(unsafe i < endIndex && j < endIndex)
442-
@unsafe let pi = unsafe (_position! + i)
443-
@unsafe let pj = unsafe (_position! + j)
444-
@unsafe let tmp = unsafe pi.move()
468+
let pi = unsafe (baseAddress! + i)
469+
let pj = unsafe (baseAddress! + j)
470+
let tmp = unsafe pi.move()
445471
unsafe pi.moveInitialize(from: pj, count: 1)
446472
unsafe pj.initialize(to: tmp)
447473
}
@@ -452,12 +478,12 @@ Now, it is very likely that an `@unsafe` function is going to make use of other
452478

453479
```swift
454480
extension UnsafeMutableBufferPointer {
455-
@unsafe public func swapAt(_ i: Int, _ j: Int) {
481+
@unsafe public func swapAt(_ i: Element, _ j: Element) {
456482
guard i != j else { return }
457483
precondition(i >= 0 && j >= 0)
458484
precondition(i < endIndex && j < endIndex)
459-
let pi = (_position! + i)
460-
let pj = (_position! + j)
485+
let pi = (baseAddress! + i)
486+
let pj = (baseAddress! + j)
461487
let tmp = pi.move()
462488
pi.moveInitialize(from: pj, count: 1)
463489
pj.initialize(to: tmp)

0 commit comments

Comments
 (0)