Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[cxx-interop] Introduce type-level annotations to specify default ownership convention for C++ foreign reference return values #81093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

fahadnayyar
Copy link
Contributor

@fahadnayyar fahadnayyar commented Apr 25, 2025

In Swift 6.1, we introduced SWIFT_RETURNS_RETAINED and SWIFT_RETURNS_UNRETAINED annotations for C++ APIs to explicitly specify the ownership convention of SWIFT_SHARED_REFERENCE type return values.

Currently the Swift compiler emits warnings for unannotated C++ APIs returning SWIFT_SHARED_REFERENCE types. We've received some feedback that people are finding these warnings useful to get a reminder to annotate their APIs. While this improves correctness , it also imposes a high annotation burden on adopters — especially in large C++ codebases.

This patch addresses that burden by introducing two new type-level annotations:

  • SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT
  • SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT

These annotations allow developers to specify a default ownership convention for all C++ APIs returning a given SWIFT_SHARED_REFERENCE-annotated type, unless explicitly overridden at the API by using SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED. If a C++ class inherits from a base class annotated with SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT or SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT, the derived class automatically inherits the default ownership convention unless it is explicitly overridden. This strikes a balance between safety/correctness and usability:

  • It avoids the need to annotate every API individually.
  • It retains the ability to opt out of the default at the API level when needed.
  • To verify correctness, the user can just remove the SWIFT_RETURNED_AS_(UN)RETAINED_BY_DEFAULT annotation from that type and they will start seeing the warnings on all the unannotated C++ APIs returning that SWIFT_SHARED_REFERENCE type. They can add SWIFT_RETURNS_(UN)RETAINED annotation at each API in which they want a different behaviour than the default. Then they can reintroduce the SWIFT_RETURNED_AS_(UN)RETAINED_BY_DEFAULT at the type level to suppress the warnings on remaining unannotated APIs.

A global default ownership convention (like always return unretained/unowned) was considered but it would weaken the diagnostic signal and remove valuable guardrails that help detect use-after-free bugs and memory leaks in absence of SWIFT_RETURNS_(UN)RETAINED annotations. In the absence of these annotations when Swift emits the unannotated API warning, the current fallback behavior (e.g. relying on heuristics based on API name such as "create", "copy", "get") is derived from Objective-C interop but is ill-suited for C++, which has no consistent naming patterns for ownership semantics.

Several codebases are expected to have project-specific conventions, such as defaulting to unretained except for factory methods and constructors. A type-level default seems like the most precise and scalable mechanism to support such patterns. It integrates cleanly with existing SWIFT_SHARED_REFERENCE usage and provides a per-type opt-in mechanism without global silencing of ownership diagnostics.

This addition improves ergonomics while preserving the safety benefits of explicit annotations and diagnostics.

rdar://145453509

Copy link
Contributor

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are not passing at the moment.

Also this does not seem to address inheritance. I think it would be really unintuitive that the SHARED_REFERENCE annotations can be inherited but this does not. I suspect that it might not be too much work to support inheritance in this PR so I'd recommend including it in this one rather than doing follow-up work.

@fahadnayyar

This comment was marked as resolved.

@fahadnayyar fahadnayyar requested a review from DougGregor April 25, 2025 15:56
@Xazax-hun

This comment was marked as resolved.

@Xazax-hun

This comment was marked as resolved.

@egorzhdan

This comment was marked as resolved.

@fahadnayyar fahadnayyar force-pushed the cxx-frt-default-returns-unretained branch from ac02517 to 55cb7cd Compare April 29, 2025 04:57
@fahadnayyar fahadnayyar force-pushed the cxx-frt-default-returns-unretained branch 2 times, most recently from 51e0d54 to 42a2e0e Compare April 29, 2025 06:09
@fahadnayyar fahadnayyar force-pushed the cxx-frt-default-returns-unretained branch from 42a2e0e to f52ef8b Compare April 29, 2025 06:22
@fahadnayyar
Copy link
Contributor Author

This patch now covers inferring SWIFT_RETURNS_RETAINED_BY_DEFAULT and SWIFT_RETURNS_UNRETAINED_BY_DEFAULT annotations from base to derived types in case of C++ inheritance.

@fahadnayyar fahadnayyar requested a review from rjmccall April 30, 2025 00:45
Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the concerns about inheritance. It mostly looks good to me and my review primarily consists of nits, but I am concerned about the naming/documentation for this annotation because it is not clear to me what should be annotated with this.

Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I have a couple of comments on the implementation, but the idea looks good to me.

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me as is, aside from some comment fixes that can be addressed in a follow-up.

