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/encode.rs b/objc/src/encode.rs index 1e9b21f9d..e1010f770 100644 --- a/objc/src/encode.rs +++ b/objc/src/encode.rs @@ -1,62 +1,23 @@ 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 RefEncode for Class { + const ENCODING_REF: Encoding<'static> = Encoding::Class; } -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; -} - -/// 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}; + 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..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}; +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/src/message/mod.rs b/objc/src/message/mod.rs index f23597abc..29660d8dc 100644 --- a/objc/src/message/mod.rs +++ b/objc/src/message/mod.rs @@ -5,16 +5,17 @@ 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 { ($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)) } }) }; @@ -52,18 +53,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. @@ -74,18 +76,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 +216,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 +249,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) } 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/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..5a8218a6d 100644 --- a/objc_encode/src/encode.rs +++ b/objc_encode/src/encode.rs @@ -1,21 +1,148 @@ -use core::ffi::c_void; +use core::{ffi::c_void, mem::ManuallyDrop, pin::Pin, ptr::NonNull}; 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, 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 /// encoding is accurate. +/// +/// 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: +/// +/// ``` +/// # 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` +/// - `NonNull` +/// - `Option<&T>` +/// - `Option<&mut T>` +/// - `Option>` +/// +/// # 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,)*) => ($( + ($($t:ty => $e:ident,)*) => ($( unsafe impl Encode for $t { const ENCODING: Encoding<'static> = Encoding::$e; } @@ -23,92 +150,269 @@ 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, ); -unsafe impl Encode for isize { - #[cfg(target_pointer_width = "16")] - const ENCODING: Encoding<'static> = i16::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; + } + )*); +} + +encode_impls_size!( + isize => (i16, i32, i64), + usize => (u16, u32, u64), +); - #[cfg(target_pointer_width = "32")] - const ENCODING: Encoding<'static> = i32::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> = i64::ENCODING; + unsafe impl Encode for Option { + const ENCODING: Encoding<'static> = <$type>::ENCODING; + } + )*); } -unsafe impl Encode for usize { - #[cfg(target_pointer_width = "16")] - const ENCODING: Encoding<'static> = u16::ENCODING; +encode_impls_nonzero!( + NonZeroI8 => i8, + NonZeroI16 => i16, + NonZeroI32 => i32, + NonZeroI64 => i64, + NonZeroIsize => isize, + NonZeroU8 => u8, + NonZeroU16 => u16, + NonZeroU32 => u32, + NonZeroU64 => u64, + NonZeroUsize => usize, +); - #[cfg(target_pointer_width = "32")] - const ENCODING: Encoding<'static> = u32::ENCODING; +// 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? - #[cfg(target_pointer_width = "64")] - const ENCODING: Encoding<'static> = u64::ENCODING; +/// [`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); } +/// [`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 *const c_void { - const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Void); +unsafe impl Encode for [T; LENGTH] { + const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING); } -unsafe impl Encode for [T; LENGTH] { - const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &::ENCODING); +unsafe impl RefEncode for [T; LENGTH] { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::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) +// 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`. +/// +/// 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<$t: RefEncode + ?Sized> $x for *mut $t { + const $c: Encoding<'static> = $e; + } + + unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a $t { + const $c: Encoding<'static> = $e; + } + + unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a mut $t { + 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; + } + + 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; + } + ); +} + +// 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. +encode_pointer_impls!( + unsafe impl Encode for &T { + const ENCODING = T::ENCODING_REF; + } +); + +// Implement `RefEncode` for pointers to types that are `RefEncode`. // -// Using `?Sized` is safe here because we delegate to other implementations -// (which will verify that the implementation is safe for the unsized type). +// 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); + } +); -unsafe impl Encode for *const T -where - for<'b> &'b T: Encode, -{ - const ENCODING: Encoding<'static> = <&T>::ENCODING; -} +/// 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 Encode for *mut T -where - for<'b> &'b mut T: Encode, -{ - const ENCODING: Encoding<'static> = <&mut T>::ENCODING; + 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, ); + }; } -unsafe impl<'a, T: ?Sized> Encode for Option<&'a T> -where - for<'b> &'b T: Encode, -{ - const ENCODING: Encoding<'static> = <&T>::ENCODING; +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>]; } -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; +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/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..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::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::Encode; -assert_eq!(i32::ENCODING.to_string(), "i"); -``` -*/ +//! # Objective-C type-encoding #![no_std] #![warn(missing_docs)] @@ -58,5 +16,5 @@ mod encode; mod encoding; mod parse; -pub use crate::encode::Encode; -pub use crate::encoding::Encoding; +pub use self::encode::{Encode, EncodeArguments, 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/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); } } 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; } ); }