Skip to content

Add rc::Allocated #172

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
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
through `&Object`.
* Added `VerificationError` as more specific return type from
`Class::verify_sel`.
* Added `rc::Allocated` struct which is used within `msg_send_id!`.

### Changed
* **BREAKING**: `Sel` is now required to be non-null, which means that you
Expand Down
28 changes: 14 additions & 14 deletions objc2/src/__macro_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::rc::{Id, Ownership};
use crate::rc::{Allocated, Id, Ownership};
use crate::runtime::{Class, Sel};
use crate::{Message, MessageArguments, MessageReceiver};

Expand Down Expand Up @@ -68,8 +68,8 @@ impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, Id<T, O>>
}
}

// `alloc`, should mark the return value as "allocated, not initialized" somehow
impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, Id<T, O>>
// `alloc`
impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, Id<Allocated<T>, O>>
for RetainSemantics<false, true, false, false>
{
#[inline]
Expand All @@ -78,26 +78,26 @@ impl<T: ?Sized + Message, O: Ownership> MsgSendId<&'_ Class, Id<T, O>>
cls: &Class,
sel: Sel,
args: A,
) -> Option<Id<T, O>> {
) -> Option<Id<Allocated<T>, O>> {
// SAFETY: Checked by caller
let obj = unsafe { MessageReceiver::send_message(cls, sel, args) };
// SAFETY: The selector is `alloc`, so this has +1 retain count
unsafe { Id::new(obj) }
unsafe { Id::new_allocated(obj) }
}
}

