From 090ea1db24c6df5dd79461b248ec9a1ceab9a3af Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 04:12:30 +0200 Subject: [PATCH 1/9] Require the `Encode` bound on msg_send types Arguments as well as return types. This is a breaking change, but the change is for the better since enabling the `verify_message` feature would break crates that weren't tested with that. Also, this opens up for more possibilities in improving how encodings work. --- objc/src/message/mod.rs | 72 ++++++++++------------------------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/objc/src/message/mod.rs b/objc/src/message/mod.rs index f23597abc..d942998a3 100644 --- a/objc/src/message/mod.rs +++ b/objc/src/message/mod.rs @@ -74,18 +74,6 @@ pub unsafe trait Message { If the selector is known at compile-time, it is recommended to use the `msg_send!` macro rather than this method. */ - #[cfg(not(feature = "verify_message"))] - unsafe fn send_message(&self, sel: Sel, args: A) -> Result - where - Self: Sized, - A: MessageArguments, - R: Any, - { - send_message(self, sel, args) - } - - #[allow(missing_docs)] - #[cfg(feature = "verify_message")] unsafe fn send_message(&self, sel: Sel, args: A) -> Result where Self: Sized, @@ -226,56 +214,28 @@ impl<'a> From> for MessageError { } #[doc(hidden)] -#[inline(always)] -#[cfg(not(feature = "verify_message"))] -pub unsafe fn send_message(obj: *const T, sel: Sel, args: A) -> Result -where - T: Message, - A: MessageArguments, - R: Any, -{ - send_unverified(obj, sel, args) -} - -#[doc(hidden)] -#[inline(always)] -#[cfg(feature = "verify_message")] +#[cfg_attr(feature = "verify_message", inline(always))] pub unsafe fn send_message(obj: *const T, sel: Sel, args: A) -> Result where T: Message, A: MessageArguments + EncodeArguments, R: Any + Encode, { - let cls = if obj.is_null() { - return Err(VerificationError::NilReceiver(sel).into()); - } else { - (*(obj as *const Object)).class() - }; + #[cfg(feature = "verify_message")] + { + let cls = if obj.is_null() { + return Err(VerificationError::NilReceiver(sel).into()); + } else { + (*(obj as *const Object)).class() + }; - verify_message_signature::(cls, sel)?; + verify_message_signature::(cls, sel)?; + } send_unverified(obj, sel, args) } #[doc(hidden)] -#[inline(always)] -#[cfg(not(feature = "verify_message"))] -pub unsafe fn send_super_message( - obj: *const T, - superclass: &Class, - sel: Sel, - args: A, -) -> Result -where - T: Message, - A: MessageArguments, - R: Any, -{ - send_super_unverified(obj, superclass, sel, args) -} - -#[doc(hidden)] -#[inline(always)] -#[cfg(feature = "verify_message")] +#[cfg_attr(feature = "verify_message", inline(always))] pub unsafe fn send_super_message( obj: *const T, superclass: &Class, @@ -287,11 +247,13 @@ where A: MessageArguments + EncodeArguments, R: Any + Encode, { - if obj.is_null() { - return Err(VerificationError::NilReceiver(sel).into()); + #[cfg(feature = "verify_message")] + { + if obj.is_null() { + return Err(VerificationError::NilReceiver(sel).into()); + } + verify_message_signature::(superclass, sel)?; } - - verify_message_signature::(superclass, sel)?; send_super_unverified(obj, superclass, sel, args) } From 19ddf3e05d0db91a8b0cfb9b974292d789ea4675 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 22:48:36 +0200 Subject: [PATCH 2/9] Add `RefEncode` which represents types whoose pointers has an encoding This means you now only have to implement `RefEncode`, and not both `&Encode` and `&mut Encode` (whom should always yield the same results anyway). This setup is much better for generic code, including fixing a bunch of encoding issues in objc_foundation. Also adds a bunch more documentation. --- objc/src/encode.rs | 20 +-- objc/src/lib.rs | 2 +- objc_encode/examples/core_graphics.rs | 2 +- objc_encode/examples/ns_string.rs | 17 +-- objc_encode/examples/ns_uinteger.rs | 31 ++++ objc_encode/examples/opaque_type.rs | 34 +++++ objc_encode/src/encode.rs | 180 +++++++++++++++++++---- objc_encode/src/encoding.rs | 19 ++- objc_encode/src/lib.rs | 8 +- objc_foundation/examples/custom_class.rs | 6 +- objc_foundation/src/enumerator.rs | 22 ++- objc_foundation/src/macros.rs | 16 +- 12 files changed, 277 insertions(+), 80 deletions(-) create mode 100644 objc_encode/examples/ns_uinteger.rs create mode 100644 objc_encode/examples/opaque_type.rs diff --git a/objc/src/encode.rs b/objc/src/encode.rs index 1e9b21f9d..7d2769105 100644 --- a/objc/src/encode.rs +++ b/objc/src/encode.rs @@ -1,24 +1,16 @@ use crate::runtime::{Class, Object, Sel}; -use crate::{Encode, Encoding}; +use crate::{Encode, Encoding, RefEncode}; unsafe impl Encode for Sel { const ENCODING: Encoding<'static> = Encoding::Sel; } -unsafe impl<'a> Encode for &'a Object { - const ENCODING: Encoding<'static> = Encoding::Object; +unsafe impl RefEncode for Object { + const ENCODING_REF: Encoding<'static> = Encoding::Object; } -unsafe impl<'a> Encode for &'a mut Object { - const ENCODING: Encoding<'static> = Encoding::Object; -} - -unsafe impl<'a> Encode for &'a Class { - const ENCODING: Encoding<'static> = Encoding::Class; -} - -unsafe impl<'a> Encode for &'a mut Class { - const ENCODING: Encoding<'static> = Encoding::Class; +unsafe impl RefEncode for Class { + const ENCODING_REF: Encoding<'static> = Encoding::Class; } /// Types that represent a group of arguments, where each has an Objective-C @@ -55,8 +47,8 @@ encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L); #[cfg(test)] mod tests { use crate::runtime::{Class, Object, Sel}; + use crate::Encode; use alloc::string::ToString; - use objc_encode::Encode; #[test] fn test_encode() { diff --git a/objc/src/lib.rs b/objc/src/lib.rs index 9cfaf408f..bc5d0b8c7 100644 --- a/objc/src/lib.rs +++ b/objc/src/lib.rs @@ -75,7 +75,7 @@ extern "C" {} #[doc = include_str!("../../README.md")] extern "C" {} -pub use objc_encode::{Encode, Encoding}; +pub use objc_encode::{Encode, Encoding, RefEncode}; pub use crate::encode::EncodeArguments; pub use crate::message::{Message, MessageArguments, MessageError}; diff --git a/objc_encode/examples/core_graphics.rs b/objc_encode/examples/core_graphics.rs index ed2f7c85a..611579e73 100644 --- a/objc_encode/examples/core_graphics.rs +++ b/objc_encode/examples/core_graphics.rs @@ -1,4 +1,4 @@ -use objc_encode::{Encode, Encoding}; +use objc::{Encode, Encoding}; #[cfg(target_pointer_width = "32")] type CGFloat = f32; diff --git a/objc_encode/examples/ns_string.rs b/objc_encode/examples/ns_string.rs index 85f51a828..afc890424 100644 --- a/objc_encode/examples/ns_string.rs +++ b/objc_encode/examples/ns_string.rs @@ -1,4 +1,4 @@ -use objc_encode::{Encode, Encoding}; +use objc::{Encode, Encoding, RefEncode}; /// We don't know the size of NSString, so we can only hold pointers to it. /// @@ -10,18 +10,9 @@ struct NSString { _priv: [u8; 0], } -/// Implement `Encode` for references. -/// -/// This also implements for `*mut NSString` and `Option<&mut NSString>`. -unsafe impl<'a> Encode for &'a NSString { - const ENCODING: Encoding<'static> = Encoding::Object; -} - -/// Implement `Encode` for mutable references. -/// -/// This also implements for `*mut NSString` and `Option<&mut NSString>`. -unsafe impl<'a> Encode for &'a mut NSString { - const ENCODING: Encoding<'static> = Encoding::Object; +/// Implement `RefEncode` for pointers and references to the string. +unsafe impl RefEncode for NSString { + const ENCODING_REF: Encoding<'static> = Encoding::Object; } fn main() { diff --git a/objc_encode/examples/ns_uinteger.rs b/objc_encode/examples/ns_uinteger.rs new file mode 100644 index 000000000..0473deecf --- /dev/null +++ b/objc_encode/examples/ns_uinteger.rs @@ -0,0 +1,31 @@ +//! Implementing `Encode` and `RefEncode` for `NSUInteger`. +//! +//! Note that in this case `NSUInteger` could actually just be a type alias +//! for `usize`. +use objc::{Encode, Encoding, RefEncode}; + +#[repr(transparent)] +struct NSUInteger { + _inner: usize, +} + +// SAFETY: `NSUInteger` has the same `repr` as `usize`. +unsafe impl Encode for NSUInteger { + /// Running `@encode(NSUInteger)` gives `Q` on 64-bit systems and `I` on + /// 32-bit systems. This corresponds exactly to `usize`, which is also how + /// we've defined our struct. + const ENCODING: Encoding<'static> = usize::ENCODING; +} + +// SAFETY: `&NSUInteger` has the same representation as `&usize`. +unsafe impl RefEncode for NSUInteger { + /// Running `@encode(NSUInteger*)` gives `^Q` on 64-bit systems and `^I` + /// on 32-bit systems. So implementing `RefEncode` as a plain pointer is + /// correct. + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&NSUInteger::ENCODING); +} + +fn main() { + assert_eq!(&NSUInteger::ENCODING, "Q"); + assert_eq!(&<&NSUInteger>::ENCODING, "^Q"); +} diff --git a/objc_encode/examples/opaque_type.rs b/objc_encode/examples/opaque_type.rs new file mode 100644 index 000000000..72df41460 --- /dev/null +++ b/objc_encode/examples/opaque_type.rs @@ -0,0 +1,34 @@ +//! Implementing `RefEncode` for `NSDecimal`. +use objc::{Encoding, RefEncode}; + +/// We choose in this case to represent `NSDecimal` as an opaque struct +/// (and in the future as an `extern type`) because we don't know much +/// about the internals. +/// +/// Therefore we do not implement `Encode`, but when implementing `RefEncode` +/// the type-encoding still has to be correct. +#[repr(C)] +struct NSDecimal { + _priv: [u8; 0], +} + +// SAFETY: `&NSDecimal` is a pointer. +unsafe impl RefEncode for NSDecimal { + // Running `@encode` on `NSDecimal*` on my 64-bit system gives `^{?=cCCC[38C]}`. + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Encoding::Struct( + "?", + &[ + Encoding::Char, + Encoding::UChar, + Encoding::UChar, + Encoding::UChar, + Encoding::Array(38, &Encoding::UChar), + ], + )); +} + +fn main() { + assert_eq!(&NSDecimal::ENCODING_REF, "^{?=cCCC[38C]}"); + // Does not compile: + // println!("{:?}", NSDecimal::ENCODING); +} diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index ca875c78a..5db43f669 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -4,16 +4,135 @@ use crate::Encoding; /// Types that have an Objective-C type-encoding. /// +/// Usually you will want to implement [`RefEncode`] as well. +/// +/// If your type is an opaque type you should not need to implement this; +/// there you will only need [`RefEncode`]. +/// /// # Safety /// +/// The type must be FFI-safe, see the [nomicon on other `repr`s][reprs]. +/// /// Objective-C will make assumptions about the type (like its size and /// alignment) from its encoding, so the implementer must verify that the /// encoding is accurate. +/// +/// Concretely, [`Self::ENCODING`] must match the result of running `@encode` +/// in Objective-C with the type in question. +/// +/// # Examples +/// +/// Implementing for a struct: +/// +/// ``` +/// # use objc_encode::{Encode, Encoding, RefEncode}; +/// # use core::ffi::c_void; +/// # +/// #[repr(C)] +/// struct MyType { +/// a: i32, +/// b: bool, +/// c: *const c_void, +/// } +/// +/// unsafe impl Encode for MyType { +/// const ENCODING: Encoding<'static> = Encoding::Struct( +/// // The name of the type that Objective-C sees. +/// "MyType", +/// &[ +/// // Delegate to field's implementations. +/// // The order is the same as in the definition. +/// i32::ENCODING, +/// bool::ENCODING, +/// <*const c_void>::ENCODING, +/// ], +/// ); +/// } +/// +/// // Note: You would also implement `RefEncode` for this type. +/// ``` +/// +/// [reprs]: https://doc.rust-lang.org/nomicon/other-reprs.html pub unsafe trait Encode { /// The Objective-C type-encoding for this type. const ENCODING: Encoding<'static>; } +/// Types whoose references has an Objective-C type-encoding. +/// +/// Implementing this for `T` provides [`Encode`] implementations for: +/// - `*const T` +/// - `*mut T` +/// - `&T` +/// - `&mut T` +/// - `Option<&T>` +/// - `Option<&mut T>` +/// +/// # Reasoning behind this trait's existence +/// +/// External crates cannot implement [`Encode`] for pointers or [`Option`]s +/// containing references, so instead, they can implement this trait. +/// Additionally it would be very cumbersome if every type had to implement +/// [`Encode`] for all possible pointer types. +/// +/// Finally, having this trait allows for much cleaner generic code that need +/// to represent types that can be encoded as pointers. +/// +/// # Safety +/// +/// References to the object must be FFI-safe. +/// +/// See the nomicon entry on [representing opaque structs][opaque] for +/// information on how to represent objects that you don't know the layout of +/// (or use `extern type` ([RFC-1861]) if you're using nightly). +/// +/// Objective-C will make assumptions about the type (like its size and +/// alignment) from its encoding, so the implementer must verify that the +/// encoding is accurate. +/// +/// Concretely, [`Self::ENCODING_REF`] must match the result of running +/// `@encode` in Objective-C with a pointer to the type in question. +/// +/// [opaque]: https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs +/// [RFC-1861]: https://rust-lang.github.io/rfcs/1861-extern-types.html +pub unsafe trait RefEncode { + /// The Objective-C type-encoding for a reference of this type. + /// + /// Should be one of [`Encoding::Object`], [`Encoding::Block`], + /// [`Encoding::Class`], [`Encoding::Pointer`], [`Encoding::Sel`] or + /// [`Encoding::Unknown`]. + /// + /// # Examples + /// + /// This is usually implemented either as an object pointer: + /// ``` + /// # use objc_encode::{Encoding, RefEncode}; + /// # #[repr(C)] + /// # struct MyObject { + /// # _priv: [u8; 0], + /// # } + /// # unsafe impl RefEncode for MyObject { + /// const ENCODING_REF: Encoding<'static> = Encoding::Object; + /// # } + /// ``` + /// + /// Or as a pointer to the type, delegating the rest to the [`Encode`] + /// implementation: + /// ``` + /// # use objc_encode::{Encode, Encoding, RefEncode}; + /// # #[repr(transparent)] + /// # struct MyType(i32); + /// # unsafe impl Encode for MyType { + /// # const ENCODING: Encoding<'static> = i32::ENCODING; + /// # } + /// # unsafe impl RefEncode for MyType { + /// const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); + /// # } + /// ``` + const ENCODING_REF: Encoding<'static>; +} + +/// Simple helper for implementing [`Encode`]. macro_rules! encode_impls { ($($t:ty : $e:ident,)*) => ($( unsafe impl Encode for $t { @@ -41,6 +160,9 @@ encode_impls!( *const u8: String, ); +/// The encoding of [`isize`] varies based on the target pointer width. +/// +/// This makes it equal to `NSInteger`. unsafe impl Encode for isize { #[cfg(target_pointer_width = "16")] const ENCODING: Encoding<'static> = i16::ENCODING; @@ -52,6 +174,9 @@ unsafe impl Encode for isize { const ENCODING: Encoding<'static> = i64::ENCODING; } +/// The encoding of [`usize`] varies based on the target pointer width. +/// +/// This makes it equal to `NSUInteger`. unsafe impl Encode for usize { #[cfg(target_pointer_width = "16")] const ENCODING: Encoding<'static> = u16::ENCODING; @@ -63,52 +188,51 @@ unsafe impl Encode for usize { const ENCODING: Encoding<'static> = u64::ENCODING; } -unsafe impl Encode for *mut c_void { +/// [`Encode`] is implemented manually for `*const c_void`, instead of +/// implementing [`RefEncode`], to discourage creating `&c_void`. +unsafe impl Encode for *const c_void { const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Void); } -unsafe impl Encode for *const c_void { +/// [`Encode`] is implemented manually for `*mut c_void`, instead of +/// implementing [`RefEncode`], to discourage creating `&mut c_void`. +unsafe impl Encode for *mut c_void { const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Void); } unsafe impl Encode for [T; LENGTH] { - const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &::ENCODING); + const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING); } -// External crates cannot implement Encode for pointers or [`Option`], but -// they *can* implement it for references. See -// [rust-lang/rust#25126](https://github.com/rust-lang/rust/issues/25126) +// Implement `Encode` for types that are `RefEncode`. // -// So, as a workaround, we provide implementations for these types that return -// the same encoding as references. +// This allows users to implement `Encode` for custom types that have a +// specific encoding as a pointer, instead of having to implement it for each +// pointer-like type in turn. // // Using `?Sized` is safe here because we delegate to other implementations // (which will verify that the implementation is safe for the unsized type). -unsafe impl Encode for *const T -where - for<'b> &'b T: Encode, -{ - const ENCODING: Encoding<'static> = <&T>::ENCODING; +unsafe impl Encode for *const T { + const ENCODING: Encoding<'static> = T::ENCODING_REF; +} + +unsafe impl Encode for *mut T { + const ENCODING: Encoding<'static> = T::ENCODING_REF; +} + +unsafe impl<'a, T: RefEncode + ?Sized> Encode for &'a T { + const ENCODING: Encoding<'static> = T::ENCODING_REF; } -unsafe impl Encode for *mut T -where - for<'b> &'b mut T: Encode, -{ - const ENCODING: Encoding<'static> = <&mut T>::ENCODING; +unsafe impl<'a, T: RefEncode + ?Sized> Encode for &'a mut T { + const ENCODING: Encoding<'static> = T::ENCODING_REF; } -unsafe impl<'a, T: ?Sized> Encode for Option<&'a T> -where - for<'b> &'b T: Encode, -{ - const ENCODING: Encoding<'static> = <&T>::ENCODING; +unsafe impl<'a, T: RefEncode + ?Sized> Encode for Option<&'a T> { + const ENCODING: Encoding<'static> = T::ENCODING_REF; } -unsafe impl<'a, T: ?Sized> Encode for Option<&'a mut T> -where - for<'b> &'b mut T: Encode, -{ - const ENCODING: Encoding<'static> = <&mut T>::ENCODING; +unsafe impl<'a, T: RefEncode + ?Sized> Encode for Option<&'a mut T> { + const ENCODING: Encoding<'static> = T::ENCODING_REF; } diff --git a/objc_encode/src/encoding.rs b/objc_encode/src/encoding.rs index 37ce2dfdd..e1079422b 100644 --- a/objc_encode/src/encoding.rs +++ b/objc_encode/src/encoding.rs @@ -2,10 +2,17 @@ use core::fmt; use crate::parse; -/// An Objective-C type encoding. +/// An Objective-C type-encoding. /// -/// For more information, see Apple's documentation: -/// +/// Can be retrieved in Objective-C for a type `T` using the `@encode(T)` +/// directive. +/// ```objective-c , ignore +/// NSLog(@"Encoding of NSException: %s", @encode(NSException)); +/// ``` +/// +/// For more information, see [Apple's documentation][ocrtTypeEncodings]. +/// +/// [ocrtTypeEncodings]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Encoding<'a> { /// A C `char`. Corresponds to the `c` code. @@ -62,10 +69,16 @@ pub enum Encoding<'a> { Array(usize, &'a Encoding<'a>), /// A struct with the given name and fields. /// + /// The order of the fields must match the order of the order in this. + /// + /// It is not uncommon for the name to be `"?"`. + /// /// Corresponds to the `{name=fields...}` code. Struct(&'a str, &'a [Encoding<'a>]), /// A union with the given name and fields. /// + /// The order of the fields must match the order of the order in this. + /// /// Corresponds to the `(name=fields...)` code. Union(&'a str, &'a [Encoding<'a>]), } diff --git a/objc_encode/src/lib.rs b/objc_encode/src/lib.rs index e1ff88f72..38d3831b0 100644 --- a/objc_encode/src/lib.rs +++ b/objc_encode/src/lib.rs @@ -26,7 +26,7 @@ An [`Encoding`] can be compared with an encoding string from the Objective-C runtime: ``` -# use objc_encode::Encode; +# use objc::Encode; assert!(&i32::ENCODING == "i"); ``` @@ -37,7 +37,7 @@ representation. This can be generated conveniently through the [`to_string`][`alloc::string::ToString::to_string`] method: ``` -# use objc_encode::Encode; +# use objc::Encode; assert_eq!(i32::ENCODING.to_string(), "i"); ``` */ @@ -58,5 +58,5 @@ mod encode; mod encoding; mod parse; -pub use crate::encode::Encode; -pub use crate::encoding::Encoding; +pub use self::encode::{Encode, RefEncode}; +pub use self::encoding::Encoding; diff --git a/objc_foundation/examples/custom_class.rs b/objc_foundation/examples/custom_class.rs index 9b988c2de..5691d22de 100644 --- a/objc_foundation/examples/custom_class.rs +++ b/objc_foundation/examples/custom_class.rs @@ -2,8 +2,8 @@ use std::sync::Once; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; -use objc::Message; use objc::{msg_send, sel}; +use objc::{Encoding, Message, RefEncode}; use objc_foundation::{INSObject, NSObject}; /// In the future this should be an `extern type`, if that gets stabilized, @@ -16,6 +16,10 @@ pub struct MYObject { _priv: [u8; 0], } +unsafe impl RefEncode for MYObject { + const ENCODING_REF: Encoding<'static> = Encoding::Object; +} + impl MYObject { fn number(&self) -> u32 { unsafe { diff --git a/objc_foundation/src/enumerator.rs b/objc_foundation/src/enumerator.rs index 62e9a486c..285c076e3 100644 --- a/objc_foundation/src/enumerator.rs +++ b/objc_foundation/src/enumerator.rs @@ -4,8 +4,8 @@ use core::ptr; use core::slice; use std::os::raw::c_ulong; -use objc::msg_send; use objc::runtime::Object; +use objc::{msg_send, Encode, Encoding, RefEncode}; use objc_id::Id; use super::INSObject; @@ -63,13 +63,29 @@ pub trait INSFastEnumeration: INSObject { } #[repr(C)] -struct NSFastEnumerationState { - state: c_ulong, +struct NSFastEnumerationState { + state: c_ulong, // TODO: Verify this is actually always 64 bit items_ptr: *const *const T, mutations_ptr: *mut c_ulong, extra: [c_ulong; 5], } +unsafe impl Encode for NSFastEnumerationState { + const ENCODING: Encoding<'static> = Encoding::Struct( + "?", + &[ + c_ulong::ENCODING, + Encoding::Pointer(&Encoding::Object), // <*const *const T>::ENCODING + Encoding::Pointer(&c_ulong::ENCODING), + Encoding::Array(5, &c_ulong::ENCODING), + ], + ); +} + +unsafe impl RefEncode for NSFastEnumerationState { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); +} + fn enumerate<'a, 'b: 'a, C: INSFastEnumeration>( object: &'b C, state: &mut NSFastEnumerationState, diff --git a/objc_foundation/src/macros.rs b/objc_foundation/src/macros.rs index 6b9b0f34d..b93521fbf 100644 --- a/objc_foundation/src/macros.rs +++ b/objc_foundation/src/macros.rs @@ -9,12 +9,8 @@ macro_rules! object_struct { unsafe impl ::objc::Message for $name {} - unsafe impl<'a> ::objc::Encode for &'a $name { - const ENCODING: ::objc::Encoding<'static> = ::objc::Encoding::Object; - } - - unsafe impl<'a> ::objc::Encode for &'a mut $name { - const ENCODING: ::objc::Encoding<'static> = ::objc::Encoding::Object; + unsafe impl ::objc::RefEncode for $name { + const ENCODING_REF: ::objc::Encoding<'static> = ::objc::Encoding::Object; } impl $crate::INSObject for $name { @@ -61,12 +57,8 @@ macro_rules! object_impl { ($name:ident, $($t:ident),*) => ( unsafe impl<$($t),*> ::objc::Message for $name<$($t),*> { } - unsafe impl<'a, $($t),*> ::objc::Encode for &'a $name<$($t),*> { - const ENCODING: ::objc::Encoding<'static> = ::objc::Encoding::Object; - } - - unsafe impl<'a, $($t),*> ::objc::Encode for &'a mut $name<$($t),*> { - const ENCODING: ::objc::Encoding<'static> = ::objc::Encoding::Object; + unsafe impl<$($t),*> ::objc::RefEncode for $name<$($t),*> { + const ENCODING_REF: ::objc::Encoding<'static> = ::objc::Encoding::Object; } ); } From 7aa257837aeec47e7698a74894cbea3660f42520 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 11:20:51 +0200 Subject: [PATCH 3/9] Implement RefEncode for pointers to types that are RefEncode Implements Encode from pointers to pointers to pointers and so on. --- objc_encode/src/encode.rs | 73 +++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index 5db43f669..d0c914a15 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -204,35 +204,58 @@ unsafe impl Encode for [T; LENGTH] { const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING); } -// Implement `Encode` for types that are `RefEncode`. -// -// This allows users to implement `Encode` for custom types that have a -// specific encoding as a pointer, instead of having to implement it for each -// pointer-like type in turn. -// -// Using `?Sized` is safe here because we delegate to other implementations -// (which will verify that the implementation is safe for the unsized type). +/// Helper for implementing `Encode`/`RefEncode` for pointers to types that +/// implement `RefEncode`. +/// +/// Using `?Sized` is safe here because we delegate to other implementations +/// (which will verify that the implementation is safe for the unsized type). +macro_rules! encode_pointer_impls { + (unsafe impl $x:ident for &$t:ident { + const $c:ident = $e:expr; + }) => ( + unsafe impl<$t: RefEncode + ?Sized> $x for *const $t { + const $c: Encoding<'static> = $e; + } -unsafe impl Encode for *const T { - const ENCODING: Encoding<'static> = T::ENCODING_REF; -} + unsafe impl<$t: RefEncode + ?Sized> $x for *mut $t { + const $c: Encoding<'static> = $e; + } -unsafe impl Encode for *mut T { - const ENCODING: Encoding<'static> = T::ENCODING_REF; -} + unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a $t { + const $c: Encoding<'static> = $e; + } -unsafe impl<'a, T: RefEncode + ?Sized> Encode for &'a T { - const ENCODING: Encoding<'static> = T::ENCODING_REF; -} + unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a mut $t { + const $c: Encoding<'static> = $e; + } -unsafe impl<'a, T: RefEncode + ?Sized> Encode for &'a mut T { - const ENCODING: Encoding<'static> = T::ENCODING_REF; -} + unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a $t> { + const $c: Encoding<'static> = $e; + } -unsafe impl<'a, T: RefEncode + ?Sized> Encode for Option<&'a T> { - const ENCODING: Encoding<'static> = T::ENCODING_REF; + unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a mut $t> { + const $c: Encoding<'static> = $e; + } + ); } -unsafe impl<'a, T: RefEncode + ?Sized> Encode for Option<&'a mut T> { - const ENCODING: Encoding<'static> = T::ENCODING_REF; -} +// Implement `Encode` for types that are `RefEncode`. +// +// This allows users to implement `Encode` for custom types that have a +// specific encoding as a pointer, instead of having to implement it for each +// pointer-like type in turn. +encode_pointer_impls!( + unsafe impl Encode for &T { + const ENCODING = T::ENCODING_REF; + } +); + +// Implement `RefEncode` for pointers to types that are `RefEncode`. +// +// This implements `Encode` for pointers to pointers (to pointers, and so on), +// which would otherwise be very cumbersome to do manually. +encode_pointer_impls!( + unsafe impl RefEncode for &T { + const ENCODING_REF = Encoding::Pointer(&T::ENCODING_REF); + } +); From b8ac799411b6639f5930a4ee4b223a33fc1e4659 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 13:20:57 +0200 Subject: [PATCH 4/9] Implement Encode for NonZero integer types --- objc_encode/src/encode.rs | 98 +++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index d0c914a15..5e945465a 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -134,7 +134,7 @@ pub unsafe trait RefEncode { /// Simple helper for implementing [`Encode`]. macro_rules! encode_impls { - ($($t:ty : $e:ident,)*) => ($( + ($($t:ty => $e:ident,)*) => ($( unsafe impl Encode for $t { const ENCODING: Encoding<'static> = Encoding::$e; } @@ -142,52 +142,72 @@ macro_rules! encode_impls { } encode_impls!( - i8: Char, - i16: Short, - i32: Int, - i64: LongLong, - u8: UChar, - u16: UShort, - u32: UInt, - u64: ULongLong, - f32: Float, - f64: Double, - bool: Bool, - (): Void, - *mut i8: String, - *const i8: String, - *mut u8: String, - *const u8: String, + i8 => Char, + i16 => Short, + i32 => Int, + i64 => LongLong, + u8 => UChar, + u16 => UShort, + u32 => UInt, + u64 => ULongLong, + f32 => Float, + f64 => Double, + bool => Bool, + () => Void, + *mut i8 => String, + *const i8 => String, + *mut u8 => String, + *const u8 => String, ); -/// The encoding of [`isize`] varies based on the target pointer width. -/// -/// This makes it equal to `NSInteger`. -unsafe impl Encode for isize { - #[cfg(target_pointer_width = "16")] - const ENCODING: Encoding<'static> = i16::ENCODING; - - #[cfg(target_pointer_width = "32")] - const ENCODING: Encoding<'static> = i32::ENCODING; - - #[cfg(target_pointer_width = "64")] - const ENCODING: Encoding<'static> = i64::ENCODING; +macro_rules! encode_impls_size { + ($($t:ty => ($t16:ty, $t32:ty, $t64:ty),)*) => ($( + #[doc = concat!("The encoding of [`", stringify!($t), "`] varies based on the target pointer width.")] + unsafe impl Encode for $t { + #[cfg(target_pointer_width = "16")] + const ENCODING: Encoding<'static> = <$t16>::ENCODING; + #[cfg(target_pointer_width = "32")] + const ENCODING: Encoding<'static> = <$t32>::ENCODING; + #[cfg(target_pointer_width = "64")] + const ENCODING: Encoding<'static> = <$t64>::ENCODING; + } + )*); } -/// The encoding of [`usize`] varies based on the target pointer width. -/// -/// This makes it equal to `NSUInteger`. -unsafe impl Encode for usize { - #[cfg(target_pointer_width = "16")] - const ENCODING: Encoding<'static> = u16::ENCODING; +encode_impls_size!( + isize => (i16, i32, i64), + usize => (u16, u32, u64), +); - #[cfg(target_pointer_width = "32")] - const ENCODING: Encoding<'static> = u32::ENCODING; +/// Simple helper for implementing [`Encode`] for integer types. +macro_rules! encode_impls_nonzero { + ($($nonzero:ident => $type:ty,)*) => ($( + unsafe impl Encode for core::num::$nonzero { + const ENCODING: Encoding<'static> = <$type>::ENCODING; + } - #[cfg(target_pointer_width = "64")] - const ENCODING: Encoding<'static> = u64::ENCODING; + unsafe impl Encode for Option { + const ENCODING: Encoding<'static> = <$type>::ENCODING; + } + )*); } +encode_impls_nonzero!( + NonZeroI8 => i8, + NonZeroI16 => i16, + NonZeroI32 => i32, + NonZeroI64 => i64, + NonZeroIsize => isize, + NonZeroU8 => u8, + NonZeroU16 => u16, + NonZeroU32 => u32, + NonZeroU64 => u64, + NonZeroUsize => usize, +); + +// Note: I'm not sure atomic integers would be safe, since they might need the +// Objective-C runtime to insert proper memory fences and ordering stuff? + /// [`Encode`] is implemented manually for `*const c_void`, instead of /// implementing [`RefEncode`], to discourage creating `&c_void`. unsafe impl Encode for *const c_void { From a46a8101b0b385e26ecbf888c4b4f8e686d261aa Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 13:24:03 +0200 Subject: [PATCH 5/9] Implement Encode and RefEncode for more builtin types - RefEncode (Encode already exist) for [T; LENGTH] - Both for ManuallyDrop - Both for Pin - Both for NonNull - Both for Option> --- objc_encode/src/encode.rs | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index 5e945465a..8e36421e3 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -1,4 +1,4 @@ -use core::ffi::c_void; +use core::{ffi::c_void, mem::ManuallyDrop, pin::Pin, ptr::NonNull}; use crate::Encoding; @@ -20,6 +20,10 @@ use crate::Encoding; /// Concretely, [`Self::ENCODING`] must match the result of running `@encode` /// in Objective-C with the type in question. /// +/// You should also beware of having [`Drop`] types implement this, since when +/// passed to Objective-C via. `objc::msg_send!` their destructor will not be +/// called! +/// /// # Examples /// /// Implementing for a struct: @@ -65,8 +69,10 @@ pub unsafe trait Encode { /// - `*mut T` /// - `&T` /// - `&mut T` +/// - `NonNull` /// - `Option<&T>` /// - `Option<&mut T>` +/// - `Option>` /// /// # Reasoning behind this trait's existence /// @@ -224,6 +230,32 @@ unsafe impl Encode for [T; LENGTH] { const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING); } +unsafe impl RefEncode for [T; LENGTH] { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); +} + +// SAFETY: `ManuallyDrop` is `repr(transparent)`. +unsafe impl Encode for ManuallyDrop { + const ENCODING: Encoding<'static> = T::ENCODING; +} + +// With specialization: `impl Encode for ManuallyDrop>` + +// SAFETY: `ManuallyDrop` is `repr(transparent)`. +unsafe impl RefEncode for ManuallyDrop { + const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; +} + +// SAFETY: `Pin` is `repr(transparent)`. +unsafe impl Encode for Pin { + const ENCODING: Encoding<'static> = T::ENCODING; +} + +// SAFETY: `Pin` is `repr(transparent)`. +unsafe impl RefEncode for Pin { + const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; +} + /// Helper for implementing `Encode`/`RefEncode` for pointers to types that /// implement `RefEncode`. /// @@ -249,6 +281,10 @@ macro_rules! encode_pointer_impls { const $c: Encoding<'static> = $e; } + unsafe impl $x for NonNull<$t> { + const $c: Encoding<'static> = $e; + } + unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a $t> { const $c: Encoding<'static> = $e; } @@ -256,6 +292,10 @@ macro_rules! encode_pointer_impls { unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a mut $t> { const $c: Encoding<'static> = $e; } + + unsafe impl $x for Option> { + const $c: Encoding<'static> = $e; + } ); } From 36c369f71b92417bfd7c905179245072527fbcf3 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 23:04:00 +0200 Subject: [PATCH 6/9] Add Encode/RefEncode implementations for function pointers Also moves EncodeArguments to objc_encode crate. --- objc/src/encode.rs | 31 ------------ objc/src/lib.rs | 3 +- objc_encode/src/encode.rs | 95 ++++++++++++++++++++++++++++++++++++ objc_encode/src/lib.rs | 2 +- objc_foundation/src/array.rs | 34 ++++++++----- 5 files changed, 118 insertions(+), 47 deletions(-) diff --git a/objc/src/encode.rs b/objc/src/encode.rs index 7d2769105..e1010f770 100644 --- a/objc/src/encode.rs +++ b/objc/src/encode.rs @@ -13,37 +13,6 @@ unsafe impl RefEncode for Class { const ENCODING_REF: Encoding<'static> = Encoding::Class; } -/// Types that represent a group of arguments, where each has an Objective-C -/// type-encoding. -pub trait EncodeArguments { - /// The type as which the encodings for Self will be returned. - const ENCODINGS: &'static [Encoding<'static>]; -} - -macro_rules! encode_args_impl { - ($($t:ident),*) => ( - impl<$($t: Encode),*> EncodeArguments for ($($t,)*) { - const ENCODINGS: &'static [Encoding<'static>] = &[ - $($t::ENCODING),* - ]; - } - ); -} - -encode_args_impl!(); -encode_args_impl!(A); -encode_args_impl!(A, B); -encode_args_impl!(A, B, C); -encode_args_impl!(A, B, C, D); -encode_args_impl!(A, B, C, D, E); -encode_args_impl!(A, B, C, D, E, F); -encode_args_impl!(A, B, C, D, E, F, G); -encode_args_impl!(A, B, C, D, E, F, G, H); -encode_args_impl!(A, B, C, D, E, F, G, H, I); -encode_args_impl!(A, B, C, D, E, F, G, H, I, J); -encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K); -encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L); - #[cfg(test)] mod tests { use crate::runtime::{Class, Object, Sel}; diff --git a/objc/src/lib.rs b/objc/src/lib.rs index bc5d0b8c7..fd0d5bb60 100644 --- a/objc/src/lib.rs +++ b/objc/src/lib.rs @@ -75,9 +75,8 @@ extern "C" {} #[doc = include_str!("../../README.md")] extern "C" {} -pub use objc_encode::{Encode, Encoding, RefEncode}; +pub use objc_encode::{Encode, EncodeArguments, Encoding, RefEncode}; -pub use crate::encode::EncodeArguments; pub use crate::message::{Message, MessageArguments, MessageError}; pub use crate::cache::CachedClass as __CachedClass; diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index 8e36421e3..f0011563a 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -319,3 +319,98 @@ encode_pointer_impls!( const ENCODING_REF = Encoding::Pointer(&T::ENCODING_REF); } ); + +/// Helper for implementing [`Encode`]/[`RefEncode`] for function pointers +/// whoose arguments implement [`Encode`]. +/// +/// Ideally we'd implement it for all function pointers, but due to coherence +/// issues, see , function +/// pointers that take arguments with "special lifetimes" (don't know the +/// termonology) don't get implemented properly. +/// +/// We could fix it by adding those impls and allowing `coherence_leak_check`, +/// but it would have to be done for _all_ references, `Option<&T>` and such as +/// well. So trying to do it quickly requires generating a polynomial amount of +/// implementations, which IMO is overkill for such a small issue. +/// +/// Using `?Sized` is probably not safe here because C functions can only take +/// and return items with a known size. +macro_rules! encode_fn_pointer_impl { + (@ $FnTy: ty, $($Arg: ident),*) => { + unsafe impl Encode for $FnTy { + const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Unknown); + } + + unsafe impl RefEncode for $FnTy { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); + } + }; + ($($Arg: ident),+) => { + // Normal functions + encode_fn_pointer_impl!(@ extern "C" fn($($Arg),+) -> Ret, $($Arg),+ ); + encode_fn_pointer_impl!(@ unsafe extern "C" fn($($Arg),+) -> Ret, $($Arg),+ ); + // Variadic functions + encode_fn_pointer_impl!(@ extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ ); + encode_fn_pointer_impl!(@ unsafe extern "C" fn($($Arg),+ , ...) -> Ret, $($Arg),+ ); + }; + () => { + // No variadic functions with 0 parameters + encode_fn_pointer_impl!(@ extern "C" fn() -> Ret, ); + encode_fn_pointer_impl!(@ unsafe extern "C" fn() -> Ret, ); + }; +} + +encode_fn_pointer_impl!(); +encode_fn_pointer_impl!(A); +encode_fn_pointer_impl!(A, B); +encode_fn_pointer_impl!(A, B, C); +encode_fn_pointer_impl!(A, B, C, D); +encode_fn_pointer_impl!(A, B, C, D, E); +encode_fn_pointer_impl!(A, B, C, D, E, F); +encode_fn_pointer_impl!(A, B, C, D, E, F, G); +encode_fn_pointer_impl!(A, B, C, D, E, F, G, H); +encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I); +encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J); +encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J, K); +encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J, K, L); + +/// Types that represent an ordered group of function arguments, where each +/// argument has an Objective-C type-encoding. +/// +/// This is implemented for tuples, and is used to make generic code easier. +/// +/// Note that tuples themselves don't implement [`Encode`] directly because +/// they're not FFI-safe. +/// +/// # Safety +/// +/// You should not need to implement this. Open an issue if you know a +/// use-case where this restrition should be lifted! +pub unsafe trait EncodeArguments { + /// The encodings for the arguments. + const ENCODINGS: &'static [Encoding<'static>]; +} + +macro_rules! encode_args_impl { + ($($Arg: ident),*) => { + unsafe impl<$($Arg: Encode),*> EncodeArguments for ($($Arg,)*) { + const ENCODINGS: &'static [Encoding<'static>] = &[ + $($Arg::ENCODING),* + ]; + } + }; +} + +encode_args_impl!(); +encode_args_impl!(A); +encode_args_impl!(A, B); +encode_args_impl!(A, B, C); +encode_args_impl!(A, B, C, D); +encode_args_impl!(A, B, C, D, E); +encode_args_impl!(A, B, C, D, E, F); +encode_args_impl!(A, B, C, D, E, F, G); +encode_args_impl!(A, B, C, D, E, F, G, H); +encode_args_impl!(A, B, C, D, E, F, G, H, I); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L); diff --git a/objc_encode/src/lib.rs b/objc_encode/src/lib.rs index 38d3831b0..d89495f8e 100644 --- a/objc_encode/src/lib.rs +++ b/objc_encode/src/lib.rs @@ -58,5 +58,5 @@ mod encode; mod encoding; mod parse; -pub use self::encode::{Encode, RefEncode}; +pub use self::encode::{Encode, EncodeArguments, RefEncode}; pub use self::encoding::Encoding; diff --git a/objc_foundation/src/array.rs b/objc_foundation/src/array.rs index c3461c0d5..8537ddec0 100644 --- a/objc_foundation/src/array.rs +++ b/objc_foundation/src/array.rs @@ -19,6 +19,10 @@ pub enum NSComparisonResult { Descending = 1, } +unsafe impl Encode for NSComparisonResult { + const ENCODING: Encoding<'static> = isize::ENCODING; +} + impl NSComparisonResult { pub fn from_ordering(order: Ordering) -> NSComparisonResult { match order { @@ -314,26 +318,30 @@ pub trait INSMutableArray: INSArray { where F: FnMut(&Self::Item, &Self::Item) -> Ordering, { - extern "C" fn compare_with_closure( + extern "C" fn compare_with_closure Ordering>( obj1: &T, obj2: &T, - compare: &mut F, - ) -> NSComparisonResult - where - F: FnMut(&T, &T) -> Ordering, - { - NSComparisonResult::from_ordering((*compare)(obj1, obj2)) + context: *mut c_void, + ) -> NSComparisonResult { + // Bring back a reference to the closure. + // Guaranteed to be unique, we gave `sortUsingFunction` unique is + // ownership, and that method only runs one function at a time. + let closure: &mut F = unsafe { &mut *(context as *mut F) }; + + NSComparisonResult::from_ordering((*closure)(obj1, obj2)) } - let f: extern "C" fn(&Self::Item, &Self::Item, &mut F) -> NSComparisonResult = - compare_with_closure; + let f: extern "C" fn(_, _, _) -> _ = compare_with_closure::; + + // Grab a type-erased pointer to the closure (a pointer to stack). let mut closure = compare; - let closure_ptr: *mut F = &mut closure; - let context = closure_ptr as *mut c_void; + let context = &mut closure as *mut F as *mut c_void; + unsafe { - let _: () = msg_send![self, sortUsingFunction:f - context:context]; + let _: () = msg_send![self, sortUsingFunction:f context:context]; } + // Keep the closure alive until the function has run. + drop(closure); } } From dd40809633379816dbd5219d7d67c19c048f9585 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 23:14:43 +0200 Subject: [PATCH 7/9] Require `RefEncode` to be implemented for `Message` I can't imagine a case where you'd have a type that you can send messages to, but that can't be encoded as an Objective-C type-encoding, and having this a requirement makes downstream usage (e.g. objc_foundation) much nicer! --- objc/src/message/mod.rs | 13 +++++++------ objc_encode/src/encode.rs | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/objc/src/message/mod.rs b/objc/src/message/mod.rs index d942998a3..cd4ee710b 100644 --- a/objc/src/message/mod.rs +++ b/objc/src/message/mod.rs @@ -5,7 +5,7 @@ use core::mem; use std::error::Error; use crate::runtime::{Class, Imp, Object, Sel}; -use crate::{Encode, EncodeArguments}; +use crate::{Encode, EncodeArguments, RefEncode}; #[cfg(feature = "exception")] macro_rules! objc_try { @@ -52,18 +52,19 @@ struct Super { /// /// Examples include objects, classes, and blocks. /// -/// The type should also implement [`Encode`] for `&Self` and `&mut Self`. +/// Implementing this allows using pointers and references to the type as the +/// receiver (first argument) in the [`msg_send!`][`crate::msg_send`] macro. /// /// # Safety /// +/// The type must implement [`RefEncode`] and adhere to the safety guidelines +/// therein. +/// /// A pointer to the type must be able to be the receiver of an Objective-C /// message sent with [`objc_msgSend`] or similar. /// -/// The type must also have a C-compatible `repr` (`repr(C)`, `repr(u8)`, -/// `repr(transparent)` where the inner types are C-compatible, and so on). -/// /// [`objc_msgSend`]: https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend -pub unsafe trait Message { +pub unsafe trait Message: RefEncode { /** Sends a message to self with the given selector and arguments. diff --git a/objc_encode/src/encode.rs b/objc_encode/src/encode.rs index f0011563a..5a8218a6d 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -11,7 +11,9 @@ use crate::Encoding; /// /// # Safety /// -/// The type must be FFI-safe, see the [nomicon on other `repr`s][reprs]. +/// The type must be FFI-safe, meaning a C-compatible `repr` (`repr(C)`, +/// `repr(u8)`, `repr(transparent)` where the inner types are C-compatible, +/// and so on). See the [nomicon on other `repr`s][reprs]. /// /// Objective-C will make assumptions about the type (like its size and /// alignment) from its encoding, so the implementer must verify that the From b3ecc1e630dfac342678c360f291763ee28ae31d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Sep 2021 23:15:07 +0200 Subject: [PATCH 8/9] Fix CI for exception and verify_message features --- .github/workflows/ci.yml | 14 ++++++++++---- objc/src/message/mod.rs | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dce6d46f..88c4900d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --verbose + args: --verbose --no-default-features - name: Install GNUStep libobjc2 if: contains(matrix.platform.os, 'ubuntu') @@ -116,10 +116,16 @@ jobs: echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV echo "CPATH=/usr/local/include:$CPATH" >> $GITHUB_ENV - - name: Build and run tests + - name: Test uses: actions-rs/cargo@v1 with: command: test - # TODO: `objc/exception` feature is broken in objc_foundation - # TODO: `objc_foundation/block` feature doesn't work on GNUStep + # TODO: `objc_foundation/block` feature doesn't work args: --verbose --no-fail-fast --no-default-features + + - name: Test w. exception and verify_message features + uses: actions-rs/cargo@v1 + with: + command: test + # TODO: `objc_foundation/block` feature doesn't work + args: --verbose --no-fail-fast --no-default-features --features exception,verify_message diff --git a/objc/src/message/mod.rs b/objc/src/message/mod.rs index cd4ee710b..29660d8dc 100644 --- a/objc/src/message/mod.rs +++ b/objc/src/message/mod.rs @@ -11,10 +11,11 @@ use crate::{Encode, EncodeArguments, RefEncode}; macro_rules! objc_try { ($b:block) => { $crate::exception::catch_exception(|| $b).map_err(|exception| { + use alloc::borrow::ToOwned; if exception.is_null() { MessageError("Uncaught exception nil".to_owned()) } else { - MessageError(format!("Uncaught exception {:?}", &**exception)) + MessageError(alloc::format!("Uncaught exception {:?}", &**exception)) } }) }; From 1600f6ec69d772eeac7b9dea3f36e726d70dd298 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 2 Sep 2021 00:21:02 +0200 Subject: [PATCH 9/9] Update objc_encode documentation to mention new RefEncode type Also moves the primary documentation into the README.md --- objc_encode/Cargo.toml | 5 +-- objc_encode/README.md | 81 ++++++++++++++++++++++++++---------------- objc_encode/src/lib.rs | 44 +---------------------- 3 files changed, 54 insertions(+), 76 deletions(-) diff --git a/objc_encode/Cargo.toml b/objc_encode/Cargo.toml index 5693bfc4c..d4673c045 100644 --- a/objc_encode/Cargo.toml +++ b/objc_encode/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "objc-encode" -version = "1.1.0" # Remember to update html_root_url in lib.rs +# Remember to update html_root_url in lib.rs and README.md +version = "1.1.0" authors = ["Steven Sheldon", "Mads Marquart "] edition = "2018" -description = "Objective-C type encoding creation and parsing." +description = "Objective-C type-encodings" keywords = ["objective-c", "macos", "ios", "encode"] categories = [ "development-tools::ffi", diff --git a/objc_encode/README.md b/objc_encode/README.md index 28aaa5cb9..72287d5d8 100644 --- a/objc_encode/README.md +++ b/objc_encode/README.md @@ -1,64 +1,83 @@ -# `objc-encode` +# `objc-encode` - Objective-C type-encoding in Rust [![Latest version](https://badgen.net/crates/v/objc-encode)](https://crates.io/crates/objc-encode) [![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt) [![Documentation](https://docs.rs/objc-encode/badge.svg)](https://docs.rs/objc-encode/) [![CI Status](https://github.com/madsmtm/objc/workflows/CI/badge.svg)](https://github.com/madsmtm/objc/actions) -Objective-C type encoding creation and parsing in Rust. +The Objective-C directive `@encode` encodes types as strings for usage in +various places in the runtime. -The Objective-C compiler encodes types as strings for usage in the runtime. -This crate aims to provide a strongly-typed (rather than stringly-typed) way -to create and describe these type encodings without memory allocation in Rust. +This crate provides the `Encoding` type to describe and compare these +type-encodings without memory allocation. +Additionally it provides traits for annotating types that has a corresponding +Objective-C encoding, respectively `Encode` for structs and `RefEncode` for +references (and `EncodeArguments` for function arguments). -## Implementing Encode +These types are exported under the `objc` crate as well, so usually you would +just use that crate. -This crate declares an `Encode` trait that can be implemented for types that -the Objective-C compiler can encode. Implementing this trait looks like: +# Examples -```rust -use objc::{Encode, Encoding}; - -#[cfg(target_pointer_width = "32")] -type CGFloat = f32; +Implementing `Encode` and `RefEncode`: -#[cfg(target_pointer_width = "64")] -type CGFloat = f64; +```rust +use objc_encode::{Encode, Encoding, RefEncode}; #[repr(C)] -struct CGPoint { - x: CGFloat, - y: CGFloat, +struct MyObject { + a: f32, + b: bool, } -unsafe impl Encode for CGPoint { - const ENCODING: Encoding<'static> = - Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]); +unsafe impl Encode for MyObject { + const ENCODING: Encoding<'static> = Encoding::Struct( + "MyObject", + &[f32::ENCODING, bool::ENCODING + ]); } -``` -For an example of how this works with more complex types, like structs -containing structs, see the `core_graphics` example. +assert_eq!(&MyObject::ENCODING, "{MyObject=fB}"); -## Comparing with encoding strings +unsafe impl RefEncode for MyObject { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); +} + +assert_eq!(&MyObject::ENCODING_REF, "^{MyObject=fB}"); +``` An `Encoding` can be compared with an encoding string from the Objective-C runtime: ```rust use objc::Encode; - assert!(&i32::ENCODING == "i"); ``` -## Generating encoding strings - -Every `Encoding` implements `Display` as its string representation. -This can be generated conveniently through the `to_string` method: +`Encoding` implements `Display` as its string representation. This can be +generated conveniently through the `to_string` method: ```rust use objc::Encode; - assert_eq!(i32::ENCODING.to_string(), "i"); ``` + +See the [`examples`] folder for more complex usage. + +# Installation + +```toml +[dependencies] +objc-encode = "1.1.0" +``` + +# License + +This project is licensed under the MIT license, see [`../LICENSE.txt`]. + +Work is in progress to make it dual-licensed under the Apache License +(Version 2.0) as well. + +[`examples`]: https://github.com/madsmtm/objc/tree/master/objc_encode/examples +[`../LICENSE.txt`]: https://github.com/madsmtm/objc/blob/master/LICENSE.txt diff --git a/objc_encode/src/lib.rs b/objc_encode/src/lib.rs index d89495f8e..263135c59 100644 --- a/objc_encode/src/lib.rs +++ b/objc_encode/src/lib.rs @@ -1,46 +1,4 @@ -/*! -Objective-C type encoding creation and parsing in Rust. - -The Objective-C compiler encodes types as strings for usage in the runtime. -This crate aims to provide a strongly-typed (rather than stringly-typed) way -to create and describe these type encodings without memory allocation in Rust. - -# Implementing Encode - -This crate declares an [`Encode`] trait that can be implemented for types that -the Objective-C compiler can encode. Implementing this trait looks like: - -```ignore -unsafe impl Encode for CGPoint { - const ENCODING: Encoding<'static> = - Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFLOAT::ENCODING]); -} -``` - -For an example of how this works with more complex types, like structs -containing structs, see the `core_graphics` example. - -# Comparing with encoding strings - -An [`Encoding`] can be compared with an encoding string from the Objective-C -runtime: - -``` -# use objc::Encode; -assert!(&i32::ENCODING == "i"); -``` - -# Generating encoding strings - -Every [`Encoding`] implements [`Display`][`core::fmt::Display`] as its string -representation. This can be generated conveniently through the -[`to_string`][`alloc::string::ToString::to_string`] method: - -``` -# use objc::Encode; -assert_eq!(i32::ENCODING.to_string(), "i"); -``` -*/ +//! # Objective-C type-encoding #![no_std] #![warn(missing_docs)]