Skip to content

Commit acb5c3e

Browse files
egorzhdanGabor Horvath
and
Gabor Horvath
committed
[cxx-interop] Add initial documentation for strict memory safety
99% of this document is taken from Gabor's PR: swiftlang#958. Co-authored-by: Gabor Horvath <[email protected]>
1 parent 536562e commit acb5c3e

File tree

1 file changed

+398
-0
lines changed
  • documentation/cxx-interop/safe-interop

1 file changed

+398
-0
lines changed
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
---
2+
layout: page
3+
title: Mixing Strict Memory Safe Swift and C++
4+
official_url: https://swift.org/documentation/cxx-interop/safe-interop/
5+
redirect_from:
6+
- /documentation/cxx-interop/safe-interop.html
7+
---
8+
9+
## Table of Contents
10+
{:.no_toc}
11+
12+
* TOC
13+
{:toc}
14+
15+
## Introduction
16+
17+
Swift 6.2 introduces [strict memory safety mode](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md),
18+
an opt-in language feature that prevents common memory safety issues by running
19+
extra compile-time safety checks and emitting diagnostics.
20+
21+
This document describes the way Swift/C++ interoperability works when strict
22+
safe mode is enabled in Swift.
23+
24+
* * *
25+
26+
<div class="info" markdown="1">
27+
C++ interoperability is an actively evolving feature of Swift.
28+
Future releases of Swift might change how Swift and C++
29+
interoperate,
30+
as the Swift community gathers feedback from real world adoption of C++
31+
interoperability in mixed Swift and C++ codebases.
32+
Please provide the feedback that you have on the
33+
[Swift Forums](https://forums.swift.org/c/development/c-interoperability/), or
34+
by filing an [issue on GitHub](https://github.com/swiftlang/swift/issues/new/choose).
35+
Future changes to the design or functionality of C++ interoperability will not
36+
break code in existing codebases [by default](#source-stability-guarantees-for-mixed-language-codebases).
37+
</div>
38+
39+
## Overview
40+
41+
Swift provides memory safety with a combination of language affordances and runtime checking.
42+
However, Swift also deliberately includes some unsafe constructs, such as the `UnsafePointer` and `UnsafeMutablePointer`
43+
types in the standard library.
44+
In some cases, Swift needs additional information that is not present in the C++ type and API declarations
45+
to safely interface with them. This document describes how such code needs to be annotated.
46+
47+
### Annotating Foreign Types
48+
49+
Types imported from C++ are considered foreign to Swift. Normally, most C++ types are imported into Swift
50+
without any restriction. However, a small set of C++ APIs e.g. pointers/references and methods returning
51+
pointers will be imported as unsafe (section [Working with C++ references and view types in Swift](https://www.swift.org/documentation/cxx-interop/#working-with-c-references-and-view-types-in-swift)
52+
explains this in more detail.) Under the strict memory safe mode, the compiler will flip the polarity and
53+
treat all types that are not known to be safe as unsafe, and will diagnose uses of them. In this section,
54+
we will show how to annotate unsafe C++ types so that they can be accessed safely and correctly from Swift.
55+
Note that the features here are agnostic to whether strictly-safe mode is on or off. When the strictly safe
56+
mode is on, the compiler warnings can serve as a guide to properly annotate C++ types and also help ensure
57+
that the code doesn't use unsafe APIs anywhere. When the strictly-memory-safe mode is off, it is still
58+
recommended to adopt these annotation wherever appropriate, especially on C++ types that are potentially
59+
lifetime dependent on other objects.
60+
61+
Under strictly-memory-safe mode the built-in integral types like `int`, some standard library types like `std::string`,
62+
and aggregate types built from other safe types are considered safe. Whereas all other unannotated types
63+
are considered unsafe. Let's see what happens when we are trying to use an unannotated type in
64+
strictly-safe mode. Consider the following C++ type and APIs:
65+
66+
```c++
67+
class StringRef {
68+
public:
69+
...
70+
private:
71+
const char* ptr;
72+
size_t len;
73+
};
74+
75+
std::string normalize(const std::string& path);
76+
77+
StringRef fileName(const std::string& normalizedPath);
78+
```
79+
80+
Let's try to use them from Swift with strict memory safety enabled:
81+
82+
```swift
83+
func getFileName(_ path: borrowing std.string) -> StringRef {
84+
let normalizedPath = normalize(path)
85+
return fileName(normalizedPath)
86+
}
87+
```
88+
89+
Building this code will emit a warning that the `fileName` call is unsafe because
90+
it references the unsafe type `StringRef`. Swift considers `StringRef` unsafe because
91+
it has a pointer member. Types like `StringRef` can dangle, so we need to take extra
92+
care using them, making sure the referenced buffer outlives the `StringRef` object.
93+
94+
Swift's [non-escapable types](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0446-non-escapable.md)
95+
can also have lifetime dependencies, just like `StringRef`. However, the Swift compiler
96+
can track these dependencies and enforce safety at compile time. To import `StringRef`
97+
as a safe type we need to mark it as a non-escapable type, we can annotate the class
98+
definition:
99+
100+
```c++
101+
class SWIFT_NONESCAPABLE StringRef { ... };
102+
```
103+
104+
Now the Swift compiler imports `StringRef` as a safe type and no longer
105+
emits a warning about using an unsafe type.
106+
107+
### Annotating C++ APIs
108+
109+
Building the code again will emit a new diagnostic for the `fileName` function about
110+
missing lifetime annotations. Functions returning non-escapable types need annotations
111+
to describe their lifetime contracts via [lifetimebound](https://clang.llvm.org/docs/AttributeReference.html#id11)
112+
and [lifetime_capture_by](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by) annotations.
113+
114+
```c++
115+
StringRef fileName(const std::string& normalizedPath [[clang::lifetimebound]]);
116+
```
117+
118+
Adding this annotation to `fileName` indicates that the returned `StringRef` value has the
119+
same lifetime as the argument of the `fileName` function.
120+
121+
Building the project again reveals a lifetime error in the Swift function:
122+
123+
```swift
124+
func getFileName(_ path: borrowing std.string) -> StringRef {
125+
let normalizedPath = normalize(path)
126+
// error: lifetime-dependent value escapes local scope
127+
// note: depends on `normalizedPath`
128+
return fileName(normalizedPath)
129+
}
130+
```
131+
132+
The value returned by `fileName` will dangle after the lifetime of `normalizedPath` ends.
133+
We can fix this error by pushing the task of normalizing a path to the callee:
134+
135+
```swift
136+
// Path needs to be normalized.
137+
func getFileName(_ path: borrowing std.string) -> StringRef {
138+
return fileName(normalizedPath)
139+
}
140+
```
141+
142+
Or we could return an `Escapable` value like `std.string` instead of a dangling `StringRef`:
143+
144+
```swift
145+
func getFileName(_ path: borrowing std.string) -> std.string {
146+
let normalizedPath = normalize(path)
147+
let ref = fileName(normalizedPath)
148+
return ref.toString()
149+
}
150+
```
151+
152+
After annotating the C++ code, the Swift compiler can enforce the lifetime
153+
contracts helping us to write code that is free of memory safety errors.
154+
155+
## Escapability Annotations in Detail
156+
157+
Under the strictly safe mode, even though compiler warns on unannotated types,
158+
they are imported as if they are `Escapable` to maintain backward
159+
compatibility. This might change in the future under a new interoperability version.
160+
We have already seen that we can import a type as `~Escapable` to Swift by adding
161+
the `SWIFT_NONESCAPABLE` annotation:
162+
163+
```c++
164+
struct SWIFT_NONESCAPABLE View {
165+
View(const int *p) : member(p) {}
166+
private:
167+
const int *member;
168+
};
169+
```
170+
171+
Moreover, we can explicitly mark types as `Escapable` using the `SWIFT_ESCAPABLE`
172+
annotation to express that they are not lifetime dependent on any other values:
173+
174+
```c++
175+
struct SWIFT_ESCAPABLE Owner { ... };
176+
```
177+
178+
The main reason for explicitly annotating a type as `SWIFT_ESCAPABLE` is to make sure
179+
it is considered as a safe type when used from Swift. Functions returning escapable
180+
types do not need lifetime annotations.
181+
182+
Escapability annotations can also be attached to types via API Notes:
183+
184+
```
185+
Tags:
186+
- Name: NonEscapableType
187+
SwiftEscapable: false
188+
- Name: EscapableType
189+
SwiftEscapable: true
190+
```
191+
192+
In case of template instantiations the escapability of a type can depend on the
193+
template arguments:
194+
195+
```c++
196+
MyList<View> f();
197+
MyList<Owner> g();
198+
```
199+
200+
In this example, `MyList<View>` should be imported as `~Escapable` while `MyList<Owner>`
201+
should be imported as `Escapable`. This can be achieved via conditional escapability
202+
annotations:
203+
204+
```c++
205+
template<typename T>
206+
struct SWIFT_ESCAPABLE_IF(T) MyList {
207+
...
208+
};
209+
```
210+
211+
Here, instantiations of `MyList` are imported as `Escapable` when `T` is substituted
212+
with an `Escapable` type.
213+
214+
The `SWIFT_ESCAPABLE_IF` macro can take multiple template parameters:
215+
216+
```c++
217+
template<typename F, typename S>
218+
struct SWIFT_ESCAPABLE_IF(F, S) MyPair {
219+
F first;
220+
S second;
221+
};
222+
```
223+
224+
`MyPair` instantiations are only imported as `Escapable` if both template arguments
225+
are `Escapable`.
226+
227+
`Escapable` types cannot have `~Escapable` fields. The following code snippet will
228+
trigger a compiler error:
229+
230+
```c++
231+
struct SWIFT_NONESCAPABLE View { ... };
232+
struct SWIFT_ESCAPABLE Owner {
233+
View v;
234+
};
235+
```
236+
237+
Escapability annotations will not only help the Swift compiler to import C++ types
238+
safely, it will also help discover missing lifetime annotations as all `~Escapable`
239+
parameters and return values need to be annotated in an API to make its use safe in
240+
Swift.
241+
242+
## Lifetime Annotations in Detail
243+
244+
The `lifetimebound` attribute on a function parameter or implicit object parameter
245+
indicates that the returned object's lifetime could end when any of the `lifetimebound`
246+
annotated parameters' lifetime ended.
247+
This annotation a constructor describes the lifetime of the created object:
248+
249+
```c++
250+
struct SWIFT_NONESCAPABLE View {
251+
View(const int *p [[clang::lifetimebound]]) : member(p) {}
252+
...
253+
};
254+
```
255+
256+
In this example, the object initialized by the `View` constructor has the same
257+
lifetime as the input argument of the constructor.
258+
259+
In case the attribute is after the method signature, the returned object has
260+
the same lifetime as the implicit `this` parameter.
261+
262+
```c++
263+
struct Owner {
264+
int data;
265+
266+
View handOutView() const [[clang::lifetimebound]] {
267+
return View(&data);
268+
}
269+
};
270+
```
271+
272+
Consider a call site like `View v = o.handOutView()`. The `v` object has the same lifetime
273+
as `o`.
274+
275+
In case the attribute is applied to a subset of the parameters, the return
276+
value might depend on the corresponding arguments:
277+
278+
```c++
279+
View getOneOfTheViews(const Owner& owner1 [[clang::lifetimebound]], const Owner& owner2
280+
View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) {
281+
if (coinFlip)
282+
return View(&owner1.data);
283+
if (coinFlip)
284+
return view1;
285+
else
286+
return view2;
287+
}
288+
```
289+
290+
Here, the returned `View`'s lifetime depends on `owner`, `view1`, and `view2` but it cannot
291+
depend on `owner2`.
292+
293+
Occasionally, a function might return a non-escapable type that has no dependency on any other values.
294+
These types might point to static data or might represent an empty sequence or lack of data.
295+
Such functions need to be annotated with `SWIFT_RETURNS_INDEPENDENT_VALUE`:
296+
297+
```c++
298+
View returnsEmpty() SWIFT_RETURNS_INDEPENDENT_VALUE {
299+
return View();
300+
}
301+
```
302+
303+
Notably, the default constructor of a type is always assumed to create an independent value.
304+
305+
We can also annotate `lifetimebound` APIs via APINotes. The `-1` index represents the `this` position.
306+
307+
```
308+
Tags:
309+
- Name: MyClass
310+
Methods:
311+
- Name: annotateThis
312+
Parameters:
313+
- Position: -1
314+
Lifetimebound: true
315+
- Name: methodToAnnotate
316+
Parameters:
317+
- Position: 0
318+
Lifetimebound: true
319+
```
320+
321+
Note that APINotes have some limitations around C++, they do not support overloaded functions.
322+
323+
While `lifetimebound` always describes the lifetime dependencies of the return value (or
324+
the constructed object in case of constructors), we can use can use `lifetime_capture_by`
325+
annotation to describe the lifetime of other output values, like output/inout arguments
326+
or globals.
327+
328+
```c++
329+
void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) {
330+
view2 = view1;
331+
}
332+
```
333+
334+
In this example, `view2` will have get all of the lifetime dependencies of `view1`
335+
after a call to `copyView`. a
336+
337+
We can annotate dependency captured by the implicit `this` object, or
338+
an inout argument capturing `this`:
339+
340+
```c++
341+
struct SWIFT_NONESCAPABLE CaptureView {
342+
void captureView(View v [[clang::lifetime_capture_by(this)]]) {
343+
view = v;
344+
}
345+
346+
void handOut(View &v) const [[clang::lifetime_capture_by(v)]] {
347+
v = view;
348+
}
349+
350+
View view;
351+
};
352+
```
353+
354+
All of the non-escapable inputs need lifetime annotations for a function to be
355+
considered safe. If an input never escapes from the called function we can use
356+
the `noescape` annotation:
357+
358+
```c++
359+
void is_palindrome(std::span<int> s [[clang::noescape]]);
360+
```
361+
362+
While the annotations in this section are powerful, they cannot express all of
363+
the lifetime contracts. APIs with inexpressible contracts can be used from Swift,
364+
but they are imported as unsafe APIs and need extra care from the developers
365+
to manually guarantee safety.
366+
367+
## Convenience Overloads for Annotated Spans and Pointers
368+
369+
C++ APIs often using standard library types or other constructs like a
370+
pointer and a size to represent buffers that have Swift equivalents like
371+
Swift's `Span` type. These Swift types have additional requirements and
372+
guarantees. When these properties are properly annotated on the C++ side,
373+
the Swift compiler can introduce safe convenience functions to make
374+
interacting with the C++ APIs as effortless as if they were written in Swift.
375+
376+
### C++ `std::span` Support
377+
378+
APIs taking or returning C++'s `std::span` with sufficient lifetime
379+
annotations will automatically get overloads taking/returning Swift
380+
`Span`.
381+
382+
The following table summarizes the generated convenience overloads:
383+
384+
```c++
385+
using IntSpan = std::span<const int>;
386+
using IntVec = std::vector<int>;
387+
```
388+
389+
| C++ API | Generated Swift overload |
390+
| --------------------------------------------------------- | -------------------------------------------------------------------- |
391+
| `void takeSpan(IntSpan x [[clang::noescape]]);` | `func takeSpan(_ x: Span<Int32>)` |
392+
| `IntSpan changeSpan(IntSpan x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: Span<Int32>) -> Span<Int32>` |
393+
| `IntSpan changeSpan(IntVec& x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span<Int32>` |
394+
| `IntSpan Owner::getSpan() [[clang::lifetimebound]];` | `@lifetime(self) func getSpan() -> Span<Int32>` |
395+
396+
These transformations only support top level `std::span`s. The compiler
397+
currently does not transform nested `std::span`s. A `std::span` of a non-const
398+
type `T` is transformed to `MutableSpan<T>` on the Swift wide.

0 commit comments

Comments
 (0)