C++ shared reference ownership

I've noticed that C++ functions with pointer arguments or return values of a SWIFT_SHARED_REFERENCE-bridged type are bridged into Swift with the reference types themselves. For example, this:

SomeReferenceType *myFunction(SomeReferenceType *x);

gets imported as this:

func myFunction(_ x: SomeReferenceType) -> SomeReferenceType

My question is, what ownership convention is used for the C++ function? Which pointers are interpreted by Swift as retained (+1) and which are interpreted as borrowed/unowned (+0)? Is there a way to customize this?

Also, something else I've noticed is that Swift assumes the retain/release functions passed to the SWIFT_SHARED_REFERENCE macro are safe to call on null pointers, so it calls retain and release for optional C++ shared reference types regardless of if they're nil or not. I was wondering if that was documented anywhere, since I shot myself in the foot with it and others might too.

1 Like

We use Swift's conventions for managing foreign reference types. Right now we don't have a good way to customize this. On the Swift side you can still leverage Unmanaged to bridge conventions, though (to return at +0 for example).

Also, something else I've noticed is that Swift assumes the retain/release functions passed to the SWIFT_SHARED_REFERENCE macro are safe to call on null pointers, so it calls retain and releasefor optional C++ shared reference types regardless of if they're nil or not. I was wondering if that was documented anywhere, since I shot myself in the foot with it and others might too.

Swift will respect _Nullable and _Nonnull specifiers, so if you code is annotated with those, I wouldn't expect any problems. By default, pointers will be imported as implicitly unwrapped optionals. Are you sure that you don't have NS_ASSUME_NONNULL_BEGIN though?

1 Like

Swift will respect _Nullable and _Nonnull specifiers, so if you code is annotated with those, I wouldn't expect any problems. By default, pointers will be imported as implicitly unwrapped optionals. Are you sure that you don't have NS_ASSUME_NONNULL_BEGIN though?

I don't think I have NS_ASSUME_NONNULL_BEGIN, because the only headers I'm importing in my C++ file are <swift/bridging> and <QuartzCore/CoreAnimation.h>. Even after annotating my retain and release functions with _Nonnull, Swift still calls them for null pointers. The retain and release functions are never explicitly called from Swift, since they're managed by ARC. I wonder if Swift assuming they're null-safe is an artifact of objc_retain and objc_release being null-safe.

We use Swift's conventions for managing foreign reference types. Right now we don't have a good way to customize this. On the Swift side you can still leverage Unmanaged to bridge conventions, though (to return at +0 for example).

One of my functions is a constructor that returns a pointer to my foreign reference type. When I use it from Swift and print each retain and release call, it seems that Swift assumes the function is returning a +0 reference even though I want it to be a +1 reference. If I'm not mistaken, Swift normally assumes that functions always return owned values. I'm currently using the hack Unmanaged.passUnretained(constructor()).takeRetainedValue() to fix this, which makes everything work correctly. Also, these retain and release functions are declarations whose implementations are linked later, within an extern "C" block; does that make a difference?

That's interesting, I don't think we have anticipated this behavior. We will document it at the very least so it's clear in the docs. Thanks for pointing it out!

1 Like

NULL / NONNULL? That's just nullability.

Although I haven't tried them myself, for ownership control you'd need these guys:

CF_RETURNS_RETAINED
NS_RETURNS_RETAINED
CF_RETURNS_NOT_RETAINED
NS_RETURNS_NOT_RETAINED
CF_CONSUMED
NS_RELEASES_ARGUMENT
2 Likes

I think IRGen “knows” that optional class values can be retained and released with the same functions as non-optional values. It should be fairly straightforward to make that a little more conditional.

2 Likes

Yeah it sounds like we should treat it as a bug, and fix IRGen to avoid such calls. In the meantime we should still document the existing behavior for 5.9 though

1 Like

I filed an issue to track prohibiting calls with null pointers for C++ reference counted types: c++ interop: foreign reference retain/release functions should not be called with `nil`, if possible. · Issue #67411 · apple/swift · GitHub

I'm surprised that an empty optional would still retain its payload. Are you sure that all your APIs that return nullable pointers are annotated with _Nullable?