Skip to content

[cxx-interop] Shared references are considered safe #82203

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
merged 1 commit into from
Jun 17, 2025

Conversation

Xazax-hun
Copy link
Contributor

This patch makes sure we don't get warnings in strict memory safe mode when using shared references. Those types are reference counted so we are unlikely to run into lifetime errors.

rdar://151039766

@Xazax-hun Xazax-hun added the c++ interop Feature: Interoperability with C++ label Jun 12, 2025
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.

Would this also make SWIFT_IMMORTAL_REFERENCE and SWIFT_UNSAFE_REFERENCE types safe in Swift?

@Xazax-hun
Copy link
Contributor Author

Would this also make SWIFT_IMMORTAL_REFERENCE and SWIFT_UNSAFE_REFERENCE types safe in Swift?

SWIFT_IMMORTAL_REFERENCE yes, SWIFT_UNSAFE_REFERENCE no. The latter is explicitly marked as unsafe which takes precedence over the rule in this PR. Do you want me to change SWIFT_IMMORTAL_REFERENCE?



// Shared references are considered safe.
if (hasSwiftAttribute(decl, "import_reference"))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think just checking if __attribute__((swift_attr("import_reference"))) is on the decl won't fix the issue for inherited SWIFT_SHARED_REFERENCE types.

In a follow-up patch, could you please check if the decl is a foreign reference type using CxxRecordSemantics like this:

  auto semanticsKind = evaluateOrDefault(
      importerImpl->SwiftContext.evaluator,
      CxxRecordSemantics({decl, importerImpl->SwiftContext, importerImpl,
                          /* shouldDiagnoseLifetimeOperations */ false}),
      {});
  return semanticsKind == CxxRecordSemanticsKind::Reference;

You can also take a look at importer::recordHasReferenceSemantics for reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, this is a good point! I will update the PR and add a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ended up going in a slightly different direction. We did not have Swift's ASTContext and the importer available in this query, and it is used from Decl.cpp which should never require an importer. Also, it is sufficient to do this query once, when we make the decision how to import a type. Instead, I started to pass in the Swift declaration which gives us a really cheap way to check if we imported something as a shared reference (we just check if it was imported as a class).

Copy link
Contributor

@fahadnayyar fahadnayyar left a comment

Choose a reason for hiding this comment

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

I'm approving this, but I'd like to add a request to consider inherited SWIFT_SHARED_REFERENCE types in a follow-up patch.

@Xazax-hun Xazax-hun force-pushed the gaborh/shared-references-are-safe branch from 731d57b to a801177 Compare June 13, 2025 10:05
@@ -427,16 +427,20 @@ class CxxRecordAsSwiftType

struct SafeUseOfCxxDeclDescriptor final {
const clang::Decl *decl;
// Could be null. Used by ClangDeclExplicitSafety to check if the type was
// imported as a shared reference.
const Decl *swiftDecl;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, could we just have bool isClass here, or do you anticipate that we'll have a more elaborate heuristic in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In some call sites we don't need/have this information. It could be a std::optional<bool> isClass; though. I don't have strong opinions here.

Copy link
Contributor

Choose a reason for hiding this comment

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

What's the call site that doesn't have this information?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This descriptor is used for two queries: IsSafeUseOfCxxDecl and ClangDeclExplicitSafety.

The former is used to rename methods to "_nameUnsafe" and we don't have a Swift decl when we make the naming decision. But the former query does not need this information. I could clean this up by having 2 separate descriptors for the 2 queries.

But more importantly, ClangDeclExplicitSafety will recursively check for the safety of the base classes. While we iterate through the Clang bases we do not have the corresponding imported Swift types for those base classes.

But this is not problematic because:

  • Either the derived class was a shared reference, in that case we do not care about the bases and do an early return.
  • Or the derived class was not a shared reference, in that case a base being a shared reference does not prevent us from considering this type as unsafe since this is not actually reference counted by Swift.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see, I didn't realize this is used for two different requests. Thanks for explaining! Yeah, I would vote for splitting it up into two distinct descriptors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

This patch makes sure we don't get warnings in strict memory safe mode
when using shared references. Those types are reference counted so we
are unlikely to run into lifetime errors.

rdar://151039766
@Xazax-hun Xazax-hun force-pushed the gaborh/shared-references-are-safe branch from a801177 to ddacdf4 Compare June 13, 2025 14:49
@Xazax-hun
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.

LGTM, thanks!

@Xazax-hun Xazax-hun enabled auto-merge June 17, 2025 12:12
@Xazax-hun Xazax-hun merged commit 304d558 into main Jun 17, 2025
3 checks passed
@Xazax-hun Xazax-hun deleted the gaborh/shared-references-are-safe branch June 17, 2025 15:13
Xazax-hun added a commit that referenced this pull request Jun 17, 2025
Explanation: Shared references imported from C++ were not considered
safe. This is a widely used feature and this fix is blocking the users
from adopting strictly memory safe Swift.
Issue: rdar://151039766
Risk: Low, the fix only changes what declarations are considered safe.
Testing: Regression test added.
Original PR: #82203
Reviewer: @egorzhdan @fahadnayyar
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