Skip to content

Commit 79cd992

Browse files
committed
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 c914fe7 commit 79cd992

File tree

1 file changed

+111
-47
lines changed
  • documentation/cxx-interop/safe-interop

1 file changed

+111
-47
lines changed

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

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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,
@@ -406,10 +409,10 @@ using IntVec = std::vector<int>;
406409

407410
| C++ API | Generated Swift overload |
408411
| --------------------------------------------------------- | -------------------------------------------------------------------- |
409-
| `void takeSpan(IntSpan x [[clang::noescape]]);` | `func takeSpan(_ x: Span<Int32>)` |
410-
| `IntSpan changeSpan(IntSpan x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: Span<Int32>) -> Span<Int32>` |
411-
| `IntSpan changeSpan(IntVec &x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span<Int32>` |
412-
| `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>` |
413416

414417
These transformations only support top-level `std::span`s. The compiler
415418
currently does not transform nested `std::span`s. A `std::span<T>` of a non-const
@@ -420,32 +423,33 @@ type `T` is transformed to `MutableSpan<T>` on the Swift side.
420423
If an API uses raw pointers rather than `std::span` - perhaps because it's written in C or Objective-C,
421424
or because it's an older C++ API that doesn't want to break backwards compatibility -
422425
it can still receive the same interop safety as `std::span`. This added bounds safety doesn't break
423-
source compatiblity, nor does it affect ABI. Instead it leverages bounds attributes to express the
426+
source compatiblity, nor does it affect ABI. Instead it leverages bounds annotations to express the
424427
pointer bounds in terms of other parameters in the function signature.
425428

426-
#### Annotating Pointers with Bounds Attributes
429+
#### Annotating Pointers with Bounds Annotations
427430

428-
The most common bounds attribute is `__counted_by`. You can apply `__counted_by` to pointer parameters
431+
The most common bounds annotation is `__counted_by`. You can apply `__counted_by` to pointer parameters
429432
and return values to indicate the number of elements that the pointer points to, like this:
430433

431434
```c
432-
int calculate_sum(const int * __counted_by(len) values [[clang::noescape]], int len);
435+
int calculate_sum(const int * __counted_by(len) values __noescape, int len);
433436
```
434437

435-
In this example the function signature on the C side hasn't changed, but the `__counted_by(len)`
438+
In this example, the function signature on the C side hasn't changed, but the `__counted_by(len)`
436439
annotation communicates that the `values` and `len` parameters are related - specifically, `values`
437440
should point to a buffer of at least `len` `int` values. When you annotate a function with a bounds
438-
attribute like this, the compiler will generate a bounds safe overload: in addition to the imported
441+
annotation like this, the compiler will generate a bounds safe overload: in addition to the imported
439442
`func calculate_sum(_ values: UnsafePointer<CInt>, _ len: CInt) -> CInt` signature, you will also
440443
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`.
441445
This signature is not only more ergonomic to work with - since the generated overload does the unpacking
442446
of base pointer and count for you - but it's also bounds safe. For example, if your API contains parameters
443447
that share a count, the bounds safe overload will check that they all correspond.
444448

445449
```c
446-
void sum_vectors(const int * __counted_by(len) a [[clang::noescape]],
447-
const int * __counted_by(len) b [[clang::noescape]],
448-
int * __counted_by(len) out [[clang::noescape]],
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,
449453
int len);
450454
```
451455
This safe overload will trap if `a.count != b.count || b.count != out.count`:
@@ -455,13 +459,13 @@ func sum_vectors(_ a: Span<CInt>,
455459
_ out: MutableSpan<CInt>)
456460
```
457461

458-
If one of the `Span` parameters is larger than the others and you intentionally want to use
462+
If the count of the `Span` parameters is larger than the C function expects and you intentionally want to use
459463
only a part of it, you can create a slice using `extracting(_:)`.
460464

461465
If your API has more complex bounds you can express those with an arithmetic expression, like so:
462466

463467
```c
464-
int transpose_matrix(int * __counted_by(columns * rows) values [[clang::noescape]], int columns, int rows);
468+
int transpose_matrix(int * __counted_by(columns * rows) values __noescape, int columns, int rows);
465469
```
466470