What happens if a class multiply inherits from two (or more) classes, where one is returned as retained by convention and the other as unretained by convention? At the moment, it looks like the code will simply take whatever annotation it encounters first. But perhaps we should be smarter about that. I think this issue can probably be addressed in a follow-up patch, though.

@fahadnayyar
Copy link
Contributor Author

What happens if a class multiply inherits from two (or more) classes, where one is returned as retained by convention and the other as unretained by convention? At the moment, it looks like the code will simply take whatever annotation it encounters first.

Great point! In this case, we should ideally emit an error. Even in the case of overridden APIs returning values of SWIFT_SHARED_REFERENCE types that have mismatched SWIFT_RETURNS_(UN)RETAINED annotations, we should emit an error. But I think we can do these diagnostics in a later release. Handling incorrect usage of annotations isn't our main focus right now. Our goal is to ship the annotations so that users can start using them at the right places to reduce the risk of use-after-free bugs, while also keeping the adoption overhead low.

egorzhdan

This comment was marked as resolved.

@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

@fahadnayyar fahadnayyar merged commit e7bbd4b into swiftlang:main May 2, 2025
3 checks passed
fahadnayyar added a commit to fahadnayyar/swift that referenced this pull request May 3, 2025
…ership convention for C++ foreign reference return values (swiftlang#81093)

In Swift 6.1, we introduced `SWIFT_RETURNS_RETAINED` and
`SWIFT_RETURNS_UNRETAINED` annotations for C++ APIs to explicitly
specify the ownership convention of `SWIFT_SHARED_REFERENCE` type return
values.

Currently the Swift compiler emits warnings for unannotated C++ APIs
returning `SWIFT_SHARED_REFERENCE` types. We've received some feedback
that people are finding these warnings useful to get a reminder to
annotate their APIs. While this improves correctness , it also imposes a
high annotation burden on adopters — especially in large C++ codebases.

This patch addresses that burden by introducing two new type-level
annotations:
- `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`
- `SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`

These annotations allow developers to specify a default ownership
convention for all C++ APIs returning a given
`SWIFT_SHARED_REFERENCE`-annotated type, unless explicitly overridden at
the API by using `SWIFT_RETURNS_RETAINED` or `SWIFT_RETURNS_UNRETAINED`.
If a C++ class inherits from a base class annotated with
`SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT` or
`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`, the derived class
automatically inherits the default ownership convention unless it is
explicitly overridden. This strikes a balance between safety/correctness
and usability:

- It avoids the need to annotate every API individually.
- It retains the ability to opt out of the default at the API level when
needed.
- To verify correctness, the user can just remove the
`SWIFT_RETURNED_AS_(UN)RETAINED_BY_DEFAULT` annotation from that type
and they will start seeing the warnings on all the unannotated C++ APIs
returning that `SWIFT_SHARED_REFERENCE` type. They can add
`SWIFT_RETURNS_(UN)RETAINED` annotation at each API in which they want a
different behaviour than the default. Then they can reintroduce the
`SWIFT_RETURNED_AS_(UN)RETAINED_BY_DEFAULT` at the type level to
suppress the warnings on remaining unannotated APIs.

A global default ownership convention (like always return
`unretained`/`unowned`) was considered but it would weaken the
diagnostic signal and remove valuable guardrails that help detect
use-after-free bugs and memory leaks in absence of
`SWIFT_RETURNS_(UN)RETAINED` annotations. In the absence of these
annotations when Swift emits the unannotated API warning, the current
fallback behavior (e.g. relying on heuristics based on API name such as
`"create"`, `"copy"`, `"get"`) is derived from Objective-C interop but
is ill-suited for C++, which has no consistent naming patterns for
ownership semantics.

Several codebases are expected to have project-specific conventions,
such as defaulting to unretained except for factory methods and
constructors. A type-level default seems like the most precise and
scalable mechanism to support such patterns. It integrates cleanly with
existing `SWIFT_SHARED_REFERENCE` usage and provides a per-type opt-in
mechanism without global silencing of ownership diagnostics.

This addition improves ergonomics while preserving the safety benefits
of explicit annotations and diagnostics.

rdar://145453509
fahadnayyar added a commit that referenced this pull request May 7, 2025
…#81329)

This patch removes the `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`
annotation while maintaining the support for
`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`. These type-level annotations
were initially introduced in
[PR-81093](#81093) to reduce the
annotation burden in large C++ codebases where many C++ APIs returning
`SWIFT_SHARED_REFERENCE` types are exposed to Swift.

### Motivation
The original goal was to make C++ interop more ergonomic by allowing
type-level defaults for ownership conventions
for`SWIFT_SHARED_REFERENCE` types . However, defaulting to retained
return values (+1) seems to be problematic and poses memory safety
risks.

### Why we’re removing `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`

- **Memory safety risks:** Defaulting to retained can potentially lead
to use-after-free bugs when the API implementation actually returns
`unowned` (`+0`). These errors are subtle and can be hard to debug or
discover, particularly in the absence of explicit API-level
`SWIFT_RETURNS_(UN)RETAINED` annotations.
- **Risky transitive behavior:** If a `SWIFT_SHARED_REFERENCE` type is
annotated with `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`, any new C++ API
returning this type will inherit the retained behavior by default—even
if the API's actual return behavior is unretained. Unless explicitly
overridden with `SWIFT_RETURNS_UNRETAINED`, this can introduce a silent
mismatch in ownership expectations and lead to use-after-free bugs. This
is especially risky in large or evolving codebases where such defaults
may be overlooked.
- **Simpler multiple inheritance semantics:** With only one type-level
default (`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`), we avoid
complications that can arise when multiple base classes specify
conflicting ownership defaults. This simplifies reasoning about behavior
in class hierarchies and avoids ambiguity when Swift determines the
ownership convention for inherited APIs.

### Why we’re keeping `SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`
- It still enables projects to suppress warnings for unannotated C++
APIs returning `SWIFT_SHARED_REFERENCE` types, helping to reduce noise
while maintaining clarity.
- It encourages explicitness for retained behavior. Developers must
annotate retained return values with `SWIFT_RETURNS_RETAINED`, making
ownership intent clearer and safer.
- The worst-case outcome of assuming unretained when the return is
actually retained is a memory leak, which is more tolerable and easier
to debug than a use-after-free.
- Having a single default mechanism improves clarity for documentation,
diagnostics, and long-term maintenance of Swift/C++ interop code.
fahadnayyar added a commit to fahadnayyar/swift that referenced this pull request May 7, 2025
…swiftlang#81329)

This patch removes the `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`
annotation while maintaining the support for
`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`. These type-level annotations
were initially introduced in
[PR-81093](swiftlang#81093) to reduce the
annotation burden in large C++ codebases where many C++ APIs returning
`SWIFT_SHARED_REFERENCE` types are exposed to Swift.

### Motivation
The original goal was to make C++ interop more ergonomic by allowing
type-level defaults for ownership conventions
for`SWIFT_SHARED_REFERENCE` types . However, defaulting to retained
return values (+1) seems to be problematic and poses memory safety
risks.

### Why we’re removing `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`

- **Memory safety risks:** Defaulting to retained can potentially lead
to use-after-free bugs when the API implementation actually returns
`unowned` (`+0`). These errors are subtle and can be hard to debug or
discover, particularly in the absence of explicit API-level
`SWIFT_RETURNS_(UN)RETAINED` annotations.
- **Risky transitive behavior:** If a `SWIFT_SHARED_REFERENCE` type is
annotated with `SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT`, any new C++ API
returning this type will inherit the retained behavior by default—even
if the API's actual return behavior is unretained. Unless explicitly
overridden with `SWIFT_RETURNS_UNRETAINED`, this can introduce a silent
mismatch in ownership expectations and lead to use-after-free bugs. This
is especially risky in large or evolving codebases where such defaults
may be overlooked.
- **Simpler multiple inheritance semantics:** With only one type-level
default (`SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`), we avoid
complications that can arise when multiple base classes specify
conflicting ownership defaults. This simplifies reasoning about behavior
in class hierarchies and avoids ambiguity when Swift determines the
ownership convention for inherited APIs.

### Why we’re keeping `SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT`
- It still enables projects to suppress warnings for unannotated C++
APIs returning `SWIFT_SHARED_REFERENCE` types, helping to reduce noise
while maintaining clarity.
- It encourages explicitness for retained behavior. Developers must
annotate retained return values with `SWIFT_RETURNS_RETAINED`, making
ownership intent clearer and safer.
- The worst-case outcome of assuming unretained when the return is
actually retained is a memory leak, which is more tolerable and easier
to debug than a use-after-free.
- Having a single default mechanism improves clarity for documentation,
diagnostics, and long-term maintenance of Swift/C++ interop code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants