Skip to content

Commit 57cc924

Browse files
authored
Safe c interop (#1071)
* [cxx-interop] Document C support for safe interop wrappers This adds documentation around __counted_by, __sized_by, API notes, and using lifetime attributes in C. * Focus on lifetime safe variants, rename page title * Address review comments - replace [[clang::noescape]] syntax with __noescape (etc) - specify YAML syntax highlighting for YAML code blocks - consistently use "annotation" in favour of "attribute" - insert HTML table to enable code block in cells
1 parent f0ad561 commit 57cc924

File tree

1 file changed

+221
-20
lines changed
  • documentation/cxx-interop/safe-interop

1 file changed

+221
-20
lines changed

documentation/cxx-interop/safe-interop/index.md

Lines changed: 221 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
layout: page
3-
title: Safely Mixing Swift and C++
3+
title: Safely Mixing Swift and C/C++
44
official_url: https://swift.org/documentation/cxx-interop/safe-interop/
55
redirect_from:
66
- /documentation/cxx-interop/safe-interop.html
@@ -111,9 +111,12 @@ Building the code again will emit a new diagnostic for the `fileName` function a
111111
missing lifetime annotations. C and C++ functions that return non-escapable types need annotations
112112
to describe their lifetime contracts via [lifetimebound](https://clang.llvm.org/docs/AttributeReference.html#id8)
113113
and [lifetime_capture_by](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by) annotations.
114+
Not all versions of C and C++ support the `[[clang::lifetimebound]]` attribute syntax. Convenience macros for
115+
lifetime annotations using the GNU style attribute syntax are available in the `lifetimebound.h` header, and we'll
116+
be using them throughout this document.
114117
115118
```c++
116-
StringRef fileName(const std::string &normalizedPath [[clang::lifetimebound]]);
119+
StringRef fileName(const std::string &normalizedPath __lifetimebound);
117120
```
118121

119122
Adding this annotation to `fileName` indicates that the returned `StringRef` value has the
@@ -186,7 +189,7 @@ types do not need lifetime annotations.
186189

187190
Escapability annotations can also be attached to types via [API Notes](https://clang.llvm.org/docs/APINotes.html):
188191

189-
```
192+
```yaml
190193
Tags:
191194
- Name: NonEscapableType
192195
SwiftEscapable: false
@@ -253,7 +256,7 @@ Annotating the parameters of a constructor describes the lifetime of the created
253256
254257
```c++
255258
struct SWIFT_NONESCAPABLE View {
256-
View(const int *p [[clang::lifetimebound]]) : member(p) {}
259+
View(const int *p __lifetimebound) : member(p) {}
257260
...
258261
};
259262
```
@@ -268,7 +271,7 @@ the same lifetime as the implicit `this` parameter.
268271
struct Owner {
269272
int data;
270273

271-
View handOutView() const [[clang::lifetimebound]] {
274+
View handOutView() const __lifetimebound {
272275
return View(&data);
273276
}
274277
};
@@ -284,10 +287,10 @@ In case the attribute is applied to a subset of the parameters, the return
284287
value might depend on the corresponding arguments:
285288

286289
```c++
287-
View getOneOfTheViews(const Owner &owner1 [[clang::lifetimebound]],
290+
View getOneOfTheViews(const Owner &owner1 __lifetimebound,
288291
const Owner &owner2,
289-
View view1 [[clang::lifetimebound]],
290-
View view2 [[clang::lifetimebound]]) {
292+
View view1 __lifetimebound,
293+
View view2 __lifetimebound) {
291294
if (coinFlip)
292295
return View(&owner1.data);
293296
if (coinFlip)
@@ -313,7 +316,7 @@ Notably, the default constructor of a type is always assumed to create an indepe
313316

314317
We can also attach `lifetimebound` annotations to C and C++ APIs using [API Notes](https://clang.llvm.org/docs/APINotes.html). The `-1` index represents the `this` position.
315318

316-
```
319+
```yaml
317320
Tags:
318321
- Name: MyClass
319322
Methods:
@@ -335,7 +338,7 @@ annotation to describe the lifetime of other output values, like output/inout ar
335338
or globals.
336339

337340
```c++
338-
void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) {
341+
void copyView(View view1 __lifetime_capture_by(view2), View &view2) {
339342
view2 = view1;
340343
}
341344
```
@@ -351,11 +354,11 @@ private:
351354
View containedView;
352355
353356
public:
354-
void captureView(View v [[clang::lifetime_capture_by(this)]]) {
357+
void captureView(View v __lifetime_capture_by(this)) {
355358
containedView = v;
356359
}
357360
358-
void handOut(View &v) const [[clang::lifetime_capture_by(v)]] {
361+
void handOut(View &v) const __lifetime_capture_by(v) {
359362
v = containedView;
360363
}
361364
};
@@ -366,7 +369,7 @@ considered safe. If an input never escapes from the called function we can use
366369
the `noescape` annotation:
367370

368371
```c++
369-
void is_palindrome(std::span<int> s [[clang::noescape]]);
372+
void is_palindrome(std::span<int> s __noescape);
370373
```
371374

372375
The lifetime annotations presented in this sections are powerful,
@@ -375,9 +378,9 @@ APIs with such contracts can still be used from Swift,
375378
but they are imported as unsafe APIs, so that developers are aware
376379
that they need to take extra care when using these APIs to avoid memory safety violations.
377380

378-
## Convenience Overloads for Annotated Spans and Pointers
381+
## Safe Overloads for Annotated Spans and Pointers
379382

380-
C++ APIs often feature parameters that denote a span of memory.
383+
C and C++ APIs often feature parameters that denote a span of memory.
381384
For example, some might have two parameters where one points to a memory buffer
382385
and the other designates the buffer's size; others might use the
383386
[`std::span`](https://en.cppreference.com/w/cpp/container/span) type from the
@@ -386,8 +389,12 @@ compiler can bridge those span-like parameters to Swift's
386389
[`Span`](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0447-span-access-shared-contiguous-storage.md)
387390
and [`MutableSpan`](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0467-MutableSpan.md)
388391
types, and the user with a safe and convenient interface to those imported APIs.
392+
These interfaces are *in addition to* the interfaces generated without annotations:
393+
adding additional annotations to C or C++ APIs will not affect Swift code currently
394+
relying on the plain interface. Adding additional information may alter the signature
395+
of any existing safe overload however, since only 1 safe overload per imported function is generated.
389396

390-
### C++ `std::span` Support
397+
### Safe Overloads for C++ `std::span`
391398

392399
APIs taking or returning C++'s `std::span` with sufficient lifetime
393400
annotations will automatically get safe overloads take or return Swift
@@ -402,11 +409,205 @@ using IntVec = std::vector<int>;
402409

403410
| C++ API | Generated Swift overload |
404411
| --------------------------------------------------------- | -------------------------------------------------------------------- |
405-
| `void takeSpan(IntSpan x [[clang::noescape]]);` | `func takeSpan(_ x: Span<Int32>)` |
406-
| `IntSpan changeSpan(IntSpan x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: Span<Int32>) -> Span<Int32>` |
407-
| `IntSpan changeSpan(IntVec &x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span<Int32>` |
408-
| `IntSpan Owner::getSpan() [[clang::lifetimebound]];` | `@lifetime(self) func getSpan() -> Span<Int32>` |
412+
| `void takeSpan(IntSpan x __noescape);` | `func takeSpan(_ x: Span<Int32>)` |
413+
| `IntSpan changeSpan(IntSpan x __lifetimebound);` | `@lifetime(x) func changeSpan(_ x: Span<Int32>) -> Span<Int32>` |
414+
| `IntSpan changeSpan(IntVec &x __lifetimebound);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span<Int32>` |
415+
| `IntSpan Owner::getSpan() __lifetimebound;` | `@lifetime(self) func getSpan() -> Span<Int32>` |
409416

410417
These transformations only support top-level `std::span`s. The compiler
411418
currently does not transform nested `std::span`s. A `std::span<T>` of a non-const
412419
type `T` is transformed to `MutableSpan<T>` on the Swift side.
420+
421+
### Safe Overloads for Pointers
422+
423+
If an API uses raw pointers rather than `std::span` - perhaps because it's written in C or Objective-C,
424+
or because it's an older C++ API that doesn't want to break backwards compatibility -
425+
it can still receive the same interop safety as `std::span`. This added bounds safety doesn't break
426+
source compatiblity, nor does it affect ABI. Instead it leverages bounds annotations to express the
427+
pointer bounds in terms of other parameters in the function signature.
428+
429+
#### Annotating Pointers with Bounds Annotations
430+
431+
The most common bounds annotation is `__counted_by`. You can apply `__counted_by` to pointer parameters
432+
and return values to indicate the number of elements that the pointer points to, like this:
433+
434+
```c
435+
int calculate_sum(const int * __counted_by(len) values __noescape, int len);
436+
```
437+
438+
In this example, the function signature on the C side hasn't changed, but the `__counted_by(len)`
439+
annotation communicates that the `values` and `len` parameters are related - specifically, `values`
440+
should point to a buffer of at least `len` `int` values. When you annotate a function with a bounds
441+
annotation like this, the compiler will generate a bounds safe overload: in addition to the imported
442+
`func calculate_sum(_ values: UnsafePointer<CInt>, _ len: CInt) -> CInt` signature, you will also
443+
get the an overload with the `func calculate_sum(_ values: Span<CInt>) -> CInt` signature.
444+
Note that, like for `std::span`, the `__noescape` annotation is necessary to get a safe wrapper using `Span`.
445+
This signature is not only more ergonomic to work with - since the generated overload does the unpacking
446+
of base pointer and count for you - but it's also bounds safe. For example, if your API contains parameters
447+
that share a count, the bounds safe overload will check that they all correspond.
448+
449+
```c
450+
void sum_vectors(const int * __counted_by(len) a __noescape,
451+
const int * __counted_by(len) b __noescape,
452+
int * __counted_by(len) out __noescape,
453+
int len);
454+
```
455+
This safe overload will trap if `a.count != b.count || b.count != out.count`:
456+
```swift
457+
func sum_vectors(_ a: Span<CInt>,
458+
_ b: Span<CInt>,
459+
_ out: MutableSpan<CInt>)
460+
```
461+
462+
If the count of the `Span` parameters is larger than the C function expects and you intentionally want to use
463+
only a part of it, you can create a slice using `extracting(_:)`.
464+
465+
If your API has more complex bounds you can express those with an arithmetic expression, like so:
466+
467+
```c
468+
int transpose_matrix(int * __counted_by(columns * rows) values __noescape, int columns, int rows);
469+
```
470+
471+
In this case the `columns` and `rows` parameters can't be elided:
472+
```swift
473+
func transpose_matrix(_ values: MutableSpan<CInt>, _ columns: CInt, _ rows: CInt)
474+
```
475+
This is because there's no way to factor out `columns` and `rows` given only `values.count`.
476+
Instead a bounds check is inserted to verify that `columns * rows == values.count`.
477+
478+
When your C/C++ API uses opaque pointers, void pointers or otherwise pointers to unsized types,
479+
the buffer size can't be described in terms of the number of elements. Instead you can annotate
480+
these pointers with `__sized_by`. This bounds annotation behaves like `__counted_by`, but takes
481+
a parameter describing the *number of bytes* in the buffer rather than the *number of elements*.
482+
Where `__counted_by` maps to `Span<T>`, `__sized_by` will map to
483+
`RawSpan` instead.
484+
485+
You can access the `__counted_by` and `__sized_by` macro definitions by including the `ptrcheck.h` header.
486+
For more information about these annotations, see Clang's [bounds safety documentation](https://clang.llvm.org/docs/BoundsSafety.html).
487+
If the C code base is compiled with `-fbounds-safety`, bounds safety is enforced on the C side as well -
488+
otherwise it is only enforced at the interop boundary.
489+
490+
#### Lifetime Annotations for Pointers
491+
492+
Like with `std::span`, pointers with bounds annotations can have their safe overloads map to
493+
`Span`/`MutableSpan` when annotated with the appropriate lifetime annotations from the `lifetimebound.h` header.
494+
Unlike `std::span`, pointers with bounds annotations also get a bounds safe overload when they lack lifetime annotations:
495+
496+
<table>
497+
<tr><td> C API </td> <td> Generated Swift overload </td></tr>
498+
499+
<tr>
500+
<td markdown=1>
501+
```c
502+
void
503+
take_ptr_lifetime(
504+
const int * __counted_by(len) x __noescape,
505+
int len);
506+
```
507+
</td>
508+
<td markdown=1>
509+
```swift
510+
func take_ptr_lifetime(_ x: Span<Int32>)
511+
```
512+
</td>
513+
</tr>
514+
515+
<tr>
516+
<td markdown=1>
517+
```c
518+
const int * __counted_by(len)
519+
change_ptr_lifetime(
520+
const int * __counted_by(len) x __lifetimebound,
521+
int len);
522+
```
523+
</td>
524+
<td markdown=1>
525+
```swift
526+
@lifetime(x)
527+
func change_ptr_lifetime(_ x: Span<Int32>)
528+
-> Span<Int32>
529+
```
530+
</td>
531+
</tr>
532+
533+
<tr>
534+
<td markdown=1>
535+
```c
536+
void
537+
take_ptr(
538+
const int * __counted_by(len) x,
539+
int len);
540+
```
541+
</td>
542+
<td markdown=1>
543+
```swift
544+
func take_ptr(_ x: UnsafeBufferPointer<Int32>)
545+
```
546+
</td>
547+
</tr>
548+
549+
<tr>
550+
<td markdown=1>
551+
```c
552+
const int * __counted_by(len)
553+
change_ptr(
554+
const int * __counted_by(len) x,
555+
int len);
556+
```
557+
</td>
558+
<td markdown=1>
559+
```swift
560+
@lifetime(x)
561+
func change_ptr(_ x: UnsafeBufferPointer<Int32>)
562+
-> UnsafeBufferPointer<Int32>
563+
```
564+
</td>
565+
</tr>
566+
567+
</table>
568+
569+
The `UnsafeBufferPointer` overloads provide the same bounds safety as their `Span` equvalents
570+
(NB: `UnsafeBufferPointer` is not bounds checked on memory access in release builds, but the generated
571+
`UnsafeBufferPointer` overloads contain bounds checks in the same cases as the `Span` overloads, *even in release builds*),
572+
but without lifetime safety. If lifetime information is available the generated safe overload will always
573+
choose to use `Span` - no `UnsafeBufferPointer` overload will be generated in this case. This means
574+
that existing callers are not affected by annotating an API with `__counted_by`, but callers using the
575+
safe overload after adding `__counted_by` *will* be affected if `__noescape` is also added later on, or
576+
if another parameter is then also annotated with `__counted_by`.
577+
To prevent source breaking changes, make sure to fully annotate the bounds and lifetimes of an API when
578+
adding any bounds or lifetime annotations.
579+
580+
#### Bounds Annotations using API Notes
581+
582+
In cases where you don't want to modify the imported headers, bounds annotations can be applied using API Notes.
583+
Given the following header:
584+
```c
585+
void foo(int *p, int len);
586+
void *bar(int size);
587+
```
588+
We can provide bounds annotations in our API note file like this:
589+
```yaml
590+
Functions:
591+
- Name: foo
592+
Parameters:
593+
- Position: 0
594+
BoundsSafety:
595+
Kind: counted_by
596+
BoundedBy: "len"
597+
- Name: bar
598+
BoundsSafety:
599+
Kind: sized_by
600+
BoundedBy: "size"
601+
```
602+
603+
#### Limitations
604+
Bounds annotations are not supported for nested pointers: only the outermost pointer can be transformed.
605+
606+
[`lifetime_capture_by`](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by)
607+
is currently not taken into account when generating safe overloads.
608+
609+
Bounds annotations on global variables or struct fields are ignored: only parameters and return values
610+
are considered.
611+
612+
Bounds annotations, while supported in Objective-C code bases, are not currently supported in Objective-C
613+
class method signatures.

0 commit comments

Comments
 (0)