// `init`, should mark the input value as "allocated, not initialized" somehow
impl<T: ?Sized + Message, O: Ownership> MsgSendId<Option<Id<T, O>>, Id<T, O>>
// `init`
impl<T: ?Sized + Message, O: Ownership> MsgSendId<Option<Id<Allocated<T>, O>>, Id<T, O>>
for RetainSemantics<false, false, true, false>
{
#[inline]
#[track_caller]
unsafe fn send_message_id<A: MessageArguments>(
obj: Option<Id<T, O>>,
obj: Option<Id<Allocated<T>, O>>,
sel: Sel,
args: A,
) -> Option<Id<T, O>> {
let ptr = Id::option_into_ptr(obj);
let ptr = Id::option_into_ptr(obj.map(|obj| unsafe { Id::assume_init(obj) }));
// SAFETY: `ptr` may be null here, but that's fine since the return
// is `*mut T`, which is one of the few types where messages to nil is
// allowed.
Expand Down Expand Up @@ -191,7 +191,7 @@ mod tests {

use core::ptr;

use crate::rc::{Owned, RcTestObject, Shared, ThreadTestData};
use crate::rc::{Allocated, Owned, RcTestObject, Shared, ThreadTestData};
use crate::runtime::Object;
use crate::{Encoding, RefEncode};

Expand All @@ -200,7 +200,7 @@ mod tests {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();

let obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![cls, alloc].unwrap() };
let obj: Id<Allocated<RcTestObject>, Shared> = unsafe { msg_send_id![cls, alloc].unwrap() };
expected.alloc += 1;
expected.assert_current();

Expand Down Expand Up @@ -230,7 +230,7 @@ mod tests {
let cls = RcTestObject::class();

let zone: *const _NSZone = ptr::null();
let _obj: Id<RcTestObject, Owned> =
let _obj: Id<Allocated<RcTestObject>, Owned> =
unsafe { msg_send_id![cls, allocWithZone: zone].unwrap() };
// `+[NSObject alloc]` delegates to `+[NSObject allocWithZone:]`, but
// `RcTestObject` only catches `alloc`.
Expand All @@ -243,14 +243,14 @@ mod tests {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();

let obj: Option<Id<RcTestObject, Shared>> = unsafe { msg_send_id![cls, alloc] };
let obj: Option<Id<Allocated<RcTestObject>, Shared>> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
// Don't check allocation error
let _obj: Id<RcTestObject, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
expected.init += 1;
expected.assert_current();

let obj: Option<Id<RcTestObject, Shared>> = unsafe { msg_send_id![cls, alloc] };
let obj: Option<Id<Allocated<RcTestObject>, Shared>> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
// Check allocation error before init
let obj = obj.unwrap();
Expand Down
10 changes: 5 additions & 5 deletions objc2/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,11 +590,12 @@ macro_rules! msg_send_bool {
/// is a generic `Option<Id<T, O>>`.
///
/// - The `alloc` family: The receiver must be `&Class`, and the return type
/// is a generic `Option<Id<T, O>>`. (This will change, see [#172]).
/// is a generic `Option<Id<Allocated<T>, O>>`.
///
/// - The `init` family: The receiver must be `Option<Id<T, O>>` as returned
/// from `alloc`. The receiver is consumed, and a the now-initialized
/// `Option<Id<T, O>>` (with the same `T` and `O`) is returned.
/// - The `init` family: The receiver must be `Option<Id<Allocated<T>, O>>`
/// as returned from `alloc`. The receiver is consumed, and a the
/// now-initialized `Option<Id<T, O>>` (with the same `T` and `O`) is
/// returned.
///
/// - The `copy` family: The receiver may be anything that implements
/// [`MessageReceiver`] and the return type is a generic `Option<Id<T, O>>`.
Expand All @@ -615,7 +616,6 @@ macro_rules! msg_send_bool {
/// [`Id::retain`], [`Id::drop`] and [`Id::autorelease`] for that.
///
/// [sel-families]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families
/// [#172]: https://github.com/madsmtm/objc2/pull/172
/// [`MessageReceiver`]: crate::MessageReceiver
/// [`Id::retain_autoreleased`]: crate::rc::Id::retain_autoreleased
/// [arc-retainable]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments
Expand Down
15 changes: 15 additions & 0 deletions objc2/src/rc/allocated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// A marker type that can be used within [`Id`] to indicate that the object
/// has been allocated but not initialized.
///
/// The reason we use `Option<Id<Allocated<T>, O>>` instead of just `*mut T`
/// is:
/// - To allow releasing allocated objects, e.g. in the face of panics.
/// - To safely know the object is valid (albeit uninitialized).
/// - To allow specifying ownership.
///
/// [`Id`]: crate::rc::Id
#[repr(transparent)]
#[derive(Debug)]
pub struct Allocated<T: ?Sized>(T);

// Explicitly don't implement `Deref`, `Message` nor `RefEncode`!
44 changes: 34 additions & 10 deletions objc2/src/rc/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::ops::{Deref, DerefMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::NonNull;

use super::Allocated;
use super::AutoreleasePool;
use super::{Owned, Ownership, Shared};
use crate::ffi;
Expand Down Expand Up @@ -124,6 +125,39 @@ pub struct Id<T: ?Sized, O: Ownership> {
notunwindsafe: PhantomData<&'static mut ()>,
}

impl<T: ?Sized, O: Ownership> Id<T, O> {
#[inline]
unsafe fn new_nonnull(ptr: NonNull<T>) -> Self {
Self {
ptr,
item: PhantomData,
own: PhantomData,
notunwindsafe: PhantomData,
}
}
}

impl<T: Message + ?Sized, O: Ownership> Id<Allocated<T>, O> {
#[inline]
pub(crate) unsafe fn new_allocated(ptr: *mut T) -> Option<Self> {
// SAFETY: Upheld by the caller
NonNull::new(ptr as *mut Allocated<T>).map(|ptr| unsafe { Self::new_nonnull(ptr) })
}

#[inline]
pub(crate) unsafe fn assume_init(this: Self) -> Id<T, O> {
let ptr = ManuallyDrop::new(this).ptr;

// NonNull::cast
let ptr = ptr.as_ptr() as *mut T;
let ptr = unsafe { NonNull::new_unchecked(ptr) };

// SAFETY: The pointer is valid.
// Caller verifies that the object is allocated.
unsafe { Id::new_nonnull(ptr) }
}
}

impl<T: Message + ?Sized, O: Ownership> Id<T, O> {
/// Constructs an [`Id`] to an object that already has +1 retain count.
///
Expand Down Expand Up @@ -181,16 +215,6 @@ impl<T: Message + ?Sized, O: Ownership> Id<T, O> {
NonNull::new(ptr).map(|ptr| unsafe { Id::new_nonnull(ptr) })
}

#[inline]
unsafe fn new_nonnull(ptr: NonNull<T>) -> Id<T, O> {
Self {
ptr,
item: PhantomData,
own: PhantomData,
notunwindsafe: PhantomData,
}
}

/// Returns a raw pointer to the object.
///
/// The pointer is valid for at least as long as the `Id` is held.
Expand Down
2 changes: 2 additions & 0 deletions objc2/src/rc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
//! assert!(weak.load().is_none());
//! ```

mod allocated;
mod autorelease;
mod id;
mod id_forwarding_impls;
Expand All @@ -65,6 +66,7 @@ mod weak_id;
#[cfg(test)]
mod test_object;

pub use self::allocated::Allocated;
pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
pub use self::id::Id;
pub use self::id_traits::{DefaultId, SliceId, SliceIdMut};
Expand Down
11 changes: 7 additions & 4 deletions tests/assembly/test_msg_send_id/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! Test assembly output of `msg_send_id!` internals.
use objc2::__macro_helpers::{MsgSendId, RetainSemantics};
use objc2::rc::{Id, Shared};
use objc2::rc::{Allocated, Id, Shared};
use objc2::runtime::{Class, Object, Sel};

#[no_mangle]
unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Option<Id<Object, Shared>> {
unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Option<Id<Allocated<Object>, Shared>> {
<RetainSemantics<false, true, false, false>>::send_message_id(obj, sel, ())
}

#[no_mangle]
unsafe fn handle_init(obj: Option<Id<Object, Shared>>, sel: Sel) -> Option<Id<Object, Shared>> {
unsafe fn handle_init(
obj: Option<Id<Allocated<Object>, Shared>>,
sel: Sel,
) -> Option<Id<Object, Shared>> {
<RetainSemantics<false, false, true, false>>::send_message_id(obj, sel, ())
}

Expand All @@ -21,7 +24,7 @@ unsafe fn handle_alloc_init(obj: &Class, sel1: Sel, sel2: Sel) -> Option<Id<Obje

#[no_mangle]
unsafe fn handle_alloc_release(cls: &Class, sel: Sel) {
let _obj: Id<Object, Shared> =
let _obj: Id<Allocated<Object>, Shared> =
<RetainSemantics<false, true, false, false>>::send_message_id(cls, sel, ())
.unwrap_unchecked();
}
Expand Down
8 changes: 6 additions & 2 deletions tests/ui/msg_send_id_invalid_receiver.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
//! Test compiler output with invalid msg_send_id receivers.
use objc2::msg_send_id;
use objc2::runtime::{Class, Object};
use objc2::rc::{Id, Shared};
use objc2::rc::{Allocated, Id, Shared};

fn main() {
let obj: &Object;
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, new].unwrap() };
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, alloc].unwrap() };
let _: Id<Allocated<Object>, Shared> = unsafe { msg_send_id![obj, alloc].unwrap() };
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, init].unwrap() };

let cls: &Class;
let _: Id<Object, Shared> = unsafe { msg_send_id![cls, init].unwrap() };
let obj: Id<Object, Shared>;
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
let obj: Option<Id<Object, Shared>>;
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, init].unwrap() };

let obj: Id<Object, Shared>;
let _: Id<Object, Shared> = unsafe { msg_send_id![obj, copy].unwrap() };
Expand Down
54 changes: 44 additions & 10 deletions tests/ui/msg_send_id_invalid_receiver.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ note: associated function defined here
| ^^^^^^^^^^^^^^^

error[E0308]: mismatched types
--> ui/msg_send_id_invalid_receiver.rs:9:55
--> ui/msg_send_id_invalid_receiver.rs:9:66
|
9 | let _: Id<Object, Shared> = unsafe { msg_send_id![obj, alloc].unwrap() };
| -------------^^^--------
| | |
| | expected struct `objc2::runtime::Class`, found struct `objc2::runtime::Object`
| arguments to this function are incorrect
9 | let _: Id<Allocated<Object>, Shared> = unsafe { msg_send_id![obj, alloc].unwrap() };
| -------------^^^--------
| | |
| | expected struct `objc2::runtime::Class`, found struct `objc2::runtime::Object`
| arguments to this function are incorrect
|
= note: expected reference `&objc2::runtime::Class`
found reference `&objc2::runtime::Object`
Expand All @@ -41,7 +41,7 @@ error[E0308]: mismatched types
| | expected enum `Option`, found `&objc2::runtime::Object`
| arguments to this function are incorrect
|
= note: expected enum `Option<Id<_, _>>`
= note: expected enum `Option<Id<Allocated<_>, _>>`
found reference `&objc2::runtime::Object`
note: associated function defined here
--> $WORKSPACE/objc2/src/__macro_helpers.rs
Expand All @@ -58,18 +58,52 @@ error[E0308]: mismatched types
| | expected enum `Option`, found `&objc2::runtime::Class`
| arguments to this function are incorrect
|
= note: expected enum `Option<Id<_, _>>`
= note: expected enum `Option<Id<Allocated<_>, _>>`
found reference `&objc2::runtime::Class`
note: associated function defined here
--> $WORKSPACE/objc2/src/__macro_helpers.rs
|
| unsafe fn send_message_id<A: MessageArguments>(obj: T, sel: Sel, args: A) -> Option<U>;
| ^^^^^^^^^^^^^^^

error[E0308]: mismatched types
--> ui/msg_send_id_invalid_receiver.rs:15:55
|
15 | let _: Id<Object, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
| -------------^^^-------
| | |
| | expected enum `Option`, found struct `Id`
| arguments to this function are incorrect
|
= note: expected enum `Option<Id<Allocated<_>, _>>`
found struct `Id<objc2::runtime::Object, Shared>`
note: associated function defined here
--> $WORKSPACE/objc2/src/__macro_helpers.rs
|
| unsafe fn send_message_id<A: MessageArguments>(obj: T, sel: Sel, args: A) -> Option<U>;
| ^^^^^^^^^^^^^^^

error[E0308]: mismatched types
--> ui/msg_send_id_invalid_receiver.rs:17:55
|
17 | let _: Id<Object, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
| -------------^^^-------
| | |
| | expected struct `Allocated`, found struct `objc2::runtime::Object`
| arguments to this function are incorrect
|
= note: expected enum `Option<Id<Allocated<_>, _>>`
found enum `Option<Id<objc2::runtime::Object, Shared>>`
note: associated function defined here
--> $WORKSPACE/objc2/src/__macro_helpers.rs
|
| unsafe fn send_message_id<A: MessageArguments>(obj: T, sel: Sel, args: A) -> Option<U>;
| ^^^^^^^^^^^^^^^

error[E0277]: the trait bound `Id<objc2::runtime::Object, Shared>: MessageReceiver` is not satisfied
--> ui/msg_send_id_invalid_receiver.rs:16:42
--> ui/msg_send_id_invalid_receiver.rs:20:42
|
16 | let _: Id<Object, Shared> = unsafe { msg_send_id![obj, copy].unwrap() };
20 | let _: Id<Object, Shared> = unsafe { msg_send_id![obj, copy].unwrap() };
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MessageReceiver` is not implemented for `Id<objc2::runtime::Object, Shared>`
|
= help: the following other types implement trait `MessageReceiver`:
Expand Down
13 changes: 7 additions & 6 deletions tests/ui/msg_send_id_invalid_return.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
//! Test compiler output with invalid msg_send_id receivers.
use objc2::msg_send_id;
use objc2::runtime::{Class, Object};
use objc2::rc::{Id, Owned, Shared};
use objc2::rc::{Allocated, Id, Owned, Shared};
use objc2_foundation::NSObject;

fn main() {
let cls: &Class;
let _: &Object = unsafe { msg_send_id![cls, new].unwrap() };
let _: Id<Class, Shared> = unsafe { msg_send_id![cls, new].unwrap() };
let _: &Object = unsafe { msg_send_id![cls, alloc].unwrap() };
let _: Id<Class, Shared> = unsafe { msg_send_id![cls, alloc].unwrap() };
let _: Id<Allocated<Class>, Shared> = unsafe { msg_send_id![cls, alloc].unwrap() };
let _: Id<Object, Shared> = unsafe { msg_send_id![cls, alloc].unwrap() };

let obj: Option<Id<Object, Shared>>;
let obj: Option<Id<Allocated<Object>, Shared>>;
let _: &Object = unsafe { msg_send_id![obj, init].unwrap() };
let obj: Option<Id<Object, Shared>>;
let obj: Option<Id<Allocated<Object>, Shared>>;
let _: Id<Class, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
let obj: Option<Id<Object, Shared>>;
let obj: Option<Id<Allocated<Object>, Shared>>;
let _: Id<NSObject, Shared> = unsafe { msg_send_id![obj, init].unwrap() };
let obj: Option<Id<Object, Shared>>;
let obj: Option<Id<Allocated<Object>, Shared>>;
let _: Id<Object, Owned> = unsafe { msg_send_id![obj, init].unwrap() };

let obj: Id<Object, Shared>;
Expand Down
Loading