467471
In this case the `columns` and `rows` parameters can't be elided:
@@ -473,35 +477,94 @@ Instead a bounds check is inserted to verify that `columns * rows == values.coun
473477

474478
When your C/C++ API uses opaque pointers, void pointers or otherwise pointers to unsized types,
475479
the buffer size can't be described in terms of the number of elements. Instead you can annotate
476-
these pointers with `__sized_by`. This bounds attribute behaves like `__counted_by`, but takes
480+
these pointers with `__sized_by`. This bounds annotation behaves like `__counted_by`, but takes
477481
a parameter describing the *number of bytes* in the buffer rather than the *number of elements*.
478482
Where `__counted_by` maps to `Span<T>`, `__sized_by` will map to
479483
`RawSpan` instead.
480484

481485
You can access the `__counted_by` and `__sized_by` macro definitions by including the `ptrcheck.h` header.
482486
For more information about these annotations, see Clang's [bounds safety documentation](https://clang.llvm.org/docs/BoundsSafety.html).
483-
If the C code base is compiled with `-fbounds-safety` bounds safety is enforced on the C side as well -
487+
If the C code base is compiled with `-fbounds-safety`, bounds safety is enforced on the C side as well -
484488
otherwise it is only enforced at the interop boundary.
485489

486490
#### Lifetime Annotations for Pointers
487491

488492
Like with `std::span`, pointers with bounds annotations can have their safe overloads map to
489-
`Span`/`MutableSpan` when annotated with the appropriate lifetime annotations. Not all C versions
490-
support the `[[clang::noescape]]` attribute syntax, so convenience macros for lifetime attributes
491-
using the GNU style syntax are available in the `lifetimebound.h` header. Unlike `std::span`, pointers
492-
with bounds annotations also get a bounds safe overload when they lack lifetime annotations:
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>
493498

499+
<tr>
500+
<td markdown=1>
494501
```c
495-
#include <ptrcheck.h>
496-
#include <lifetimebound.h>
502+
void
503+
take_ptr_lifetime(
504+
const int * __counted_by(len) x __noescape,
505+
int len);
497506
```
507+
</td>
508+
<td markdown=1>
509+
```swift
510+
func take_ptr_lifetime(_ x: Span<Int32>)
511+
```
512+
</td>
513+
</tr>
498514

499-
| C API | Generated Swift overload |
500-
| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
501-
| `void take_ptr_lifetime(const int * __counted_by(len) x __noescape, int len);` | `func take_ptr_lifetime(_ x: Span<Int32>)` |
502-
| `const int * __counted_by(len) change_ptr_lifetime(const int * __counted_by(len) x __lifetimebound, int len);` | `@lifetime(x) func change_ptr_lifetime(_ x: Span<Int32>) -> Span<Int32>` |
503-
| `void take_ptr(const int * __counted_by(len) x, int len);` | `func take_ptr(_ x: UnsafeBufferPointer<Int32>)` |
504-
| `const int * __counted_by(len) change_ptr(const int * __counted_by(len) x, int len);` | `@lifetime(x) func change_ptr(_ x: UnsafeBufferPointer<Int32>) -> UnsafeBufferPointer<Int32>` |
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>
505568

506569
The `UnsafeBufferPointer` overloads provide the same bounds safety as their `Span` equvalents
507570
(NB: `UnsafeBufferPointer` is not bounds checked on memory access in release builds, but the generated
@@ -516,14 +579,14 @@ adding any bounds or lifetime annotations.
516579

517580
#### Bounds Annotations using API Notes
518581

519-
In cases where you don't want to modify the imported headers, bounds attributes can be applied using API Notes.
582+
In cases where you don't want to modify the imported headers, bounds annotations can be applied using API Notes.
520583
Given the following header:
521584
```c
522585
void foo(int *p, int len);
523586
void *bar(int size);
524587
```
525588
We can provide bounds annotations in our API note file like this:
526-
```
589+
```yaml
527590
Functions:
528591
- Name: foo
529592
Parameters:
@@ -538,12 +601,13 @@ Functions:
538601
```
539602

540603
#### Limitations
541-
Bounds attributes are not supported for nested pointers: only the outermost pointer can be transformed.
604+
Bounds annotations are not supported for nested pointers: only the outermost pointer can be transformed.
542605

543-
`lifetime_capture_by` is currently not taken into account when generating safe overloads.
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.
544608

545-
Bounds attributes on global variables or struct fields are ignored: only parameters and return values
609+
Bounds annotations on global variables or struct fields are ignored: only parameters and return values
546610
are considered.
547611

548-
Bounds attributes, while supported in Objective-C code bases, are not currently supported in Objective-C
612+
Bounds annotations, while supported in Objective-C code bases, are not currently supported in Objective-C
549613
class method signatures.

0 commit comments

Comments
 (0)