use core::mem::ManuallyDrop;
use core::ptr::NonNull;
use super::ConvertArgument;
use crate::rc::Retained;
use crate::Message;
impl<T: Message + 'static> ConvertArgument for &mut Retained<T> {
type __Inner = NonNull<*mut T>;
type __StoredBeforeMessage = (
Self::__Inner,
NonNull<T>,
);
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`&mut Retained<_>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
let ptr: NonNull<Retained<T>> = NonNull::from(self);
let ptr: NonNull<NonNull<T>> = ptr.cast();
let old: NonNull<T> = unsafe { *ptr.as_ptr() };
let ptr: NonNull<*mut T> = ptr.cast();
(ptr, (ptr, old))
}
#[inline]
unsafe fn __process_after_message_send((ptr, old): Self::__StoredBeforeMessage) {
let new: Option<Retained<T>> = unsafe { Retained::retain(*ptr.as_ptr()) };
let _new = ManuallyDrop::new(new);
#[cfg(debug_assertions)]
if _new.is_none() {
panic!("found that NULL was written to `&mut Retained<_>`, which is UB! You should handle this with `&mut Option<Retained<_>>` instead");
}
let _: Retained<T> = unsafe { Retained::new_nonnull(old) };
}
}
impl<T: Message + 'static> ConvertArgument for &mut Option<Retained<T>> {
type __Inner = NonNull<*mut T>;
type __StoredBeforeMessage = (Self::__Inner, *mut T);
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`&mut Option<Retained<_>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
let ptr: NonNull<Option<Retained<T>>> = NonNull::from(self);
let ptr: NonNull<*mut T> = ptr.cast();
let old: *mut T = unsafe { *ptr.as_ptr() };
(ptr, (ptr, old))
}
#[inline]
unsafe fn __process_after_message_send((ptr, old): Self::__StoredBeforeMessage) {
let new: Option<Retained<T>> = unsafe { Retained::retain(*ptr.as_ptr()) };
let _ = ManuallyDrop::new(new);
let _: Option<Retained<T>> = unsafe { Retained::from_raw(old) };
}
}
impl<T: Message + 'static> ConvertArgument for Option<&mut Retained<T>> {
type __Inner = Option<NonNull<*mut T>>;
type __StoredBeforeMessage = Option<(NonNull<*mut T>, NonNull<T>)>;
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`Option<&mut Retained<_>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
if let Some(this) = self {
let (ptr, stored) = this.__into_argument();
(Some(ptr), Some(stored))
} else {
(None, None)
}
}
#[inline]
unsafe fn __process_after_message_send(stored: Self::__StoredBeforeMessage) {
if let Some(stored) = stored {
unsafe { <&mut Retained<T>>::__process_after_message_send(stored) };
}
}
}
impl<T: Message + 'static> ConvertArgument for Option<&mut Option<Retained<T>>> {
type __Inner = Option<NonNull<*mut T>>;
type __StoredBeforeMessage = Option<(NonNull<*mut T>, *mut T)>;
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`Option<&mut Option<Retained<_>>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
if let Some(this) = self {
let (ptr, stored) = this.__into_argument();
(Some(ptr), Some(stored))
} else {
(None, None)
}
}
#[inline]
unsafe fn __process_after_message_send(stored: Self::__StoredBeforeMessage) {
if let Some(stored) = stored {
unsafe { <&mut Option<Retained<T>>>::__process_after_message_send(stored) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{autoreleasepool, Allocated, RcTestObject, ThreadTestData};
use crate::{msg_send, msg_send_id, ClassType};
#[test]
fn test_bool_error() {
let mut expected = ThreadTestData::current();
fn bool_error(should_error: bool, error: Option<&mut Option<Retained<RcTestObject>>>) {
let cls = RcTestObject::class();
let did_succeed: bool =
unsafe { msg_send![cls, boolAndShouldError: should_error, error: error] };
assert_ne!(should_error, did_succeed);
}
bool_error(false, None);
bool_error(true, None);
expected.assert_current();
fn helper(
expected: &mut ThreadTestData,
should_error: bool,
mut error: Option<Retained<RcTestObject>>,
) {
std::dbg!(should_error, &error);
autoreleasepool(|_| {
bool_error(should_error, Some(&mut error));
if should_error {
expected.alloc += 1;
expected.init += 1;
expected.autorelease += 1;
}
expected.assert_current();
});
if should_error {
expected.release += 1;
}
expected.assert_current();
if error.is_some() {
expected.release += 1;
expected.drop += 1;
}
drop(error);
expected.assert_current();
}
helper(&mut expected, false, None);
expected.retain += 1;
helper(&mut expected, true, None);
expected.alloc += 1;
expected.init += 1;
expected.retain += 1;
expected.release += 1;
helper(&mut expected, false, Some(RcTestObject::new()));
expected.alloc += 1;
expected.init += 1;
expected.retain += 1;
expected.release += 1;
expected.drop += 1;
helper(&mut expected, true, Some(RcTestObject::new()));
}
#[test]
#[cfg_attr(
any(
not(debug_assertions),
all(not(target_pointer_width = "64"), feature = "catch-all")
),
ignore = "invokes UB which is only caught with debug_assertions"
)]
#[should_panic = "found that NULL was written to `&mut Retained<_>`, which is UB! You should handle this with `&mut Option<Retained<_>>` instead"]
fn test_debug_check_ub() {
let cls = RcTestObject::class();
let mut param: Retained<_> = RcTestObject::new();
let _: () = unsafe { msg_send![cls, outParamNull: &mut param] };
}
const AUTORELEASE_SKIPPED: bool = cfg!(feature = "gnustep-1-7");
#[test]
fn test_id_interaction() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let mut err: Retained<RcTestObject> = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
autoreleasepool(|_| {
let obj: Option<Retained<RcTestObject>> =
unsafe { msg_send_id![cls, idAndShouldError: false, error: &mut err] };
expected.alloc += 1;
expected.init += 1;
if !AUTORELEASE_SKIPPED {
expected.autorelease += 1;
expected.retain += 1;
}
expected.retain += 1;
expected.release += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
if AUTORELEASE_SKIPPED {
expected.drop += 1;
}
expected.assert_current();
});
if !AUTORELEASE_SKIPPED {
expected.release += 1;
expected.drop += 1;
}
expected.assert_current();
drop(err);
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
fn test_error_alloc() {
let mut expected = ThreadTestData::current();
let mut error: Option<Retained<RcTestObject>> = None;
let res: Allocated<RcTestObject> = unsafe {
msg_send_id![RcTestObject::class(), allocAndShouldError: false, error: &mut error]
};
expected.alloc += 1;
expected.assert_current();
assert!(!Allocated::as_ptr(&res).is_null());
assert!(error.is_none());
drop(res);
expected.release += 1;
expected.assert_current();
let res: Retained<RcTestObject> = autoreleasepool(|_pool| {
let mut error = None;
let res: Allocated<RcTestObject> = unsafe {
msg_send_id![RcTestObject::class(), allocAndShouldError: true, error: &mut error]
};
expected.alloc += 1;
expected.init += 1;
expected.autorelease += 1;
expected.retain += 1;
expected.assert_current();
assert!(Allocated::as_ptr(&res).is_null());
error.unwrap()
});
expected.release += 1;
expected.assert_current();
drop(res);
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
}