Skip to content

[cxx-interop] Remove SWIFT_RETURNED_AS_RETAINED_BY_DEFAULT annotation #81329

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

Conversation

fahadnayyar
Copy link
Contributor

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 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 forSWIFT_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 fahadnayyar self-assigned this May 6, 2025
@fahadnayyar fahadnayyar added the c++ interop Feature: Interoperability with C++ label May 6, 2025
@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

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.

Not saying we shouldn't land this, but let's have a discussion on this prior to landing.

@fahadnayyar fahadnayyar requested a review from DougGregor May 6, 2025 12:39
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.

LGTM, let's land this.
I have one minor request for a follow-up patch.

@@ -8809,8 +8809,7 @@ swift::importer::getCxxRefConventionWithAttrs(const clang::Decl *decl) {
if (const clang::RecordDecl *record =
ptrType->getPointeeType()->getAsRecordDecl()) {
return matchSwiftAttrConsideringInheritance<RC>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Not specific to this patch, but could you please remove matchSwiftAttrConsideringInheritance, etc. from the header and move them into the implementation file, since they aren't used anywhere outside of ClangImporter?

@fahadnayyar fahadnayyar merged commit b311c63 into swiftlang:main May 7, 2025
3 checks passed
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.

3 participants