diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c4900d7..399a51477 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - name: Install Packages if: contains(matrix.platform.os, 'ubuntu') - run: sudo apt-get install gobjc clang make + run: sudo apt-get install gobjc clang make libblocksruntime-dev - name: Install different Rust toolchain # A default toolchain is already installed @@ -120,12 +120,11 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - # TODO: `objc_foundation/block` feature doesn't work args: --verbose --no-fail-fast --no-default-features - - name: Test w. exception and verify_message features + - name: Test with 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 + # Not using --all-features because some features are nightly-only + args: --verbose --no-fail-fast --features block,exception,verify_message diff --git a/Cargo.toml b/Cargo.toml index 5fac7544e..75b073626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [workspace] members = [ "objc", + "objc_block", "objc_encode", "objc_exception", "objc_foundation", "objc_foundation_derive", "objc_id", + "objc_test_utils", ] exclude = ["objc/tests-ios"] diff --git a/objc/src/lib.rs b/objc/src/lib.rs index 1744d3c06..0f96c4a43 100644 --- a/objc/src/lib.rs +++ b/objc/src/lib.rs @@ -59,8 +59,10 @@ The bindings can be used on Linux or *BSD utilizing the */ #![no_std] -#![cfg_attr(feature = "unstable_autoreleasesafe", feature(negative_impls, auto_traits))] - +#![cfg_attr( + feature = "unstable_autoreleasesafe", + feature(negative_impls, auto_traits) +)] #![warn(missing_docs)] #![allow(clippy::missing_safety_doc)] // Update in Cargo.toml as well. diff --git a/objc/src/rc/autorelease.rs b/objc/src/rc/autorelease.rs index 661bb5b83..b3ed68ec4 100644 --- a/objc/src/rc/autorelease.rs +++ b/objc/src/rc/autorelease.rs @@ -1,6 +1,6 @@ use core::ffi::c_void; #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] -use std::{cell::RefCell, vec::Vec, thread_local}; +use std::{cell::RefCell, thread_local, vec::Vec}; use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush}; @@ -81,6 +81,7 @@ impl AutoreleasePool { all(debug_assertions, not(feature = "unstable_autoreleasesafe")), inline )] + #[allow(clippy::needless_lifetimes)] pub unsafe fn ptr_as_ref<'p, T>(&'p self, ptr: *const T) -> &'p T { #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] POOLS.with(|c| { @@ -110,6 +111,8 @@ impl AutoreleasePool { all(debug_assertions, not(feature = "unstable_autoreleasesafe")), inline )] + #[allow(clippy::needless_lifetimes)] + #[allow(clippy::mut_from_ref)] pub unsafe fn ptr_as_mut<'p, T>(&'p self, ptr: *mut T) -> &'p mut T { #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] POOLS.with(|c| { @@ -265,7 +268,10 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// inner pool: /// #[cfg_attr(feature = "unstable_autoreleasesafe", doc = "```rust,compile_fail")] -#[cfg_attr(not(feature = "unstable_autoreleasesafe"), doc = "```rust,should_panic")] +#[cfg_attr( + not(feature = "unstable_autoreleasesafe"), + doc = "```rust,should_panic" +)] /// # use objc::{class, msg_send}; /// # use objc::rc::{autoreleasepool, AutoreleasePool}; /// # use objc::runtime::Object; diff --git a/objc_block/Cargo.toml b/objc_block/Cargo.toml new file mode 100644 index 000000000..bf9056a55 --- /dev/null +++ b/objc_block/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "block" +version = "0.1.6" +authors = ["Steven Sheldon", "Mads Marquart "] +edition = "2018" + +description = "Interface for Apple's C language extension of blocks." +keywords = ["objective-c", "macos", "ios", "blocks"] +categories = [ + "api-bindings", + "development-tools::ffi", + "os::macos-apis", +] +readme = "README.md" +repository = "https://github.com/madsmtm/objc" +documentation = "https://docs.rs/block/" +license = "MIT" + +exclude = [ + "tests-ios/**", +] + +[dependencies] +objc-encode = { path = "../objc_encode", version = "1.1.0" } + +[dev-dependencies] +objc_test_utils = { path = "../objc_test_utils", version = "0.0" } diff --git a/objc_block/README.md b/objc_block/README.md new file mode 100644 index 000000000..303855279 --- /dev/null +++ b/objc_block/README.md @@ -0,0 +1,49 @@ +# `block` + +[![Latest version](https://badgen.net/crates/v/block)](https://crates.io/crates/block) +[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt) +[![Documentation](https://docs.rs/block/badge.svg)](https://docs.rs/block/) +[![CI Status](https://github.com/madsmtm/objc/workflows/CI/badge.svg)](https://github.com/madsmtm/objc/actions) + +Rust interface for Apple's C language extension of blocks. + +For more information on the specifics of the block implementation, see +Clang's documentation: http://clang.llvm.org/docs/Block-ABI-Apple.html + +## Invoking blocks + +The `Block` struct is used for invoking blocks from Objective-C. For example, +consider this Objective-C function: + +``` objc +int32_t sum(int32_t (^block)(int32_t, int32_t)) { + return block(5, 8); +} +``` + +We could write it in Rust as the following: + +``` rust +unsafe fn sum(block: &Block<(i32, i32), i32>) -> i32 { + block.call((5, 8)) +} +``` + +Note the extra parentheses in the `call` method, since the arguments must be +passed as a tuple. + +## Creating blocks + +Creating a block to pass to Objective-C can be done with the `ConcreteBlock` +struct. For example, to create a block that adds two `i32`s, we could write: + +``` rust +let block = ConcreteBlock::new(|a: i32, b: i32| a + b); +let block = block.copy(); +assert!(unsafe { block.call((5, 8)) } == 13); +``` + +It is important to copy your block to the heap (with the `copy` method) before +passing it to Objective-C; this is because our `ConcreteBlock` is only meant +to be copied once, and we can enforce this in Rust, but if Objective-C code +were to copy it twice we could have a double free. diff --git a/objc_block/src/lib.rs b/objc_block/src/lib.rs new file mode 100644 index 000000000..3a81222f2 --- /dev/null +++ b/objc_block/src/lib.rs @@ -0,0 +1,542 @@ +/*! +A Rust interface for Objective-C blocks. + +For more information on the specifics of the block implementation, see +Clang's documentation: + +# Invoking blocks + +The `Block` struct is used for invoking blocks from Objective-C. For example, +consider this Objective-C function: + +``` objc +int32_t sum(int32_t (^block)(int32_t, int32_t)) { + return block(5, 8); +} +``` + +We could write it in Rust as the following: + +``` +# use block::Block; +unsafe fn sum(block: &Block<(i32, i32), i32>) -> i32 { + block.call((5, 8)) +} +``` + +Note the extra parentheses in the `call` method, since the arguments must be +passed as a tuple. + +# Creating blocks + +Creating a block to pass to Objective-C can be done with the `ConcreteBlock` +struct. For example, to create a block that adds two `i32`s, we could write: + +``` +# use block::ConcreteBlock; +let block = ConcreteBlock::new(|a: i32, b: i32| a + b); +let block = block.copy(); +assert!(unsafe { block.call((5, 8)) } == 13); +``` + +It is important to copy your block to the heap (with the `copy` method) before +passing it to Objective-C; this is because our `ConcreteBlock` is only meant +to be copied once, and we can enforce this in Rust, but if Objective-C code +were to copy it twice we could have a double free. +*/ + +#![no_std] + +extern crate alloc; +extern crate std; + +#[cfg(test)] +mod test_utils; + +use alloc::boxed::Box; +use core::ffi::c_void; +use core::marker::PhantomData; +use core::mem; +use core::ops::{Deref, DerefMut}; +use core::ptr; +use std::os::raw::{c_int, c_ulong}; + +use objc_encode::{Encode, EncodeArguments, Encoding, RefEncode}; + +// TODO: Replace with `objc::Class` +#[repr(C)] +struct ClassInternal { + _priv: [u8; 0], +} + +#[cfg_attr( + any(target_os = "macos", target_os = "ios"), + link(name = "System", kind = "dylib") +)] +#[cfg_attr( + not(any(target_os = "macos", target_os = "ios")), + link(name = "BlocksRuntime", kind = "dylib") +)] +extern "C" { + static _NSConcreteStackBlock: ClassInternal; + + fn _Block_copy(block: *const c_void) -> *mut c_void; + fn _Block_release(block: *const c_void); +} + +/// Types that may be used as the arguments to an Objective-C block. +pub trait BlockArguments: Sized { + /// Calls the given `Block` with self as the arguments. + /// + /// # Safety + /// + /// The given block must point to a valid `Block`. + /// + /// This invokes foreign code whose safety the user must guarantee. + unsafe fn call_block(self, block: *mut Block) -> R; +} + +macro_rules! block_args_impl { + ($($a:ident : $t:ident),*) => ( + impl<$($t),*> BlockArguments for ($($t,)*) { + unsafe fn call_block(self, block: *mut Block) -> R { + let invoke: unsafe extern fn(*mut Block $(, $t)*) -> R = { + let base = block as *mut BlockBase; + mem::transmute((*base).invoke) + }; + let ($($a,)*) = self; + invoke(block $(, $a)*) + } + } + ); +} + +block_args_impl!(); +block_args_impl!(a: A); +block_args_impl!(a: A, b: B); +block_args_impl!(a: A, b: B, c: C); +block_args_impl!(a: A, b: B, c: C, d: D); +block_args_impl!(a: A, b: B, c: C, d: D, e: E); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); +block_args_impl!( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K +); +block_args_impl!( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L +); + +#[repr(C)] +struct BlockBase { + isa: *const ClassInternal, + flags: c_int, + _reserved: c_int, + invoke: unsafe extern "C" fn(*mut Block, ...) -> R, +} + +/// An Objective-C block that takes arguments of `A` when called and +/// returns a value of `R`. +#[repr(C)] +pub struct Block { + _base: PhantomData>, +} + +unsafe impl RefEncode for Block { + const ENCODING_REF: Encoding<'static> = Encoding::Block; +} + +impl Block { + /// Call self with the given arguments. + /// + /// # Safety + /// + /// This invokes foreign code that the caller must verify doesn't violate + /// any of Rust's safety rules. + /// + /// For example, if this block is shared with multiple references, the + /// caller must ensure that calling it will not cause a data race. + pub unsafe fn call(&self, args: A) -> R { + args.call_block(self as *const _ as *mut _) + } +} + +/// A reference-counted Objective-C block. +pub struct RcBlock { + ptr: *mut Block, +} + +impl RcBlock { + /// Construct an `RcBlock` for the given block without copying it. + /// The caller must ensure the block has a +1 reference count. + /// + /// # Safety + /// + /// The given pointer must point to a valid `Block` and must have a +1 + /// reference count or it will be overreleased when the `RcBlock` is + /// dropped. + pub unsafe fn new(ptr: *mut Block) -> Self { + RcBlock { ptr } + } + + /// Constructs an `RcBlock` by copying the given block. + /// + /// # Safety + /// + /// The given pointer must point to a valid `Block`. + pub unsafe fn copy(ptr: *mut Block) -> Self { + let ptr = _Block_copy(ptr as *const c_void) as *mut Block; + RcBlock { ptr } + } +} + +impl Clone for RcBlock { + fn clone(&self) -> RcBlock { + unsafe { RcBlock::copy(self.ptr) } + } +} + +impl Deref for RcBlock { + type Target = Block; + + fn deref(&self) -> &Block { + unsafe { &*self.ptr } + } +} + +impl Drop for RcBlock { + fn drop(&mut self) { + unsafe { + _Block_release(self.ptr as *const c_void); + } + } +} + +/// Types that may be converted into a `ConcreteBlock`. +pub trait IntoConcreteBlock: Sized { + /// The return type of the resulting `ConcreteBlock`. + type Ret: Encode; + + /// Consumes self to create a `ConcreteBlock`. + fn into_concrete_block(self) -> ConcreteBlock; +} + +macro_rules! concrete_block_impl { + ($f:ident) => ( + concrete_block_impl!($f,); + ); + ($f:ident, $($a:ident : $t:ident),*) => ( + impl<$($t: Encode,)* R: Encode, X> IntoConcreteBlock<($($t,)*)> for X + where X: Fn($($t,)*) -> R { + type Ret = R; + + fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> { + unsafe extern fn $f<$($t,)* R, X>( + block_ptr: *mut ConcreteBlock<($($t,)*), R, X> + $(, $a: $t)*) -> R + where X: Fn($($t,)*) -> R { + let block = &*block_ptr; + (block.closure)($($a),*) + } + + let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; + unsafe { + ConcreteBlock::with_invoke(mem::transmute(f), self) + } + } + } + ); +} + +concrete_block_impl!(concrete_block_invoke_args0); +concrete_block_impl!(concrete_block_invoke_args1, a: A); +concrete_block_impl!(concrete_block_invoke_args2, a: A, b: B); +concrete_block_impl!(concrete_block_invoke_args3, a: A, b: B, c: C); +concrete_block_impl!(concrete_block_invoke_args4, a: A, b: B, c: C, d: D); +concrete_block_impl!(concrete_block_invoke_args5, a: A, b: B, c: C, d: D, e: E); +concrete_block_impl!( + concrete_block_invoke_args6, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F +); +concrete_block_impl!( + concrete_block_invoke_args7, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G +); +concrete_block_impl!( + concrete_block_invoke_args8, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H +); +concrete_block_impl!( + concrete_block_invoke_args9, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I +); +concrete_block_impl!( + concrete_block_invoke_args10, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J +); +concrete_block_impl!( + concrete_block_invoke_args11, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K +); +concrete_block_impl!( + concrete_block_invoke_args12, + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L +); + +/// An Objective-C block whose size is known at compile time and may be +/// constructed on the stack. +#[repr(C)] +pub struct ConcreteBlock { + base: BlockBase, + descriptor: Box>>, + closure: F, +} + +unsafe impl RefEncode + for ConcreteBlock +{ + const ENCODING_REF: Encoding<'static> = Encoding::Block; +} + +impl ConcreteBlock +where + A: BlockArguments + EncodeArguments, + R: Encode, + F: IntoConcreteBlock, +{ + /// Constructs a `ConcreteBlock` with the given closure. + /// When the block is called, it will return the value that results from + /// calling the closure. + pub fn new(closure: F) -> Self { + closure.into_concrete_block() + } +} + +impl ConcreteBlock { + /// Constructs a `ConcreteBlock` with the given invoke function and closure. + /// Unsafe because the caller must ensure the invoke function takes the + /// correct arguments. + unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self { + ConcreteBlock { + base: BlockBase { + isa: &_NSConcreteStackBlock, + // 1 << 25 = BLOCK_HAS_COPY_DISPOSE + flags: 1 << 25, + _reserved: 0, + invoke: mem::transmute(invoke), + }, + descriptor: Box::new(BlockDescriptor::new()), + closure, + } + } +} + +impl ConcreteBlock +where + F: 'static, +{ + /// Copy self onto the heap as an `RcBlock`. + pub fn copy(self) -> RcBlock { + unsafe { + let mut block = self; + let copied = RcBlock::copy(&mut *block); + // At this point, our copy helper has been run so the block will + // be moved to the heap and we can forget the original block + // because the heap block will drop in our dispose helper. + mem::forget(block); + copied + } + } +} + +impl Clone for ConcreteBlock +where + F: Clone, +{ + fn clone(&self) -> Self { + unsafe { + ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), self.closure.clone()) + } + } +} + +impl Deref for ConcreteBlock { + type Target = Block; + + fn deref(&self) -> &Block { + unsafe { &*(&self.base as *const _ as *const Block) } + } +} + +impl DerefMut for ConcreteBlock { + fn deref_mut(&mut self) -> &mut Block { + unsafe { &mut *(&mut self.base as *mut _ as *mut Block) } + } +} + +unsafe extern "C" fn block_context_dispose(block: &mut B) { + // Read the block onto the stack and let it drop + ptr::read(block); +} + +unsafe extern "C" fn block_context_copy(_dst: &mut B, _src: &B) { + // The runtime memmoves the src block into the dst block, nothing to do +} + +#[repr(C)] +struct BlockDescriptor { + _reserved: c_ulong, + block_size: c_ulong, + copy_helper: unsafe extern "C" fn(&mut B, &B), + dispose_helper: unsafe extern "C" fn(&mut B), +} + +impl BlockDescriptor { + fn new() -> BlockDescriptor { + BlockDescriptor { + _reserved: 0, + block_size: mem::size_of::() as c_ulong, + copy_helper: block_context_copy::, + dispose_helper: block_context_dispose::, + } + } +} + +#[cfg(test)] +mod tests { + use super::{ConcreteBlock, RcBlock}; + use crate::test_utils::*; + use alloc::string::ToString; + + #[test] + fn test_call_block() { + let block = get_int_block_with(13); + unsafe { + assert!(block.call(()) == 13); + } + } + + #[test] + fn test_call_block_args() { + let block = get_add_block_with(13); + unsafe { + assert!(block.call((2,)) == 15); + } + } + + #[test] + fn test_create_block() { + let block = ConcreteBlock::new(|| 13); + let result = invoke_int_block(&block); + assert!(result == 13); + } + + #[test] + fn test_create_block_args() { + let block = ConcreteBlock::new(|a: i32| a + 5); + let result = invoke_add_block(&block, 6); + assert!(result == 11); + } + + #[test] + fn test_concrete_block_copy() { + let s = "Hello!".to_string(); + let expected_len = s.len() as i32; + let block = ConcreteBlock::new(move || s.len() as i32); + assert!(invoke_int_block(&block) == expected_len); + + let copied = block.copy(); + assert!(invoke_int_block(&copied) == expected_len); + } + + #[test] + fn test_concrete_block_stack_copy() { + fn make_block() -> RcBlock<(), i32> { + let x = 7; + let block = ConcreteBlock::new(move || x); + block.copy() + } + + let block = make_block(); + assert!(invoke_int_block(&block) == 7); + } +} diff --git a/objc_block/src/test_utils.rs b/objc_block/src/test_utils.rs new file mode 100644 index 000000000..71632bda5 --- /dev/null +++ b/objc_block/src/test_utils.rs @@ -0,0 +1,27 @@ +use objc_test_utils; + +use crate::{Block, RcBlock}; + +pub fn get_int_block_with(i: i32) -> RcBlock<(), i32> { + unsafe { + let ptr = objc_test_utils::get_int_block_with(i); + RcBlock::new(ptr as *mut _) + } +} + +pub fn get_add_block_with(i: i32) -> RcBlock<(i32,), i32> { + unsafe { + let ptr = objc_test_utils::get_add_block_with(i); + RcBlock::new(ptr as *mut _) + } +} + +pub fn invoke_int_block(block: &Block<(), i32>) -> i32 { + let ptr = block as *const _; + unsafe { objc_test_utils::invoke_int_block(ptr as *mut _) } +} + +pub fn invoke_add_block(block: &Block<(i32,), i32>, a: i32) -> i32 { + let ptr = block as *const _; + unsafe { objc_test_utils::invoke_add_block(ptr as *mut _, a) } +} diff --git a/objc_block/tests-ios/prelude.rs b/objc_block/tests-ios/prelude.rs new file mode 100644 index 000000000..1c5eea202 --- /dev/null +++ b/objc_block/tests-ios/prelude.rs @@ -0,0 +1,7 @@ +extern crate block; + +#[path = "../src/test_utils.rs"] +mod test_utils; + +pub use block::*; +use test_utils::*; diff --git a/objc_encode/Cargo.toml b/objc_encode/Cargo.toml index d4673c045..40de8a601 100644 --- a/objc_encode/Cargo.toml +++ b/objc_encode/Cargo.toml @@ -17,6 +17,3 @@ readme = "README.md" repository = "https://github.com/madsmtm/objc" documentation = "https://docs.rs/objc-encode/" license = "MIT" - -[dev-dependencies] -objc = { path = "../objc", version = "0.2.7" } diff --git a/objc_encode/README.md b/objc_encode/README.md index 72287d5d8..3f08c9900 100644 --- a/objc_encode/README.md +++ b/objc_encode/README.md @@ -51,7 +51,7 @@ An `Encoding` can be compared with an encoding string from the Objective-C runtime: ```rust -use objc::Encode; +use objc_encode::Encode; assert!(&i32::ENCODING == "i"); ``` @@ -59,7 +59,7 @@ assert!(&i32::ENCODING == "i"); generated conveniently through the `to_string` method: ```rust -use objc::Encode; +use objc_encode::Encode; assert_eq!(i32::ENCODING.to_string(), "i"); ``` diff --git a/objc_encode/examples/core_graphics.rs b/objc_encode/examples/core_graphics.rs index 611579e73..ed2f7c85a 100644 --- a/objc_encode/examples/core_graphics.rs +++ b/objc_encode/examples/core_graphics.rs @@ -1,4 +1,4 @@ -use objc::{Encode, Encoding}; +use objc_encode::{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 afc890424..a1c250f0f 100644 --- a/objc_encode/examples/ns_string.rs +++ b/objc_encode/examples/ns_string.rs @@ -1,4 +1,4 @@ -use objc::{Encode, Encoding, RefEncode}; +use objc_encode::{Encode, Encoding, RefEncode}; /// We don't know the size of NSString, so we can only hold pointers to it. /// diff --git a/objc_encode/examples/ns_uinteger.rs b/objc_encode/examples/ns_uinteger.rs index 0473deecf..929d18517 100644 --- a/objc_encode/examples/ns_uinteger.rs +++ b/objc_encode/examples/ns_uinteger.rs @@ -2,7 +2,7 @@ //! //! Note that in this case `NSUInteger` could actually just be a type alias //! for `usize`. -use objc::{Encode, Encoding, RefEncode}; +use objc_encode::{Encode, Encoding, RefEncode}; #[repr(transparent)] struct NSUInteger { diff --git a/objc_encode/examples/opaque_type.rs b/objc_encode/examples/opaque_type.rs index 72df41460..48d9ad44b 100644 --- a/objc_encode/examples/opaque_type.rs +++ b/objc_encode/examples/opaque_type.rs @@ -1,5 +1,5 @@ //! Implementing `RefEncode` for `NSDecimal`. -use objc::{Encoding, RefEncode}; +use objc_encode::{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 diff --git a/objc_foundation/Cargo.toml b/objc_foundation/Cargo.toml index 9b56aadda..8fcf11094 100644 --- a/objc_foundation/Cargo.toml +++ b/objc_foundation/Cargo.toml @@ -20,6 +20,7 @@ license = "MIT" default = ["block"] [dependencies] -block = { optional = true, version = "0.1" } +# Feature provided as a way to cut down on dependencies +block = { path = "../objc_block", optional = true, version = "0.1" } objc = { path = "../objc", version = "0.2.7" } -objc_id = { path = "../objc_id", version = "0.1" } +objc_id = { path = "../objc_id", version = "0.1.1" } diff --git a/objc_foundation_derive/src/lib.rs b/objc_foundation_derive/src/lib.rs index 2e4c72ae4..dc1a356ec 100644 --- a/objc_foundation_derive/src/lib.rs +++ b/objc_foundation_derive/src/lib.rs @@ -5,10 +5,9 @@ #[doc = include_str!("../README.md")] extern "C" {} -extern crate proc_macro; #[macro_use] extern crate quote; -extern crate syn; +use syn; use proc_macro::TokenStream; use quote::{ToTokens, Tokens}; diff --git a/objc_test_utils/Cargo.toml b/objc_test_utils/Cargo.toml new file mode 100644 index 000000000..7b906a7a4 --- /dev/null +++ b/objc_test_utils/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "objc_test_utils" +version = "0.0.2" +authors = ["Steven Sheldon", "Mads Marquart "] +edition = "2018" + +description = "Utilities for testing Objective-C interop." +keywords = ["objective-c", "macos", "ios", "testing"] +categories = [ + "development-tools::ffi", + "no-std", + "development-tools::testing", + "os::macos-apis", +] +repository = "https://github.com/madsmtm/objc" +documentation = "https://docs.rs/objc_test_utils/" +license = "MIT" + +build = "build.rs" + +[build-dependencies] +cc = "1.0" diff --git a/objc_test_utils/build.rs b/objc_test_utils/build.rs new file mode 100644 index 000000000..1da9e08c1 --- /dev/null +++ b/objc_test_utils/build.rs @@ -0,0 +1,9 @@ +use cc; + +fn main() { + cc::Build::new() + .compiler("clang") + .file("extern/block_utils.c") + .flag("-fblocks") + .compile("libblock_utils.a"); +} diff --git a/objc_test_utils/extern/block_utils.c b/objc_test_utils/extern/block_utils.c new file mode 100644 index 000000000..440dbb948 --- /dev/null +++ b/objc_test_utils/extern/block_utils.c @@ -0,0 +1,29 @@ +#include +#include + +typedef int32_t (^IntBlock)(); +typedef int32_t (^AddBlock)(int32_t); + +IntBlock get_int_block() { + return ^{ return (int32_t)7; }; +} + +IntBlock get_int_block_with(int32_t i) { + return Block_copy(^{ return i; }); +} + +AddBlock get_add_block() { + return ^(int32_t a) { return a + 7; }; +} + +AddBlock get_add_block_with(int32_t i) { + return Block_copy(^(int32_t a) { return a + i; }); +} + +int32_t invoke_int_block(IntBlock block) { + return block(); +} + +int32_t invoke_add_block(AddBlock block, int32_t a) { + return block(a); +} diff --git a/objc_test_utils/src/lib.rs b/objc_test_utils/src/lib.rs new file mode 100644 index 000000000..d1b9efa8a --- /dev/null +++ b/objc_test_utils/src/lib.rs @@ -0,0 +1,50 @@ +#![no_std] + +/// A block that takes no arguments and returns an integer: `int32_t (^)()`. +#[repr(C)] +pub struct IntBlock { + _priv: [u8; 0], +} + +/// A block that takes one integer argument, adds to it, and returns the sum: +/// `int32_t (^)(int32_t)`. +#[repr(C)] +pub struct AddBlock { + _priv: [u8; 0], +} + +extern "C" { + /// Returns a pointer to a global `IntBlock` that returns 7. + pub fn get_int_block() -> *mut IntBlock; + /// Returns a pointer to a copied `IntBlock` that returns `i`. + pub fn get_int_block_with(i: i32) -> *mut IntBlock; + /// Returns a pointer to a global `AddBlock` that returns its argument + 7. + pub fn get_add_block() -> *mut AddBlock; + /// Returns a pointer to a copied `AddBlock` that returns its argument + `i`. + pub fn get_add_block_with(i: i32) -> *mut AddBlock; + /// Invokes an `IntBlock` and returns its result. + pub fn invoke_int_block(block: *mut IntBlock) -> i32; + /// Invokes an `AddBlock` with `a` and returns the result. + pub fn invoke_add_block(block: *mut AddBlock, a: i32) -> i32; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_int_block() { + unsafe { + assert!(invoke_int_block(get_int_block()) == 7); + assert!(invoke_int_block(get_int_block_with(13)) == 13); + } + } + + #[test] + fn test_add_block() { + unsafe { + assert!(invoke_add_block(get_add_block(), 5) == 12); + assert!(invoke_add_block(get_add_block_with(3), 5) == 8); + } + } +}