diff --git a/documentation/cxx-interop/index.md b/documentation/cxx-interop/index.md index dec9d631e..9d2b0b729 100644 --- a/documentation/cxx-interop/index.md +++ b/documentation/cxx-interop/index.md @@ -1331,6 +1331,12 @@ automatically. ## Working with C++ References and View Types in Swift +Swift has a new [safe interoperability](https://www.swift.org/documentation/cxx-interop/safe-interop) +model under development that makes code auditable for memory safety errors by explicitly delineating potentially +unsafe code from safe code and introducing ways to annotate lifetime contracts. We recommend giving this +new mode of interoperability a try as the rest of this section might be subsumed by these new features under +a new interoperability version in the future. + As outlined [earlier](#member-functions-returning-references-are-unsafe-by-default), member functions that return references, pointers, or certain structures/classes that diff --git a/documentation/cxx-interop/safe-interop/index.md b/documentation/cxx-interop/safe-interop/index.md new file mode 100644 index 000000000..23d2b5d32 --- /dev/null +++ b/documentation/cxx-interop/safe-interop/index.md @@ -0,0 +1,401 @@ +--- +layout: page +title: Mixing Strict Memory Safe Swift and C++ +official_url: https://swift.org/documentation/cxx-interop/safe-interop/ +redirect_from: +- /documentation/cxx-interop/safe-interop.html +--- + +## Table of Contents +{:.no_toc} + +* TOC +{:toc} + +## Introduction + +This document describes how to ergonomically interact with Swift by importing +C++ construct safely. Swift's [strict memory safety mode](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md) +is a new feature under development to make code easier to audit for memory safety. +All of the features in this document work in regular Swift +but they provide more value in strict memory safe mode. + +* * * + +
+C++ interoperability is an actively evolving feature of Swift. +Future releases of Swift might change how Swift and C++ +interoperate, +as the Swift community gathers feedback from real world adoption of C++ +interoperability in mixed Swift and C++ codebases. +Please provide the feedback that you have on the +[Swift Forums](https://forums.swift.org/c/development/c-interoperability/), or +by filing an [issue on GitHub](https://github.com/swiftlang/swift/issues/new/choose). +Future changes to the design or functionality of C++ interoperability will not +break code in existing codebases [by default](#source-stability-guarantees-for-mixed-language-codebases). +
+ +## Overview + +Swift provides memory safety with a combination of language affordances and runtime checking. +However, Swift also deliberately includes some unsafe constructs, such as the `UnsafePointer` and `UnsafeMutablePointer` +types in the standard library. +In some cases, Swift needs additional information that is not present in the C++ type and API declarations +to safely interface with them. This document describes how such code needs to be annotated. + +### Annotating foreign types + +Types imported from C++ are considered foreign to Swift. +Types imported from C++ are considered foreign to Swift. Normally, most C++ types are imported into Swift +without any restriction. However, a small set of C++ APIs e.g. pointers/references and methods returning +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) +explains this in more detail.) Under the strict memory safe mode, the compiler will flip the polarity and +treat all types that are not known to be safe as unsafe, and will diagnose uses of them. In this section, +we will show how to annotate unsafe C++ types so that they can be accessed safely and correctly from Swift. +Note that the features here are agnostic to whether strictly-safe mode is on or off. When the strictly safe +mode is on, the compiler warnings can serve as a guide to properly annotate C++ types and also help ensure +that the code doesn't use unsafe APIs anywhere. When the strictly-memory-safe mode is off, it is still +recommended to adopt these annotation whereever appropriate, especially on C++ types that are potentially +lifetime dependent on other objects. + +Under strictly-memory-safe mode the built-in integral types like `int`, some standard library types like `std::string`, +and aggregate types built from other safe types are considered safe. Whereas all other unannotated types +are considered unsafe. Let's see what happens when we are trying to use an unannotated type in +strictly-safe mode. Consider the following C++ type and APIs: + +```c++ +class StringRef { +public: + ... +private: + const char* ptr; + size_t len; +}; + +std::string normalize(const std::string& path); + +StringRef fileName(const std::string& normalizedPath); +``` + +Let's try to use them from Swift with strict memory safety enabled: + +```swift +func getFileName(_ path: borrowing std.string) -> StringRef { + let normalizedPath = normalize(path) + return fileName(normalizedPath) +} +``` + +Building this code will emit a warning that the `fileName` call is unsafe because +it references the unsafe type `StringRef`. Swift considers `StringRef` unsafe because +it has a pointer member. Types like `StringRef` can dangle, so we need to take extra +care using them, making sure the referenced buffer outlives the `StringRef` object. + +Swift's [non-escapable types](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0446-non-escapable.md) +can also have lifetime dependencies, just like `StringRef`. However, the Swift compiler +can track these dependencies and enforce safety at compile time. To import `StringRef` +as a safe type we need to mark it as a non-escapable type, we can annotate the class +definition: + +```c++ +class SWIFT_NONESCAPABLE StringRef { ... }; +``` + +Now the Swift compiler imports `StringRef` as a safe type and no longer +emits a warning about using an unsafe type. + +### Annotating APIs + +Building the code again will emit a new diagnostic for the `fileName` function about +missing lifetime annotations. Functions returning non-escapable types need annotations +to describe their lifetime contracts via [lifetimebound](https://clang.llvm.org/docs/AttributeReference.html#id11) +and [lifetime_capture_by](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by) annotations. + +```c++ +StringRef fileName(const std::string& normalizedPath [[clang::lifetimebound]]); +``` + +Adding this annotation to `fileName` indicates that the returned `StringRef` value has the +same lifetime as the argument of the `fileName` function. + +Building the project again reveals a lifetime error in the Swift function: + +```swift +func getFileName(_ path: borrowing std.string) -> StringRef { + let normalizedPath = normalize(path) + // error: lifetime-dependent value escapes local scope + // note: depends on `normalizedPath` + return fileName(normalizedPath) +} +``` + +The value returned by `fileName` will dangle after the lifetime of `normalizedPath` ends. +We can fix this error by pushing the task of normalizing a path to the callee: + +```swift +// Path needs to be normalized. +func getFileName(_ path: borrowing std.string) -> StringRef { + return fileName(normalizedPath) +} +``` + +Or we could return an `Escapable` value like `std.string` instead of a dangling `StringRef`: + +```swift +func getFileName(_ path: borrowing std.string) -> std.string { + let normalizedPath = normalize(path) + let ref = fileName(normalizedPath) + return ref.toString() +} +``` + +After annotating the C++ code, the Swift compiler can enforce the lifetime +contracts helping us to write code that is free of memory safety errors. + +## Escapability annotations in detail + +Under the strictly-safe mode, even though compiler warns on unannotated types, +they are imported as if they are `Escapable` to maintain backward +compatibility. This might change in the future under a new interoperability version. +We have already seen that we can import a type as `~Escapable` to Swift by adding +the `SWIFT_NONESCAPABLE` annotation: + +```c++ +struct SWIFT_NONESCAPABLE View { + View(const int *p) : member(p) {} +private: + const int *member; +}; +``` + +Moreover, we can explicitly mark types as `Escapable` using the `SWIFT_ESCAPABLE` +annotation to express that they are not lifetime dependent on any other values: + +```c++ +struct SWIFT_ESCAPABLE Owner { ... }; +``` + +The main reason for explicitly annotating a type as `SWIFT_ESCAPABLE` is to make sure +it is considered as a safe type when used from Swift. Functions returning escapable +types do not need lifetime annotations. + +Escapability annotations can also be attached to types via APINotes: + +``` +Tags: +- Name: NonEscapableType + SwiftEscapable: false +- Name: EscapableType + SwiftEscapable: true +``` + +In case of template instantiations the escapability of a type can depend on the +template arguments: + +```c++ +MyList f(); +MyList g(); +``` + +In this example, `MyList` should be imported as `~Escapable` while `MyList` +should be imported as `Escapable`. This can be achieved via conditional escapability +annotations: + +```c++ +template +struct SWIFT_ESCAPABLE_IF(T) MyList { + ... +}; +``` + +Here, instantiations of `MyList` are imported as `Escapable` when `T` is substituted +with an `Escapable` type. + +The `SWIFT_ESCAPABLE_IF` macro can take multiple template parameters: + +```c++ +template +struct SWIFT_ESCAPABLE_IF(F, S) MyPair { + F first; + S second; +}; +``` + +`MyPair` instantiations are only imported as `Escapable` if both template arguments +are `Escapable`. + +`Escapable` types cannot have `~Escapable` fields. The following code snippet will +trigger a compiler error: + +```c++ +struct SWIFT_NONESCAPABLE View { ... }; +struct SWIFT_ESCAPABLE Owner { + View v; +}; +``` + +Escapability annotations will not only help the Swift compiler to import C++ types +safely, it will also help discover missing lifetime annotations as all `~Escapable` +parameters and return values need to be annotated in an API to make its use safe in +Swift. + +## Lifetime annotations in detail + +The `lifetimebound` attribute on a function parameter or implicit object parameter +indicates that the returned object's lifetime could end when any of the `lifetimebound` +annotated parameters' lifetime ended. +This annotation a constructor describes the lifetime of the created object: + +```c++ +struct SWIFT_NONESCAPABLE View { + View(const int *p [[clang::lifetimebound]]) : member(p) {} + ... +}; +``` + +In this example, the object initialized by the `View` constructor has the same +lifetime as the input argument of the constructor. + +In case the attribute is after the method signature, the returned object has +the same lifetime as the implicit `this` parameter. + +```c++ +struct Owner { + int data; + + View handOutView() const [[clang::lifetimebound]] { + return View(&data); + } +}; +``` + +Consider a call site like `View v = o.handOutView()`. The `v` object has the same lifetime +as `o`. + +In case the attribute is applied to a subset of the parameters, the return +value might depend on the corresponding arguments: + +```c++ +View getOneOfTheViews(const Owner& owner1 [[clang::lifetimebound]], const Owner& owner2 + View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) { + if (coinFlip) + return View(&owner1.data); + if (coinFlip) + return view1; + else + return view2; +} +``` + +Here, the returned `View`'s lifetime depends on `owner`, `view1`, and `view2` but it cannot +depend on `owner2`. + +Occasionally, a function might return a non-escapable type that has no dependency on any other values. +These types might point to static data or might represent an empty sequence or lack of data. +Such functions need to be annotated with `SWIFT_RETURNS_INDEPENDENT_VALUE`: + +```c++ +View returnsEmpty() SWIFT_RETURNS_INDEPENDENT_VALUE { + return View(); +} +``` + +Notably, the default constructor of a type is always assumed to create an independent value. + +We can also annotate `lifetimebound` APIs via APINotes. The `-1` index represents the this position. + +``` +Tags: +- Name: MyClass + Methods: + - Name: annotateThis + Parameters: + - Position: -1 + Lifetimebound: true + - Name: methodToAnnotate + Parameters: + - Position: 0 + Lifetimebound: true +``` + +Note that APINotes have some limitations around C++, they do not support overloaded functions. + +While `lifetimebound` always describes the lifetime dependencies of the return value (or +the constructed object in case of constructors), we can use can use `lifetime_capture_by` +annotation to descibe the lifetime of other output values, like output/inout arguments +or globals. + +```c++ +void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) { + view2 = view1; +} +``` + +In this example, `view2` will have get all of the lifetime dependencies of `view1` +after a call to `copyView`. a + +We can annotate dependency captured by the implicit `this` object, or +an inout argument capturing `this`: + +```c++ +struct SWIFT_NONESCAPABLE CaptureView { + void captureView(View v [[clang::lifetime_capture_by(this)]]) { + view = v; + } + + void handOut(View &v) const [[clang::lifetime_capture_by(v)]] { + v = view; + } + + View view; +}; +``` + +All of the non-escapable inputs need lifetime annotations for a function to be +considered safe. If an input never escapes from the called function we can use +the `noescape` annotation: + +```c++ +void is_palindrome(std::span s [[clang::noescape]]); +``` + +While the annotations in this section are powerful, they cannot express all of +the lifetime contracts. APIs with inexpressible contracts can be used from Swift, +but they are imported as unsafe APIs and need extra care from the developers +to manually guarantee safety. + +## Convenience overloads for annotated spans and pointers + +C++ APIs often using standard library types or other constructs like a +pointer and a size to represent buffers that have Swift equivalents like +Swift's `Span` type. These Swift types have additional requirements and +guarantees. When these properties are properly annotated on the C++ side, +the Swift compiler can introduce safe convenience functions to make +interacting with the C++ APIs as effortless as if they were written in Swift. + +### C++ span support + +APIs taking/returning C++'s `std::span` with sufficient lifetime +annotations will automatically get overloads taking/returning Swift +`Span`. + +The following table summarizes the generated convenience overloads: + +```c++ +using IntSpan = std::span; +using IntVec = std::vector; +``` + +| C++ API | Generated Swift overload | +| --------------------------------------------------------- | -------------------------------------------------------------------- | +| `void takeSpan(IntSpan x [[clang::noescape]]);` | `func takeSpan(_ x: Span)` | +| `IntSpan changeSpan(IntSpan x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: Span) -> Span` | +| `IntSpan changeSpan(IntVec& x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span` | +| `IntSpan Owner::getSpan() [[clang::lifetimebound]];` | `@lifetime(self) func getSpan() -> Span` | + +These transformations only support top level `std::span`s, we do not +transform the nested cases. A `std::span` of a non-const type `T` will +be transformed to `MutableSpan` on the Swift wide. + +### Annotated pointers +