From 27e3e29088dc6ef93f2d129a8f0476fc7ff1f3a9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 6 Jan 2024 11:18:47 +0100 Subject: [PATCH 1/4] Restrict exposed symbols in `block2::ffi` module --- crates/block2/CHANGELOG.md | 6 +++++ crates/block2/src/abi.rs | 1 + crates/block2/src/block.rs | 6 ++--- crates/block2/src/concrete_block.rs | 16 +++++++------- crates/block2/src/debug.rs | 34 ++++++++++++++--------------- crates/block2/src/ffi.rs | 6 +++++ crates/block2/src/global.rs | 24 ++++++++++---------- crates/block2/src/lib.rs | 6 +++-- crates/tests/src/ffi.rs | 9 ++++---- 9 files changed, 62 insertions(+), 46 deletions(-) create mode 100644 crates/block2/src/abi.rs create mode 100644 crates/block2/src/ffi.rs diff --git a/crates/block2/CHANGELOG.md b/crates/block2/CHANGELOG.md index 379095fd6..9e24815d4 100644 --- a/crates/block2/CHANGELOG.md +++ b/crates/block2/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Changed +* **BREAKING**: Only expose the actually public symbols `_Block_copy`, + `_Block_release`, `_Block_object_assign`, `_Block_object_dispose`, + `_NSConcreteGlobalBlock`, `_NSConcreteStackBlock` and `Class` in `ffi` + module. + ## 0.4.0 - 2023-12-03 diff --git a/crates/block2/src/abi.rs b/crates/block2/src/abi.rs new file mode 100644 index 000000000..159d0e94e --- /dev/null +++ b/crates/block2/src/abi.rs @@ -0,0 +1 @@ +pub(crate) use block_sys::*; diff --git a/crates/block2/src/block.rs b/crates/block2/src/block.rs index 00c38cdcf..075f55d22 100644 --- a/crates/block2/src/block.rs +++ b/crates/block2/src/block.rs @@ -3,7 +3,7 @@ use core::mem; use objc2::encode::{EncodeArgument, EncodeReturn, Encoding, RefEncode}; -use crate::ffi; +use crate::abi; /// Types that may be used as the arguments of an Objective-C block. /// @@ -92,7 +92,7 @@ pub struct Block { // We store `Block_layout` + a bit more, but `Block` has to remain an // empty type otherwise the compiler thinks we only have provenance over // `Block_layout`. - _layout: PhantomData, + _layout: PhantomData, // To get correct variance on args and return types _p: PhantomData R>, } @@ -113,7 +113,7 @@ impl Block { /// caller must ensure that calling it will not cause a data race. pub unsafe fn call(&self, args: A) -> R { let ptr: *const Self = self; - let layout = unsafe { ptr.cast::().as_ref().unwrap_unchecked() }; + let layout = unsafe { ptr.cast::().as_ref().unwrap_unchecked() }; // TODO: Is `invoke` actually ever null? let invoke = layout.invoke.unwrap_or_else(|| unreachable!()); diff --git a/crates/block2/src/concrete_block.rs b/crates/block2/src/concrete_block.rs index 9c9542bf9..d2884e342 100644 --- a/crates/block2/src/concrete_block.rs +++ b/crates/block2/src/concrete_block.rs @@ -7,7 +7,7 @@ use std::os::raw::c_ulong; use objc2::encode::{EncodeArgument, EncodeReturn, Encoding, RefEncode}; -use crate::{ffi, Block, BlockArguments, RcBlock}; +use crate::{abi, ffi, Block, BlockArguments, RcBlock}; mod private { pub trait Sealed {} @@ -163,7 +163,7 @@ concrete_block_impl!( #[repr(C)] pub struct ConcreteBlock { p: PhantomData>, - pub(crate) layout: ffi::Block_layout, + pub(crate) layout: abi::Block_layout, pub(crate) closure: F, } @@ -187,14 +187,14 @@ where impl ConcreteBlock { // TODO: Use new ABI with BLOCK_HAS_SIGNATURE - const FLAGS: ffi::block_flags = if mem::needs_drop::() { - ffi::BLOCK_HAS_COPY_DISPOSE + const FLAGS: abi::block_flags = if mem::needs_drop::() { + abi::BLOCK_HAS_COPY_DISPOSE } else { 0 }; - const DESCRIPTOR: ffi::Block_descriptor = ffi::Block_descriptor { - header: ffi::Block_descriptor_header { + const DESCRIPTOR: abi::Block_descriptor = abi::Block_descriptor { + header: abi::Block_descriptor_header { reserved: 0, size: mem::size_of::() as c_ulong, }, @@ -214,12 +214,12 @@ impl ConcreteBlock { /// Unsafe because the caller must ensure the invoke function takes the /// correct arguments. unsafe fn with_invoke(invoke: unsafe extern "C" fn(), closure: F) -> Self { - let layout = ffi::Block_layout { + let layout = abi::Block_layout { isa: unsafe { &ffi::_NSConcreteStackBlock }, flags: Self::FLAGS, reserved: 0, invoke: Some(invoke), - descriptor: &Self::DESCRIPTOR as *const ffi::Block_descriptor as *mut c_void, + descriptor: &Self::DESCRIPTOR as *const abi::Block_descriptor as *mut c_void, }; Self { p: PhantomData, diff --git a/crates/block2/src/debug.rs b/crates/block2/src/debug.rs index 2878ba3d6..477edee40 100644 --- a/crates/block2/src/debug.rs +++ b/crates/block2/src/debug.rs @@ -4,10 +4,10 @@ use core::fmt::{Debug, DebugStruct, Error, Formatter}; use core::ptr; use std::ffi::CStr; -use crate::{ffi, Block, ConcreteBlock, GlobalBlock, RcBlock}; +use crate::{abi, ffi, Block, ConcreteBlock, GlobalBlock, RcBlock}; #[derive(Clone, Copy, PartialEq, Eq)] -struct Isa(*const ffi::Class); +struct Isa(*const abi::Class); impl Isa { fn is_global(self) -> bool { @@ -19,7 +19,7 @@ impl Isa { } fn is_malloc(self) -> bool { - ptr::eq(unsafe { &ffi::_NSConcreteMallocBlock }, self.0) + ptr::eq(unsafe { &abi::_NSConcreteMallocBlock }, self.0) } } @@ -37,7 +37,7 @@ impl Debug for Isa { } } -fn debug_block_layout(layout: &ffi::Block_layout, f: &mut DebugStruct<'_, '_>) { +fn debug_block_layout(layout: &abi::Block_layout, f: &mut DebugStruct<'_, '_>) { f.field("isa", &Isa(layout.isa)); f.field("flags", &BlockFlags(layout.flags)); f.field("reserved", &layout.reserved); @@ -45,8 +45,8 @@ fn debug_block_layout(layout: &ffi::Block_layout, f: &mut DebugStruct<'_, '_>) { f.field( "descriptor", &BlockDescriptor { - has_copy_dispose: layout.flags & ffi::BLOCK_HAS_COPY_DISPOSE != 0, - has_signature: layout.flags & ffi::BLOCK_HAS_SIGNATURE != 0, + has_copy_dispose: layout.flags & abi::BLOCK_HAS_COPY_DISPOSE != 0, + has_signature: layout.flags & abi::BLOCK_HAS_SIGNATURE != 0, descriptor: layout.descriptor, }, ); @@ -56,7 +56,7 @@ impl Debug for Block { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { let mut f = f.debug_struct("Block"); let ptr: *const Self = self; - let layout = unsafe { ptr.cast::().as_ref().unwrap() }; + let layout = unsafe { ptr.cast::().as_ref().unwrap() }; debug_block_layout(layout, &mut f); f.finish_non_exhaustive() } @@ -65,7 +65,7 @@ impl Debug for Block { impl Debug for RcBlock { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { let mut f = f.debug_struct("RcBlock"); - let layout = unsafe { self.ptr.cast::().as_ref().unwrap() }; + let layout = unsafe { self.ptr.cast::().as_ref().unwrap() }; debug_block_layout(layout, &mut f); f.finish_non_exhaustive() } @@ -89,7 +89,7 @@ impl Debug for GlobalBlock { } #[derive(Clone, Copy, PartialEq, Eq)] -struct BlockFlags(ffi::block_flags); +struct BlockFlags(abi::block_flags); impl Debug for BlockFlags { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { @@ -102,7 +102,7 @@ impl Debug for BlockFlags { $name:ident: $flag:ident );* $(;)?} => ($( $(#[$m])? - f.field(stringify!($name), &(self.0 & ffi::$flag != 0)); + f.field(stringify!($name), &(self.0 & abi::$flag != 0)); )*) } test_flags! { @@ -129,11 +129,11 @@ impl Debug for BlockFlags { f.field( "over_referenced", - &(self.0 & ffi::BLOCK_REFCOUNT_MASK == ffi::BLOCK_REFCOUNT_MASK), + &(self.0 & abi::BLOCK_REFCOUNT_MASK == abi::BLOCK_REFCOUNT_MASK), ); f.field( "reference_count", - &((self.0 & ffi::BLOCK_REFCOUNT_MASK) >> 1), + &((self.0 & abi::BLOCK_REFCOUNT_MASK) >> 1), ); f.finish_non_exhaustive() } @@ -156,7 +156,7 @@ impl Debug for BlockDescriptor { let header = unsafe { self.descriptor - .cast::() + .cast::() .as_ref() .unwrap() }; @@ -169,7 +169,7 @@ impl Debug for BlockDescriptor { (true, false) => { let descriptor = unsafe { self.descriptor - .cast::() + .cast::() .as_ref() .unwrap() }; @@ -179,7 +179,7 @@ impl Debug for BlockDescriptor { (false, true) => { let descriptor = unsafe { self.descriptor - .cast::() + .cast::() .as_ref() .unwrap() }; @@ -195,7 +195,7 @@ impl Debug for BlockDescriptor { (true, true) => { let descriptor = unsafe { self.descriptor - .cast::() + .cast::() .as_ref() .unwrap() }; @@ -230,7 +230,7 @@ mod tests { assert!(!isa.is_global()); assert!(isa.is_stack()); assert!(!isa.is_malloc()); - let isa = Isa(unsafe { &ffi::_NSConcreteMallocBlock }); + let isa = Isa(unsafe { &abi::_NSConcreteMallocBlock }); assert!(!isa.is_global()); assert!(!isa.is_stack()); assert!(isa.is_malloc()); diff --git a/crates/block2/src/ffi.rs b/crates/block2/src/ffi.rs new file mode 100644 index 000000000..a338aac1e --- /dev/null +++ b/crates/block2/src/ffi.rs @@ -0,0 +1,6 @@ +//! `Block.h` + +pub use block_sys::{ + Class, _Block_copy, _Block_object_assign, _Block_object_dispose, _Block_release, + _NSConcreteGlobalBlock, _NSConcreteStackBlock, +}; diff --git a/crates/block2/src/global.rs b/crates/block2/src/global.rs index f016301b1..47c409b24 100644 --- a/crates/block2/src/global.rs +++ b/crates/block2/src/global.rs @@ -7,13 +7,13 @@ use std::os::raw::c_ulong; use objc2::encode::EncodeReturn; -use super::{ffi, Block}; -use crate::BlockArguments; +use super::Block; +use crate::{abi, BlockArguments}; // TODO: Should this be a static to help the compiler deduplicating them? -const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_header { +const GLOBAL_DESCRIPTOR: abi::Block_descriptor_header = abi::Block_descriptor_header { reserved: 0, - size: mem::size_of::() as c_ulong, + size: mem::size_of::() as c_ulong, }; /// An Objective-C block that does not capture its environment. @@ -28,7 +28,7 @@ const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_he /// [`global_block!`]: crate::global_block #[repr(C)] pub struct GlobalBlock { - pub(crate) layout: ffi::Block_layout, + pub(crate) layout: abi::Block_layout, p: PhantomData<(A, R)>, } @@ -52,22 +52,22 @@ where // triggers an error. impl GlobalBlock { // TODO: Use new ABI with BLOCK_HAS_SIGNATURE - const FLAGS: ffi::block_flags = ffi::BLOCK_IS_GLOBAL | ffi::BLOCK_USE_STRET; + const FLAGS: abi::block_flags = abi::BLOCK_IS_GLOBAL | abi::BLOCK_USE_STRET; #[doc(hidden)] - pub const __DEFAULT_LAYOUT: ffi::Block_layout = ffi::Block_layout { + pub const __DEFAULT_LAYOUT: abi::Block_layout = abi::Block_layout { // Populated in `global_block!` isa: ptr::null_mut(), flags: Self::FLAGS, reserved: 0, // Populated in `global_block!` invoke: None, - descriptor: &GLOBAL_DESCRIPTOR as *const ffi::Block_descriptor_header as *mut c_void, + descriptor: &GLOBAL_DESCRIPTOR as *const abi::Block_descriptor_header as *mut c_void, }; /// Use the [`global_block`] macro instead. #[doc(hidden)] - pub const unsafe fn from_layout(layout: ffi::Block_layout) -> Self { + pub const unsafe fn from_layout(layout: abi::Block_layout) -> Self { Self { layout, p: PhantomData, @@ -173,10 +173,10 @@ macro_rules! global_block { let mut layout = $crate::GlobalBlock::<($($t,)*) $(, $r)?>::__DEFAULT_LAYOUT; layout.isa = &$crate::ffi::_NSConcreteGlobalBlock; layout.invoke = ::core::option::Option::Some({ - unsafe extern "C" fn inner(_: *mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? { + unsafe extern "C" fn inner(_: *mut $crate::__Block_layout, $($a: $t),*) $(-> $r)? { $body } - let inner: unsafe extern "C" fn(*mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? = inner; + let inner: unsafe extern "C" fn(*mut $crate::__Block_layout, $($a: $t),*) $(-> $r)? = inner; // TODO: SAFETY ::core::mem::transmute(inner) @@ -253,7 +253,7 @@ mod tests { #[test] fn test_debug() { let invoke = NOOP_BLOCK.layout.invoke.unwrap(); - let size = mem::size_of::(); + let size = mem::size_of::(); let expected = format!( "GlobalBlock {{ isa: _NSConcreteGlobalBlock, diff --git a/crates/block2/src/lib.rs b/crates/block2/src/lib.rs index 1bb7edd03..a313eee7e 100644 --- a/crates/block2/src/lib.rs +++ b/crates/block2/src/lib.rs @@ -99,15 +99,17 @@ compile_error!("The `std` feature currently must be enabled."); #[doc = include_str!("../README.md")] extern "C" {} -pub use block_sys as ffi; - +mod abi; mod block; mod concrete_block; mod debug; +pub mod ffi; mod global; mod rc_block; pub use block::{Block, BlockArguments}; +#[doc(hidden)] +pub use block_sys::Block_layout as __Block_layout; pub use concrete_block::{ConcreteBlock, IntoConcreteBlock}; pub use global::GlobalBlock; pub use rc_block::RcBlock; diff --git a/crates/tests/src/ffi.rs b/crates/tests/src/ffi.rs index 5b01dcca9..517aa6806 100644 --- a/crates/tests/src/ffi.rs +++ b/crates/tests/src/ffi.rs @@ -1,4 +1,6 @@ -use block2::{ffi, Block}; +use std::os::raw::c_void; + +use block2::Block; use objc2::{Encode, Encoding}; /// A block that takes no arguments and returns an integer: `int32_t (^)()`. @@ -67,9 +69,8 @@ extern "C" { } #[no_mangle] -extern "C" fn debug_block(layout: &ffi::Block_layout) { - let block: &Block<(), ()> = - unsafe { &*(layout as *const ffi::Block_layout as *const Block<(), ()>) }; +extern "C" fn debug_block(layout: *mut c_void) { + let block: &Block<(), ()> = unsafe { &*(layout as *const Block<(), ()>) }; std::println!("{block:#?}"); } From d90f06271a0c44491b09788cdceb0df181dec6cc Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 6 Jan 2024 09:44:11 +0100 Subject: [PATCH 2/4] Don't use `_NSConcreteMallocBlock` in public implementation This is considered a private implementation detail, and is probably not safe to rely on. --- crates/block2/src/debug.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/block2/src/debug.rs b/crates/block2/src/debug.rs index 477edee40..c1b9d486a 100644 --- a/crates/block2/src/debug.rs +++ b/crates/block2/src/debug.rs @@ -17,10 +17,6 @@ impl Isa { fn is_stack(self) -> bool { ptr::eq(unsafe { &ffi::_NSConcreteStackBlock }, self.0) } - - fn is_malloc(self) -> bool { - ptr::eq(unsafe { &abi::_NSConcreteMallocBlock }, self.0) - } } impl Debug for Isa { @@ -29,10 +25,8 @@ impl Debug for Isa { f.write_str("_NSConcreteGlobalBlock") } else if self.is_stack() { f.write_str("_NSConcreteStackBlock") - } else if self.is_malloc() { - f.write_str("_NSConcreteMallocBlock") } else { - write!(f, "{:?}", self.0) + write!(f, "{:?} (likely _NSConcreteMallocBlock)", self.0) } } } @@ -225,18 +219,14 @@ mod tests { let isa = Isa(unsafe { &ffi::_NSConcreteGlobalBlock }); assert!(isa.is_global()); assert!(!isa.is_stack()); - assert!(!isa.is_malloc()); let isa = Isa(unsafe { &ffi::_NSConcreteStackBlock }); assert!(!isa.is_global()); assert!(isa.is_stack()); - assert!(!isa.is_malloc()); let isa = Isa(unsafe { &abi::_NSConcreteMallocBlock }); assert!(!isa.is_global()); assert!(!isa.is_stack()); - assert!(isa.is_malloc()); let isa = Isa(ptr::null()); assert!(!isa.is_global()); assert!(!isa.is_stack()); - assert!(!isa.is_malloc()); } } From 40af4286bfc1261c29c8d0fe19107f0a05a254f2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 7 Jan 2024 08:54:56 +0100 Subject: [PATCH 3/4] Merge block-sys into block2 `block-sys` doesn't actually build a library, it only links to it, so there is no real need for a build script, nor for the `links` key. Additionally, it should actually only expose the six public symbols, and in that case, it just seems wasteful to have multiple crates. This _does_ remove: 1. The ability to override the linking. This was brittle already, and is probably better handled by dedicated tools / by modifying the binaries after they're compiled / by inserting extra linker commands. 2. The `DEP_BLOCK_0_2_CC_ARGS` environment variable exposed to downstream build scripts. This was not really that useful, usually you want to know exactly what flags you're passing to your compiler, and the standard `-fblocks` flag is enough. --- .github/workflows/ci.yml | 7 +- CONTRIBUTING.md | 1 - Cargo.lock | 9 - crates/block-sys/CHANGELOG.md | 70 --- crates/block-sys/Cargo.toml | 84 ---- crates/block-sys/README.md | 119 ----- crates/block-sys/build.rs | 76 --- crates/block-sys/src/lib.rs | 455 ------------------ crates/block2/CHANGELOG.md | 8 +- crates/block2/Cargo.toml | 46 +- crates/block2/src/abi.rs | 313 +++++++++++- crates/block2/src/debug.rs | 4 +- crates/block2/src/ffi.rs | 171 ++++++- crates/block2/src/lib.rs | 147 +++++- crates/tests/Cargo.toml | 1 - crates/tests/build.rs | 15 +- .../compat-headers/gnustep-pre-2-0}/Block.h | 0 .../gnustep-pre-2-0}/Block_private.h | 0 .../compat-headers/objfw/Block.h | 0 19 files changed, 670 insertions(+), 856 deletions(-) delete mode 100644 crates/block-sys/CHANGELOG.md delete mode 100644 crates/block-sys/Cargo.toml delete mode 100644 crates/block-sys/README.md delete mode 100644 crates/block-sys/build.rs delete mode 100644 crates/block-sys/src/lib.rs rename crates/{block-sys/compat-headers/gnustep => tests/compat-headers/gnustep-pre-2-0}/Block.h (100%) rename crates/{block-sys/compat-headers/gnustep => tests/compat-headers/gnustep-pre-2-0}/Block_private.h (100%) rename crates/{block-sys => tests}/compat-headers/objfw/Block.h (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1834db060..89c751f79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,6 @@ env: # # This excludes `header-translator`, `test-assembly`, `tests` and `test-ui`. PUBLIC_CRATES: >- - --package=block-sys --package=block2 --package=icrate --package=objc-sys @@ -780,10 +779,8 @@ jobs: - lint env: - # `compiler-rt` is only relevant for these crates - PUBLIC_CRATES: >- - --package=block-sys - --package=block2 + # `compiler-rt` is only relevant for block2 + PUBLIC_CRATES: --package=block2 steps: - uses: actions/checkout@v3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b93c27a97..c5e862bf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,6 @@ Copy and fill out the following checklist into the release PR: - `objc2-proc-macros` - `objc-sys` - `objc2-encode` - - `block-sys` - `objc2` - `block2` - `icrate` diff --git a/Cargo.lock b/Cargo.lock index 2177547c1..d87da7f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,18 +49,10 @@ dependencies = [ "serde", ] -[[package]] -name = "block-sys" -version = "0.2.0" -dependencies = [ - "objc-sys", -] - [[package]] name = "block2" version = "0.4.0" dependencies = [ - "block-sys", "objc2", ] @@ -636,7 +628,6 @@ dependencies = [ name = "tests" version = "0.1.0" dependencies = [ - "block-sys", "block2", "cc", "icrate", diff --git a/crates/block-sys/CHANGELOG.md b/crates/block-sys/CHANGELOG.md deleted file mode 100644 index f992b14d9..000000000 --- a/crates/block-sys/CHANGELOG.md +++ /dev/null @@ -1,70 +0,0 @@ -# Changelog - -Notable changes to this crate will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - -## Unreleased - YYYY-MM-DD - - -## 0.2.0 - 2023-02-07 - -### Changed -* Updated `objc-sys` to `v0.3.0`. -* **BREAKING**: Changed `links` key from `block_0_1` to `block_0_2` (so - `DEP_BLOCK_0_1_CC_ARGS` in build scripts becomes `DEP_BLOCK_0_2_CC_ARGS`). - - -## 0.1.0-beta.2 - 2022-12-24 - -### Changed -* Updated `objc-sys` to `v0.2.0-beta.3`. - - -## 0.1.0-beta.1 - 2022-08-28 - -### Fixed -* Fixed `docs.rs` setup. - - -## 0.1.0-beta.0 - 2022-07-19 - -### Added -* Use `doc_auto_cfg` to improve documentation output. - -### Changed -* **BREAKING**: Changed `links` key from `block_0_0` to `block_0_1` (so - `DEP_BLOCK_0_0_CC_ARGS` in build scripts becomes `DEP_BLOCK_0_1_CC_ARGS`). - - -## 0.0.4 - 2022-06-13 - -### Changed -* **BREAKING**: Changed `links` key from `block` to `block_0_0` for better - future compatibility, until we reach 1.0 (so `DEP_BLOCK_CC_ARGS` in build - scripts becomes `DEP_BLOCK_0_0_CC_ARGS`). -* **BREAKING**: Apple's runtime is now always the default. -* **BREAKING**: Updated `objc-sys` to `v0.2.0-beta.0`. - -### Fixed -* **BREAKING**: Tweak the types of a lot of fields and arguments. - - -## 0.0.3 - 2022-01-03 - -### Changed -* **BREAKING**: Updated `objc-sys` to `v0.2.0-alpha.1`. - - -## 0.0.2 - 2021-12-22 - -### Changed -* **BREAKING**: Updated `objc-sys` to `v0.2.0-alpha.0`. - -### Fixed -* **BREAKING**: `Class` is now `!UnwindSafe`. - - -## 0.0.1 - 2021-11-22 - -Initial release. diff --git a/crates/block-sys/Cargo.toml b/crates/block-sys/Cargo.toml deleted file mode 100644 index 4fc64b4fd..000000000 --- a/crates/block-sys/Cargo.toml +++ /dev/null @@ -1,84 +0,0 @@ -[package] -name = "block-sys" -# Remember to update `html_root_url` in lib.rs and the `links` key. -# -# Also, beware of using pre-release versions here, since because of the -# `links` key, two pre-releases requested with `=...` are incompatible. -version = "0.2.0" -authors = ["Mads Marquart "] -edition = "2021" -rust-version = "1.60" - -description = "Raw bindings to Apple's C language extension of blocks" -keywords = ["objective-c", "macos", "ios", "blocks", "sys"] -categories = [ - "external-ffi-bindings", - # "no_std", # TODO - "os::macos-apis", -] -repository = "https://github.com/madsmtm/objc2" -documentation = "https://docs.rs/block-sys/" -license = "MIT" - -readme = "README.md" - -# Downstream users can customize the linking! -# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts -links = "block_0_2" -build = "build.rs" - -[features] -# The default runtime is Apple's. Other platforms will probably error if the -# correct feature flag is not specified. -default = ["std", "apple"] - -# Currently not possible to turn off, put here for forwards compatibility. -std = ["alloc", "objc-sys?/std"] -alloc = ["objc-sys?/alloc"] - -# Link to Apple's libclosure (exists in libSystem) -apple = [] - -# Link to libBlocksRuntime from compiler-rt -compiler-rt = [] - -# Link to GNUStep's libobjc2 (which contains the block implementation) -gnustep-1-7 = ["objc-sys", "objc-sys/gnustep-1-7"] -gnustep-1-8 = ["objc-sys/gnustep-1-8", "gnustep-1-7"] -gnustep-1-9 = ["objc-sys/gnustep-1-9", "gnustep-1-8"] -gnustep-2-0 = ["objc-sys/gnustep-2-0", "gnustep-1-9"] -gnustep-2-1 = ["objc-sys/gnustep-2-1", "gnustep-2-0"] - -# Link to Microsoft's libobjc2 -unstable-winobjc = ["objc-sys/unstable-winobjc", "gnustep-1-8"] - -# Link to ObjFW -unstable-objfw = [] - -# Private -# Need `objc-sys` on certain platforms -unstable-docsrs = ["objc-sys", "objc-sys/unstable-docsrs"] - -[dependencies] -objc-sys = { path = "../objc-sys", version = "0.3.2", default-features = false, optional = true } - -[package.metadata.docs.rs] -default-target = "x86_64-apple-darwin" -no-default-features = true -features = ["std", "unstable-docsrs"] - -targets = [ - # MacOS - "x86_64-apple-darwin", - "aarch64-apple-darwin", - # "i686-apple-darwin", - # iOS - "aarch64-apple-ios", - "x86_64-apple-ios", - # "i386-apple-ios", - # GNUStep - "x86_64-unknown-linux-gnu", - "i686-unknown-linux-gnu", - # Windows - "x86_64-pc-windows-msvc", -] diff --git a/crates/block-sys/README.md b/crates/block-sys/README.md deleted file mode 100644 index e26a2ae24..000000000 --- a/crates/block-sys/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# `block-sys` - -[![Latest version](https://badgen.net/crates/v/block-sys)](https://crates.io/crates/block-sys) -[![License](https://badgen.net/badge/license/MIT/blue)](https://github.com/madsmtm/objc2/blob/master/LICENSE.txt) -[![Documentation](https://docs.rs/block-sys/badge.svg)](https://docs.rs/block-sys/) -[![CI](https://github.com/madsmtm/objc2/actions/workflows/ci.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/ci.yml) - -Raw Rust bindings to Apple's C language extension of blocks. - -This crate is part of the [`objc2` project](https://github.com/madsmtm/objc2), -see that for related crates. - - -## Runtime Support - -This library is a raw interface to the aptly specified [Blocks ABI][abi]. -However, different runtime implementations exist and act in slightly different -ways (and have several different helper functions), the most important aspect -being that the libraries are named differently, so the linking must take that -into account. - -You can choose the desired runtime by using the relevant cargo feature flags, -see the following sections (you might have to disable the default `apple` -feature first). Note that if the `objc-sys` crate is present in the module -tree, this should have the same feature flag enabled as that. - - -[abi]: https://clang.llvm.org/docs/Block-ABI-Apple.html - - -### Apple's [`libclosure`](https://github.com/apple-oss-distributions/libclosure) - -- Feature flag: `apple`. - -This is the most sophisticated runtime, and it has quite a lot more features -than the specification mandates. It is used by default. - -The minimum required operating system versions are as follows: -- macOS: `10.6` -- iOS: `3.2` -- tvOS: Unknown -- watchOS: Unknown - -Though in practice Rust itself requires higher versions than this. - - -### LLVM `compiler-rt`'s [`libBlocksRuntime`](https://github.com/llvm/llvm-project/tree/release/13.x/compiler-rt/lib/BlocksRuntime) - -- Feature flag: `compiler-rt`. - -This is a copy of Apple's older (around macOS 10.6) runtime, and is now used -in [Swift's `libdispatch`] and [Swift's Foundation] as well. - -The runtime and associated headers can be installed on many Linux systems with -the `libblocksruntime-dev` package. - -Using this runtime probably won't work together with `objc-sys` crate. - -[Swift's `libdispatch`]: https://github.com/apple/swift-corelibs-libdispatch/tree/swift-5.5.1-RELEASE/src/BlocksRuntime -[Swift's Foundation]: https://github.com/apple/swift-corelibs-foundation/tree/swift-5.5.1-RELEASE/Sources/BlocksRuntime - - -### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2) - -- Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0` and - `gnustep-2-1` depending on the version you're using. - -GNUStep is a bit odd, because it bundles blocks support into its Objective-C -runtime. This means we have to link to `libobjc`, and this is done by -depending on the `objc-sys` crate. A bit unorthodox, yes, but it works. - -Sources: -- [`Block.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_runtime.h) -- [`Block_private.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_private.h) - - -### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC) - -- Feature flag: `unstable-winobjc`. - -**Unstable: Hasn't been tested on Windows yet!** - -[A fork](https://github.com/microsoft/libobjc2) based on GNUStep's `libobjc2` -version 1.8. - - -### [`ObjFW`](https://github.com/ObjFW/ObjFW) - -- Feature flag: `unstable-objfw`. - -**Unstable: Doesn't work yet!** - -TODO. - - -## C Compiler configuration - -To our knowledge, currently only `clang` supports the [Language Specification -for Blocks][block-lang]. To assist in compiling C (or Objective-C) sources -using these features, this crate's build script expose the -`DEP_BLOCK_0_2_CC_ARGS` environment variable to downstream build scripts. - -Example usage in your `build.rs` (using the `cc` crate) would be as follows: - -```rust , ignore -fn main() { - let mut builder = cc::Build::new(); - builder.compiler("clang"); - builder.file("my_script_using_blocks.c"); - - for flag in std::env::var("DEP_BLOCK_0_2_CC_ARGS").unwrap().split(' ') { - builder.flag(flag); - } - - builder.compile("libmy_script_using_blocks.a"); -} -``` - -[block-lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html diff --git a/crates/block-sys/build.rs b/crates/block-sys/build.rs deleted file mode 100644 index 72bfa1c87..000000000 --- a/crates/block-sys/build.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::env; -use std::path::Path; - -fn main() { - // Only rerun if this file changes; the script doesn't depend on our code - println!("cargo:rerun-if-changed=build.rs"); - - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - - let mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some(); - let compiler_rt = env::var_os("CARGO_FEATURE_COMPILER_RT").is_some(); - let mut gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some(); - let objfw = env::var_os("CARGO_FEATURE_UNSTABLE_OBJFW").is_some(); - - // Only when the crate is being compiled directly - if cfg!(feature = "unstable-docsrs") { - if let "macos" | "ios" | "tvos" | "watchos" = &*target_os { - apple = true; - // Add cheaty #[cfg(feature = "apple")] directive - println!("cargo:rustc-cfg=feature=\"apple\""); - } else { - // Also winobjc - gnustep = true; - // Add cheaty #[cfg(feature = "gnustep-1-7")] directive - println!("cargo:rustc-cfg=feature=\"gnustep-1-7\""); - } - } - - let mut cc_args = "-fblocks".to_owned(); - - match (apple, compiler_rt, gnustep, objfw) { - (true, false, false, false) => { - // Link to libclosure (internally called libsystem_blocks), which - // is exported by libSystem.dylib. - // - // Note: Don't get confused by the presence of `System.framework`, - // it is a deprecated wrapper over the dynamic library, so we'd - // rather use the latter. - println!("cargo:rustc-link-lib=dylib=System"); - // Alternative: Only link to libsystem_blocks.dylib - // println!("cargo:rustc-link-search=native=/usr/lib/system"); - // println!("cargo:rustc-link-lib=dylib=system_blocks"); - } - (false, true, false, false) => { - println!("cargo:rustc-link-lib=dylib=BlocksRuntime"); - } - (false, false, true, false) => { - // Don't link to anything; objc-sys already does that for us! - - // Add GNUStep compability headers to make `#include ` - // work (on newer GNUStep versions these headers are present) - if env::var_os("CARGO_FEATURE_GNUSTEP_2_0").is_none() { - let compat_headers = - Path::new(env!("CARGO_MANIFEST_DIR")).join("compat-headers/gnustep"); - cc_args.push_str(" -I"); - cc_args.push_str(compat_headers.to_str().unwrap()); - } - } - (false, false, false, true) => { - // Add compability headers to make `#include ` work. - let compat_headers = Path::new(env!("CARGO_MANIFEST_DIR")).join("compat-headers/objfw"); - cc_args.push_str(" -I"); - cc_args.push_str(compat_headers.to_str().unwrap()); - println!("cargo:rustc-link-lib=dylib=objfw"); - unimplemented!("ObjFW is not yet supported") - } - // Checked in if-let above - (false, false, false, false) => { - panic!("Invalid feature combination; at least one runtime must be selected!") - } - (_, _, _, _) => panic!("Invalid feature combination; only one runtime may be selected!"), - } - - // Add DEP_BLOCK_[version]_CC_ARGS - println!("cargo:cc_args={cc_args}"); -} diff --git a/crates/block-sys/src/lib.rs b/crates/block-sys/src/lib.rs deleted file mode 100644 index 145226376..000000000 --- a/crates/block-sys/src/lib.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! # Raw bindings to Apple's C language extension of blocks -//! -//! The documentation for these bindings is a mix from GNUStep's and Apple's -//! sources, but the [ABI specification][ABI] is really the place you should -//! be looking! -//! -//! See also the [`README.md`](https://crates.io/crates/block-sys) for more -//! background information, and for how to configure the desired runtime. -//! -//! [ABI]: https://clang.llvm.org/docs/Block-ABI-Apple.html - -// TODO: Replace `extern "C"` with `extern "C-unwind"` where applicable. -// See https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html. - -#![no_std] -#![warn(elided_lifetimes_in_paths)] -#![warn(missing_copy_implementations)] -#![deny(non_ascii_idents)] -#![warn(unreachable_pub)] -#![deny(unsafe_op_in_unsafe_fn)] -#![warn(clippy::cargo)] -#![warn(clippy::ptr_as_ptr)] -#![warn(clippy::missing_errors_doc)] -#![warn(clippy::missing_panics_doc)] -#![allow(non_camel_case_types)] -// Update in Cargo.toml as well. -#![doc(html_root_url = "https://docs.rs/block-sys/0.2.0")] -#![cfg_attr(feature = "unstable-docsrs", feature(doc_auto_cfg, doc_cfg_hide))] -#![cfg_attr(feature = "unstable-docsrs", doc(cfg_hide(doc)))] - -extern crate std; - -#[cfg(not(feature = "std"))] -compile_error!("The `std` feature currently must be enabled."); - -// Ensure linkage actually happens -#[cfg(feature = "gnustep-1-7")] -extern crate objc_sys as _; - -#[cfg(doctest)] -#[doc = include_str!("../README.md")] -extern "C" {} - -use core::cell::UnsafeCell; -use core::ffi::c_void; -use core::marker::{PhantomData, PhantomPinned}; -use std::os::raw::{c_char, c_ulong}; - -#[repr(C)] -pub struct Class { - #[cfg(any(feature = "apple", feature = "compiler-rt"))] - _priv: [*mut c_void; 32], - - #[cfg(any(feature = "gnustep-1-7", feature = "objfw"))] - // The size of this is unknown - _priv: [u8; 0], - - /// See objc_sys::OpaqueData - _opaque: UnsafeCell, PhantomPinned)>>, -} - -/// Block descriptor flags. -/// Values for Block_layout->flags to describe block objects -#[allow(non_camel_case_types)] -pub type block_flags = i32; - -#[cfg(any(doc, feature = "apple"))] -pub const BLOCK_DEALLOCATING: block_flags = 0x0001; - -pub const BLOCK_REFCOUNT_MASK: block_flags = if cfg!(feature = "gnustep-1-7") { - // Mask for the reference count in byref structure's flags field. The low - // 3 bytes are reserved for the reference count, the top byte for the flags. - 0x00ffffff -} else if cfg!(any(feature = "compiler-rt", feature = "objfw")) { - 0xffff -} else if cfg!(feature = "apple") { - 0xfffe // runtime -} else { - 0 -}; - -#[cfg(any(doc, feature = "apple"))] -/// compiler -pub const BLOCK_INLINE_LAYOUT_STRING: block_flags = 1 << 21; - -#[cfg(any(doc, feature = "apple"))] -/// compiler -pub const BLOCK_SMALL_DESCRIPTOR: block_flags = 1 << 22; - -#[cfg(any(doc, feature = "apple"))] // Part of ABI? -/// compiler -pub const BLOCK_IS_NOESCAPE: block_flags = 1 << 23; - -#[cfg(any(doc, feature = "apple"))] -/// runtime -pub const BLOCK_NEEDS_FREE: block_flags = 1 << 24; - -/// The block descriptor contains copy and dispose helpers. -/// compiler -pub const BLOCK_HAS_COPY_DISPOSE: block_flags = 1 << 25; - -/// The helpers have C++ code. -/// compiler: helpers have C++ code -pub const BLOCK_HAS_CTOR: block_flags = 1 << 26; - -#[cfg(any(doc, feature = "apple"))] -/// compiler -pub const BLOCK_IS_GC: block_flags = 1 << 27; - -/// Block is stored in global memory and does not need to be copied. -/// compiler -pub const BLOCK_IS_GLOBAL: block_flags = 1 << 28; - -/// Block function uses a calling convention that returns a structure via a -/// pointer passed in by the caller. -/// -/// match (BLOCK_USE_STRET, BLOCK_HAS_SIGNATURE) { -/// (false, false) => 10.6.ABI, no signature field available -/// (true, false) => 10.6.ABI, no signature field available -/// (false, true) => ABI.2010.3.16, regular calling convention, presence of signature field -/// (true, true) => ABI.2010.3.16, stret calling convention, presence of signature field, -/// } -/// -/// See -#[doc(alias = "BLOCK_USE_SRET")] -#[doc(alias = "BLOCK_HAS_DESCRIPTOR")] // compiler-rt || macOS 10.6 -pub const BLOCK_USE_STRET: block_flags = 1 << 29; - -/// Block has an Objective-C type encoding. -/// compiler -pub const BLOCK_HAS_SIGNATURE: block_flags = 1 << 30; - -#[cfg(any(doc, feature = "apple"))] -/// compiler -pub const BLOCK_HAS_EXTENDED_LAYOUT: block_flags = 1 << 31; - -/// Flags used in the final argument to _Block_object_assign() and -/// _Block_object_dispose(). These indicate the type of copy or dispose to -/// perform. -/// Values for _Block_object_assign() and _Block_object_dispose() parameters -/// -/// This is a helper type, in the sources this type does not have a name! -#[allow(non_camel_case_types)] -pub type block_assign_dispose_flags = i32; - -/// The value is of some id-like type, and should be copied as an Objective-C -/// object: i.e. by sending -retain or via the GC assign functions in GC mode -/// (not yet supported). -/// -/// id, NSObject, __attribute__((NSObject)), block, ... -pub const BLOCK_FIELD_IS_OBJECT: block_assign_dispose_flags = 3; - -/// The field is a block. This must be copied by the block copy functions. -/// -/// a block variable -pub const BLOCK_FIELD_IS_BLOCK: block_assign_dispose_flags = 7; - -/// The field is an indirect reference to a variable declared with the __block -/// storage qualifier. -/// -/// the on stack structure holding the __block variable -pub const BLOCK_FIELD_IS_BYREF: block_assign_dispose_flags = 8; - -/// The field is an indirect reference to a variable declared with the __block -/// storage qualifier. -/// -/// declared __weak, only used in byref copy helpers -pub const BLOCK_FIELD_IS_WEAK: block_assign_dispose_flags = 16; - -/// The field is an indirect reference to a variable declared with the __block -/// storage qualifier. -/// -/// called from __block (byref) copy/dispose support routines. -pub const BLOCK_BYREF_CALLER: block_assign_dispose_flags = 128; - -#[cfg(any(doc, feature = "apple"))] -pub const BLOCK_ALL_COPY_DISPOSE_FLAGS: block_assign_dispose_flags = BLOCK_FIELD_IS_OBJECT - | BLOCK_FIELD_IS_BLOCK - | BLOCK_FIELD_IS_BYREF - | BLOCK_FIELD_IS_WEAK - | BLOCK_BYREF_CALLER; - -// TODO: BLOCK_LAYOUT_X - -extern "C" { - // the raw data space for runtime classes for blocks - // class+meta used for stack, malloc, and collectable based blocks - - pub static _NSConcreteGlobalBlock: Class; - pub static _NSConcreteStackBlock: Class; - pub static _NSConcreteMallocBlock: Class; - #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] - pub static _NSConcreteAutoBlock: Class; - #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] - pub static _NSConcreteFinalizingBlock: Class; - #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] - pub static _NSConcreteWeakBlockVariable: Class; - - pub fn _Block_copy(block: *const c_void) -> *mut c_void; - pub fn _Block_release(block: *const c_void); - - /// Runtime entry point called by compiler when assigning objects inside - /// copy helper routines - pub fn _Block_object_assign( - dest_addr: *mut c_void, - object: *const c_void, - flags: block_assign_dispose_flags, - ); - - /// runtime entry point called by the compiler when disposing of objects - /// inside dispose helper routine - pub fn _Block_object_dispose(object: *const c_void, flags: block_assign_dispose_flags); - - #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] - pub fn Block_size(block: *mut c_void) -> c_ulong; // usize - - // Whether the return value of the block is on the stack. - // macOS 10.7 - // #[cfg(any(doc, feature = "apple"))] - // pub fn _Block_use_stret(block: *mut c_void) -> bool; - - // Returns a string describing the block's GC layout. - // This uses the GC skip/scan encoding. - // May return NULL. - // macOS 10.7 - // #[cfg(any(doc, feature = "apple"))] - // pub fn _Block_layout(block: *mut c_void) -> *const c_char; - - // Returns a string describing the block's layout. - // This uses the "extended layout" form described above. - // May return NULL. - // macOS 10.8 - // #[cfg(any(doc, feature = "apple"))] - // pub fn _Block_extended_layout(block: *mut c_void) -> *const c_char; - - // Callable only from the ARR weak subsystem while in exclusion zone - // macOS 10.7 - // #[cfg(any(doc, feature = "apple"))] - // pub fn _Block_tryRetain(block: *const c_void) -> bool; - - // Callable only from the ARR weak subsystem while in exclusion zone - // macOS 10.7 - // #[cfg(any(doc, feature = "apple"))] - // pub fn _Block_isDeallocating(block: *const c_void) -> bool; - - // indicates whether block was compiled with compiler that sets the ABI - // related metadata bits - // macOS 10.7 - // #[cfg(any(doc, feature = "apple", feature = "gnustep-1-7"))] - // pub fn _Block_has_signature(block: *mut c_void) -> bool; - - // Returns a string describing the block's parameter and return types. - // The encoding scheme is the same as Objective-C @encode. - // Returns NULL for blocks compiled with some compilers. - // macOS 10.7 - // #[cfg(any(doc, feature = "apple", feature = "gnustep-1-7"))] - // pub fn _Block_signature(block: *mut c_void) -> *const c_char; -} - -#[repr(C)] -pub struct Block_layout { - /// Class pointer. Always initialised to &_NSConcreteStackBlock for blocks - /// that are created on the stack or &_NSConcreteGlobalBlock for blocks - /// that are created in global storage. - pub isa: *const Class, - /// Flags. - /// See the `block_flags` enumerated type for possible values. - /// Contains ref count in Apple and ObjFW. - pub flags: block_flags, - /// Reserved - always initialised to 0 by the compiler (but this is not - /// said in the specification). - /// - /// Used for the reference count in GNUStep and WinObjC. - pub reserved: i32, - /// The function that implements the block. The first argument is this - /// structure, the subsequent arguments are the block's explicit - /// parameters. If the BLOCK_USE_SRET & BLOCK_HAS_SIGNATURE flag is set, - /// there is an additional hidden argument, which is a pointer to the - /// space on the stack allocated to hold the return value. - pub invoke: Option, - /// The block's descriptor. The actual type of this is: - /// ```pseudo-code - /// match (BLOCK_HAS_COPY_DISPOSE, BLOCK_HAS_SIGNATURE) { - /// (false, false) => Block_descriptor_header, - /// (true, false) => Block_descriptor, - /// (false, true) => Block_descriptor_basic, - /// (true, true) => Block_descriptor_with_signature, - /// } - /// ``` - /// - /// But since all of these start with `Block_descriptor_header`, it is - /// always safe to reinterpret this pointer as that. - // Note: Important to use `*const c_void` until we know which type it is! - pub descriptor: *const c_void, -} - -#[repr(C)] -#[allow(missing_copy_implementations)] -pub struct Block_descriptor_header { - /// Reserved for future use. Currently always 0. - pub reserved: c_ulong, // usize - /// Size of the block. - pub size: c_ulong, // usize -} - -/// Block descriptor that contains copy and dispose operations. -/// -/// Requires BLOCK_HAS_COPY_DISPOSE -#[repr(C)] -pub struct Block_descriptor { - pub header: Block_descriptor_header, - - /// Copy function, generated by the compiler to help copy the block if it - /// contains nontrivial copy operations. - pub copy: Option, - /// Dispose function, generated by the compiler to help copy the block if - /// it contains nontrivial destructors. - pub dispose: Option, -} - -/// Extended block descriptor that does not contain copy and dispose helper -/// functions. -/// -/// Requires BLOCK_HAS_SIGNATURE -#[repr(C)] -pub struct Block_descriptor_basic { - pub header: Block_descriptor_header, - - /// Objective-C type encoding of the block. - #[doc(alias = "signature")] - pub encoding: *const c_char, -} - -/// Requires BLOCK_HAS_COPY_DISPOSE and BLOCK_HAS_SIGNATURE -#[repr(C)] -pub struct Block_descriptor_with_signature { - pub header: Block_descriptor_header, - - /// Same as [`Block_descriptor::copy`]. - pub copy: Option, - /// Same as [`Block_descriptor::dispose`]. - pub dispose: Option, - - /// Objective-C type encoding of the block. - #[doc(alias = "signature")] - pub encoding: *const c_char, -} - -// #[cfg(any(doc, feature = "apple"))] -// pub layout: *const c_char, - -// #[repr(C)] -// pub struct Block_descriptor_small { -// pub size: u32, -// pub signature: i32, -// pub layout: i32, -// pub copy: i32, -// pub dispose: i32, -// } - -// #[repr(C)] -// pub struct Block_basic { -// pub isa: *mut c_void, -// pub Block_flags: i32, -// pub Block_size: i32, -// pub Block_invoke: Option, -// pub Block_copy: Option, -// pub Block_dispose: Option, -// } -// Example usage: https://github.com/apple-oss-distributions/libdispatch/blob/libdispatch-84.5/src/once.c - -/// Structure used for on-stack variables that are referenced by blocks. -#[repr(C)] -#[doc(alias = "Block_byref_1")] -pub struct Block_byref_header { - /// Class pointer. Currently unused on GNUStep and always NULL. Could be - /// used in the future to support introspection. - pub isa: *const Class, - /// The pointer to the structure that contains the real version of the - /// data. All accesses go via this pointer. If an on-stack byref structure - /// is copied to the heap, then its forwarding pointer should point to the - /// heap version. Otherwise it should point to itself. - pub forwarding: *mut Block_byref_header, - /// Flags and reference count. - /// - /// TODO: Volatile! - pub flags: block_flags, - #[cfg(feature = "apple")] - /// Size of this structure. - pub size: u32, - #[cfg(not(feature = "apple"))] - /// Size of this structure. - pub size: i32, -} - -/// Structure used for on-stack variables that are referenced by blocks. -/// -/// requires BLOCK_BYREF_HAS_COPY_DISPOSE -#[repr(C)] -#[doc(alias = "Block_byref_2")] -pub struct Block_byref { - pub header: Block_byref_header, - /// Copy function. - pub keep: Option, - /// Dispose function. - pub destroy: Option, -} - -#[cfg(any(doc, feature = "apple"))] -/// Structure used for on-stack variables that are referenced by blocks. -/// -/// requires BLOCK_BYREF_LAYOUT_EXTENDED -#[repr(C)] -#[doc(alias = "Block_byref_3")] -pub struct Block_byref_extended { - pub header: Block_byref_header, - /// Same as [`Block_byref::keep`]. - pub keep: Option, - /// Same as [`Block_byref::destroy`]. - pub destroy: Option, - pub layout: *const c_char, -} - -#[cfg(test)] -mod tests { - use super::*; - use core::ptr; - use std::println; - - #[test] - fn smoke() { - assert_eq!(unsafe { _Block_copy(ptr::null_mut()) }, ptr::null_mut()); - } - - #[test] - fn test_linkable() { - println!("{:p}", unsafe { &_NSConcreteGlobalBlock }); - println!("{:p}", unsafe { &_NSConcreteStackBlock }); - println!("{:p}", unsafe { &_NSConcreteMallocBlock }); - println!("{:p}", _Block_copy as unsafe extern "C" fn(_) -> _); - println!( - "{:p}", - _Block_object_assign as unsafe extern "C" fn(_, _, _) - ); - println!("{:p}", _Block_object_dispose as unsafe extern "C" fn(_, _)); - println!("{:p}", _Block_release as unsafe extern "C" fn(_)); - #[cfg(any(feature = "apple", feature = "compiler-rt"))] - { - println!("{:p}", unsafe { &_NSConcreteAutoBlock }); - println!("{:p}", unsafe { &_NSConcreteFinalizingBlock }); - println!("{:p}", unsafe { &_NSConcreteWeakBlockVariable }); - println!("{:p}", Block_size as unsafe extern "C" fn(_) -> _); - } - } -} diff --git a/crates/block2/CHANGELOG.md b/crates/block2/CHANGELOG.md index 9e24815d4..bf777ac50 100644 --- a/crates/block2/CHANGELOG.md +++ b/crates/block2/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). `_Block_release`, `_Block_object_assign`, `_Block_object_dispose`, `_NSConcreteGlobalBlock`, `_NSConcreteStackBlock` and `Class` in `ffi` module. +* No longer use the `block-sys` crate for linking to the blocks runtime. ## 0.4.0 - 2023-12-03 @@ -76,7 +77,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed * **BREAKING**: Updated `objc2-encode` to `v2.0.0-pre.0`. -* **BREAKING**: Updated `ffi` module to `block-sys v0.0.4`. +* **BREAKING**: Updated `ffi` module to `block-sys v0.0.4`. This tweaks the + types of a lot of fields and arguments, and makes the apple runtime always + be the default. ### Removed * **BREAKING**: Removed `DerefMut` implementation for `ConcreteBlock`. @@ -97,7 +100,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). creating blocks that don't reference their environment. ### Changed -* **BREAKING**: Updated `ffi` module to `block-sys v0.0.2` +* **BREAKING**: Updated `ffi` module to `block-sys v0.0.2`. This means that + `Class` is now `!UnwindSafe`. ## 0.2.0-alpha.1 - 2021-11-22 diff --git a/crates/block2/Cargo.toml b/crates/block2/Cargo.toml index 86fd02465..56c7ad66b 100644 --- a/crates/block2/Cargo.toml +++ b/crates/block2/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "block2" -# Remember to update html_root_url in lib.rs and README.md +# Remember to update html_root_url in lib.rs version = "0.4.0" authors = ["Steven Sheldon", "Mads Marquart "] edition = "2021" @@ -12,6 +12,7 @@ categories = [ "api-bindings", "development-tools::ffi", "os::macos-apis", + "external-ffi-bindings", ] readme = "README.md" repository = "https://github.com/madsmtm/objc2" @@ -19,28 +20,45 @@ documentation = "https://docs.rs/block2/" license = "MIT" [features] +# The default runtime is Apple's. Other platforms will probably error if the +# correct feature flag is not specified. default = ["std", "apple"] # Currently not possible to turn off, put here for forwards compatibility. -std = ["alloc", "objc2/std", "block-sys/std"] -alloc = ["objc2/alloc", "block-sys/alloc"] - -# Runtime selection. Default is `apple`. See `block-sys` for details. -apple = ["block-sys/apple", "objc2/apple"] -compiler-rt = ["block-sys/compiler-rt", "objc2/unstable-compiler-rt"] # TODO: fix this -gnustep-1-7 = ["block-sys/gnustep-1-7", "objc2/gnustep-1-7"] -gnustep-1-8 = ["gnustep-1-7", "block-sys/gnustep-1-8", "objc2/gnustep-1-8"] -gnustep-1-9 = ["gnustep-1-8", "block-sys/gnustep-1-9", "objc2/gnustep-1-9"] -gnustep-2-0 = ["gnustep-1-9", "block-sys/gnustep-2-0", "objc2/gnustep-2-0"] -gnustep-2-1 = ["gnustep-2-0", "block-sys/gnustep-2-1", "objc2/gnustep-2-1"] +std = ["alloc", "objc2/std"] +alloc = ["objc2/alloc"] + +# Link to Apple's libclosure (exists in libSystem). +apple = ["objc2/apple"] + +# Link to libBlocksRuntime from compiler-rt. +compiler-rt = ["objc2/unstable-compiler-rt"] # TODO: fix this + +# Link to GNUStep's libobjc2 (which contains the block implementation). +gnustep-1-7 = ["objc2/gnustep-1-7"] +gnustep-1-8 = ["gnustep-1-7", "objc2/gnustep-1-8"] +gnustep-1-9 = ["gnustep-1-8", "objc2/gnustep-1-9"] +gnustep-2-0 = ["gnustep-1-9", "objc2/gnustep-2-0"] +gnustep-2-1 = ["gnustep-2-0", "objc2/gnustep-2-1"] + +# Link to Microsoft's libobjc2. +unstable-winobjc = ["gnustep-1-8"] + +# Link to ObjFW. +unstable-objfw = [] + +# Expose private ffi functions and statics. +unstable-private = [] + +# For better documentation on docs.rs. +unstable-docsrs = [] [dependencies] objc2 = { path = "../objc2", version = "0.5.0", default-features = false } -block-sys = { path = "../block-sys", version = "0.2.0", default-features = false } [package.metadata.docs.rs] default-target = "x86_64-apple-darwin" - +features = ["unstable-docsrs", "unstable-private"] targets = [ # MacOS "x86_64-apple-darwin", diff --git a/crates/block2/src/abi.rs b/crates/block2/src/abi.rs index 159d0e94e..fe5ab024e 100644 --- a/crates/block2/src/abi.rs +++ b/crates/block2/src/abi.rs @@ -1 +1,312 @@ -pub(crate) use block_sys::*; +//! The documentation for these bindings is a mix from GNUStep's and Apple's +//! sources, but the [ABI specification][ABI] is really the place you should +//! be looking! +//! +//! [ABI]: https://clang.llvm.org/docs/Block-ABI-Apple.html +#![allow( + unreachable_pub, + unused, + non_camel_case_types, + missing_debug_implementations +)] + +use core::ffi::c_void; +use std::os::raw::{c_char, c_ulong}; + +use crate::ffi::Class; + +/// Block descriptor flags. +/// Values for Block_layout->flags to describe block objects +#[allow(non_camel_case_types)] +pub type block_flags = i32; + +#[cfg(any(doc, feature = "apple"))] +pub const BLOCK_DEALLOCATING: block_flags = 0x0001; + +pub const BLOCK_REFCOUNT_MASK: block_flags = if cfg!(feature = "gnustep-1-7") { + // Mask for the reference count in byref structure's flags field. The low + // 3 bytes are reserved for the reference count, the top byte for the flags. + 0x00ffffff +} else if cfg!(any(feature = "compiler-rt", feature = "objfw")) { + 0xffff +} else if cfg!(feature = "apple") { + 0xfffe // runtime +} else { + 0 +}; + +#[cfg(any(doc, feature = "apple"))] +/// compiler +pub const BLOCK_INLINE_LAYOUT_STRING: block_flags = 1 << 21; + +#[cfg(any(doc, feature = "apple"))] +/// compiler +pub const BLOCK_SMALL_DESCRIPTOR: block_flags = 1 << 22; + +#[cfg(any(doc, feature = "apple"))] // Part of ABI? +/// compiler +pub const BLOCK_IS_NOESCAPE: block_flags = 1 << 23; + +#[cfg(any(doc, feature = "apple"))] +/// runtime +pub const BLOCK_NEEDS_FREE: block_flags = 1 << 24; + +/// The block descriptor contains copy and dispose helpers. +/// compiler +pub const BLOCK_HAS_COPY_DISPOSE: block_flags = 1 << 25; + +/// The helpers have C++ code. +/// compiler: helpers have C++ code +pub const BLOCK_HAS_CTOR: block_flags = 1 << 26; + +#[cfg(any(doc, feature = "apple"))] +/// compiler +pub const BLOCK_IS_GC: block_flags = 1 << 27; + +/// Block is stored in global memory and does not need to be copied. +/// compiler +pub const BLOCK_IS_GLOBAL: block_flags = 1 << 28; + +/// Block function uses a calling convention that returns a structure via a +/// pointer passed in by the caller. +/// +/// match (BLOCK_USE_STRET, BLOCK_HAS_SIGNATURE) { +/// (false, false) => 10.6.ABI, no signature field available +/// (true, false) => 10.6.ABI, no signature field available +/// (false, true) => ABI.2010.3.16, regular calling convention, presence of signature field +/// (true, true) => ABI.2010.3.16, stret calling convention, presence of signature field, +/// } +/// +/// See +#[doc(alias = "BLOCK_USE_SRET")] +#[doc(alias = "BLOCK_HAS_DESCRIPTOR")] // compiler-rt || macOS 10.6 +pub const BLOCK_USE_STRET: block_flags = 1 << 29; + +/// Block has an Objective-C type encoding. +/// compiler +pub const BLOCK_HAS_SIGNATURE: block_flags = 1 << 30; + +#[cfg(any(doc, feature = "apple"))] +/// compiler +pub const BLOCK_HAS_EXTENDED_LAYOUT: block_flags = 1 << 31; + +/// Flags used in the final argument to _Block_object_assign() and +/// _Block_object_dispose(). These indicate the type of copy or dispose to +/// perform. +/// Values for _Block_object_assign() and _Block_object_dispose() parameters +/// +/// This is a helper type, in the sources this type does not have a name! +#[allow(non_camel_case_types)] +pub type block_assign_dispose_flags = i32; + +/// The value is of some id-like type, and should be copied as an Objective-C +/// object: i.e. by sending -retain or via the GC assign functions in GC mode +/// (not yet supported). +/// +/// id, NSObject, __attribute__((NSObject)), block, ... +pub const BLOCK_FIELD_IS_OBJECT: block_assign_dispose_flags = 3; + +/// The field is a block. This must be copied by the block copy functions. +/// +/// a block variable +pub const BLOCK_FIELD_IS_BLOCK: block_assign_dispose_flags = 7; + +/// The field is an indirect reference to a variable declared with the __block +/// storage qualifier. +/// +/// the on stack structure holding the __block variable +pub const BLOCK_FIELD_IS_BYREF: block_assign_dispose_flags = 8; + +/// The field is an indirect reference to a variable declared with the __block +/// storage qualifier. +/// +/// declared __weak, only used in byref copy helpers +pub const BLOCK_FIELD_IS_WEAK: block_assign_dispose_flags = 16; + +/// The field is an indirect reference to a variable declared with the __block +/// storage qualifier. +/// +/// called from __block (byref) copy/dispose support routines. +pub const BLOCK_BYREF_CALLER: block_assign_dispose_flags = 128; + +#[cfg(any(doc, feature = "apple"))] +pub const BLOCK_ALL_COPY_DISPOSE_FLAGS: block_assign_dispose_flags = BLOCK_FIELD_IS_OBJECT + | BLOCK_FIELD_IS_BLOCK + | BLOCK_FIELD_IS_BYREF + | BLOCK_FIELD_IS_WEAK + | BLOCK_BYREF_CALLER; + +// TODO: BLOCK_LAYOUT_X + +/// The expected layout of every block. +#[repr(C)] +#[doc(alias = "__block_literal")] +pub struct Block_layout { + /// Class pointer. Always initialised to &_NSConcreteStackBlock for blocks + /// that are created on the stack or &_NSConcreteGlobalBlock for blocks + /// that are created in global storage. + pub isa: *const Class, + /// Flags. + /// See the `block_flags` enumerated type for possible values. + /// Contains ref count in Apple and ObjFW. + pub flags: block_flags, + /// Reserved - always initialised to 0 by the compiler (but this is not + /// said in the specification). + /// + /// Used for the reference count in GNUStep and WinObjC. + pub reserved: i32, + /// The function that implements the block. The first argument is this + /// structure, the subsequent arguments are the block's explicit + /// parameters. If the BLOCK_USE_SRET & BLOCK_HAS_SIGNATURE flag is set, + /// there is an additional hidden argument, which is a pointer to the + /// space on the stack allocated to hold the return value. + pub invoke: Option, + /// The block's descriptor. The actual type of this is: + /// ```pseudo-code + /// match (BLOCK_HAS_COPY_DISPOSE, BLOCK_HAS_SIGNATURE) { + /// (false, false) => Block_descriptor_header, + /// (true, false) => Block_descriptor, + /// (false, true) => Block_descriptor_basic, + /// (true, true) => Block_descriptor_with_signature, + /// } + /// ``` + /// + /// But since all of these start with `Block_descriptor_header`, it is + /// always safe to reinterpret this pointer as that. + // Note: Important to use `*const c_void` until we know which type it is! + pub descriptor: *const c_void, +} + +#[repr(C)] +#[allow(missing_copy_implementations)] +#[doc(alias = "__block_descriptor")] +#[doc(alias = "Block_descriptor_1")] +pub struct Block_descriptor_header { + /// Reserved for future use. Currently always 0. + pub reserved: c_ulong, // usize + /// Size of the block. + pub size: c_ulong, // usize +} + +/// Block descriptor that contains copy and dispose operations. +/// +/// Requires BLOCK_HAS_COPY_DISPOSE +#[repr(C)] +#[doc(alias = "Block_descriptor_2")] +pub struct Block_descriptor { + pub header: Block_descriptor_header, + + /// Copy function, generated by the compiler to help copy the block if it + /// contains nontrivial copy operations. + pub copy: Option, + /// Dispose function, generated by the compiler to help copy the block if + /// it contains nontrivial destructors. + pub dispose: Option, +} + +/// Extended block descriptor that does not contain copy and dispose helper +/// functions. +/// +/// Requires BLOCK_HAS_SIGNATURE +#[repr(C)] +#[doc(alias = "Block_descriptor_3")] +pub struct Block_descriptor_basic { + pub header: Block_descriptor_header, + + /// Objective-C type encoding of the block. + #[doc(alias = "signature")] + pub encoding: *const c_char, +} + +/// Requires BLOCK_HAS_COPY_DISPOSE and BLOCK_HAS_SIGNATURE +#[repr(C)] +#[doc(alias = "Block_descriptor_2")] +#[doc(alias = "Block_descriptor_3")] +pub struct Block_descriptor_with_signature { + pub header: Block_descriptor_header, + + /// Same as [`Block_descriptor::copy`]. + pub copy: Option, + /// Same as [`Block_descriptor::dispose`]. + pub dispose: Option, + + /// Objective-C type encoding of the block. + #[doc(alias = "signature")] + pub encoding: *const c_char, +} + +// #[cfg(any(doc, feature = "apple"))] +// pub layout: *const c_char, + +// #[repr(C)] +// pub struct Block_descriptor_small { +// pub size: u32, +// pub signature: i32, +// pub layout: i32, +// pub copy: i32, +// pub dispose: i32, +// } + +// #[repr(C)] +// pub struct Block_basic { +// pub isa: *mut Class, +// pub Block_flags: i32, +// pub Block_size: i32, +// pub Block_invoke: Option, +// pub Block_copy: Option, +// pub Block_dispose: Option, +// } +// Example usage: https://github.com/apple-oss-distributions/libdispatch/blob/libdispatch-84.5/src/once.c + +/// Structure used for on-stack variables that are referenced by blocks. +#[repr(C)] +#[doc(alias = "Block_byref_1")] +#[doc(alias = "_block_byref")] +pub struct Block_byref_header { + /// Class pointer. Currently unused on GNUStep and always NULL. Could be + /// used in the future to support introspection. + pub isa: *const Class, + /// The pointer to the structure that contains the real version of the + /// data. All accesses go via this pointer. If an on-stack byref structure + /// is copied to the heap, then its forwarding pointer should point to the + /// heap version. Otherwise it should point to itself. + pub forwarding: *mut Block_byref_header, + /// Flags and reference count. + /// + /// TODO: Volatile! + pub flags: block_flags, + #[cfg(feature = "apple")] + /// Size of this structure. + pub size: u32, + #[cfg(not(feature = "apple"))] + /// Size of this structure. + pub size: i32, +} + +/// Structure used for on-stack variables that are referenced by blocks. +/// +/// requires BLOCK_BYREF_HAS_COPY_DISPOSE +#[repr(C)] +#[doc(alias = "Block_byref_2")] +pub struct Block_byref { + pub header: Block_byref_header, + /// Copy function. + pub keep: Option, + /// Dispose function. + pub destroy: Option, +} + +#[cfg(any(doc, feature = "apple"))] +/// Structure used for on-stack variables that are referenced by blocks. +/// +/// requires BLOCK_BYREF_LAYOUT_EXTENDED +#[repr(C)] +#[doc(alias = "Block_byref_3")] +pub struct Block_byref_extended { + pub header: Block_byref_header, + /// Same as [`Block_byref::keep`]. + pub keep: Option, + /// Same as [`Block_byref::destroy`]. + pub destroy: Option, + pub layout: *const c_char, +} diff --git a/crates/block2/src/debug.rs b/crates/block2/src/debug.rs index c1b9d486a..430a87d98 100644 --- a/crates/block2/src/debug.rs +++ b/crates/block2/src/debug.rs @@ -7,7 +7,7 @@ use std::ffi::CStr; use crate::{abi, ffi, Block, ConcreteBlock, GlobalBlock, RcBlock}; #[derive(Clone, Copy, PartialEq, Eq)] -struct Isa(*const abi::Class); +struct Isa(*const ffi::Class); impl Isa { fn is_global(self) -> bool { @@ -222,7 +222,7 @@ mod tests { let isa = Isa(unsafe { &ffi::_NSConcreteStackBlock }); assert!(!isa.is_global()); assert!(isa.is_stack()); - let isa = Isa(unsafe { &abi::_NSConcreteMallocBlock }); + let isa = Isa(unsafe { &ffi::private::_NSConcreteMallocBlock }); assert!(!isa.is_global()); assert!(!isa.is_stack()); let isa = Isa(ptr::null()); diff --git a/crates/block2/src/ffi.rs b/crates/block2/src/ffi.rs index a338aac1e..a8776d177 100644 --- a/crates/block2/src/ffi.rs +++ b/crates/block2/src/ffi.rs @@ -1,6 +1,167 @@ -//! `Block.h` +//! # Raw bindings to `Block.h` -pub use block_sys::{ - Class, _Block_copy, _Block_object_assign, _Block_object_dispose, _Block_release, - _NSConcreteGlobalBlock, _NSConcreteStackBlock, -}; +use core::cell::UnsafeCell; +use core::ffi::c_void; +use core::marker::{PhantomData, PhantomPinned}; +use std::os::raw::c_int; + +/// Type for block class ISAs. +/// +/// This will likely become an extern type in the future. +#[repr(C)] +#[allow(missing_debug_implementations)] +pub struct Class { + /// The size probably doesn't really matter here, as we only ever use the + /// classes behind pointers, but let's import it with the correct size to + /// be sure. + #[cfg(any(feature = "apple", feature = "compiler-rt"))] + _priv: [*mut c_void; 32], + + /// The size of this is unknown, so let's use a ZST so the compiler + /// doesn't assume anything about the size. + #[cfg(any(feature = "gnustep-1-7", feature = "objfw"))] + _priv: [u8; 0], + + /// Mark as `!Send + !Sync + !Unpin` and as mutable behind shared + /// references (`!Freeze`). + /// + /// Same as `objc_sys::OpaqueData`. + _opaque: UnsafeCell, PhantomPinned)>>, +} + +// TODO: Use `extern "C-unwind"` when in MSRV (because the runtime functions +// may call external routines). +extern "C" { + /// Class ISA used for global blocks. + pub static _NSConcreteGlobalBlock: Class; + + /// Class ISA used for stack blocks. + pub static _NSConcreteStackBlock: Class; + + /// Copy/retain a block. + /// + /// When called on a: + /// - Global block: Does nothing. + /// - Stack block: `memmove`s the block to a new heap allocation, calls + /// the copy helper, and returns the new malloc block. + /// - Malloc block: Increments the retain count. + /// + /// Returns `NULL` on allocation failure. + #[doc(alias = "Block_copy")] + pub fn _Block_copy(block: *const c_void) -> *mut c_void; + + /// Release a block. + /// + /// When called on a: + /// - Global block: Does nothing. + /// - Stack block: Does nothing. + /// - Malloc block: Decrements the retain count, and if it reaches zero, + /// calls the dispose helper and frees the underlying storage. + #[doc(alias = "Block_release")] + pub fn _Block_release(block: *const c_void); + + /// Copy a block field or `__block` variable from one location to another. + /// + /// Called by C compilers to clone fields inside copy helper routines, and + /// to handle memory management of `__block` marked variables. + pub fn _Block_object_assign(dest_addr: *mut c_void, object: *const c_void, flags: c_int); + + /// Dispose an object previously copied using `_Block_object_assign`. + /// + /// Called by C compilers to drop fields inside dispose helper routines, + /// and handle memory management of `__block` marked variables. + pub fn _Block_object_dispose(object: *const c_void, flags: c_int); +} + +/// `Block_private.h` +#[allow(missing_docs)] +#[cfg(any(test, feature = "unstable-private"))] +pub mod private { + use super::*; + #[cfg(any(doc, feature = "apple", feature = "gnustep-1-7"))] + use std::os::raw::c_char; + #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] + use std::os::raw::c_ulong; + + extern "C" { + pub static _NSConcreteMallocBlock: Class; + #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] + pub static _NSConcreteAutoBlock: Class; + #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] + pub static _NSConcreteFinalizingBlock: Class; + #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] + pub static _NSConcreteWeakBlockVariable: Class; + + #[cfg(any(doc, feature = "apple", feature = "compiler-rt"))] + pub fn Block_size(block: *mut c_void) -> c_ulong; // usize + + // Whether the return value of the block is on the stack. + // macOS 10.7 + #[cfg(any(doc, feature = "apple"))] + pub fn _Block_use_stret(block: *mut c_void) -> bool; + + // Returns a string describing the block's GC layout. + // This uses the GC skip/scan encoding. + // May return NULL. + // macOS 10.7 + #[cfg(any(doc, feature = "apple"))] + pub fn _Block_layout(block: *mut c_void) -> *const c_char; + + // Returns a string describing the block's layout. + // This uses the "extended layout" form described above. + // May return NULL. + // macOS 10.8 + #[cfg(any(doc, feature = "apple"))] + pub fn _Block_extended_layout(block: *mut c_void) -> *const c_char; + + // Callable only from the ARR weak subsystem while in exclusion zone + // macOS 10.7 + #[cfg(any(doc, feature = "apple"))] + pub fn _Block_tryRetain(block: *const c_void) -> bool; + + // Callable only from the ARR weak subsystem while in exclusion zone + // macOS 10.7 + #[cfg(any(doc, feature = "apple"))] + pub fn _Block_isDeallocating(block: *const c_void) -> bool; + + // indicates whether block was compiled with compiler that sets the ABI + // related metadata bits + // macOS 10.7 + #[cfg(any(doc, feature = "apple", feature = "gnustep-1-7"))] + pub fn _Block_has_signature(block: *mut c_void) -> bool; + + // Returns a string describing the block's parameter and return types. + // The encoding scheme is the same as Objective-C @encode. + // Returns NULL for blocks compiled with some compilers. + // macOS 10.7 + #[cfg(any(doc, feature = "apple", feature = "gnustep-1-7"))] + pub fn _Block_signature(block: *mut c_void) -> *const c_char; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::ptr; + use std::println; + + #[test] + fn smoke() { + assert_eq!(unsafe { _Block_copy(ptr::null_mut()) }, ptr::null_mut()); + unsafe { _Block_release(ptr::null_mut()) }; + } + + #[test] + fn test_linkable() { + println!("{:p}", unsafe { &_NSConcreteGlobalBlock }); + println!("{:p}", unsafe { &_NSConcreteStackBlock }); + println!("{:p}", unsafe { &private::_NSConcreteMallocBlock }); + println!("{:p}", _Block_copy as unsafe extern "C" fn(_) -> _); + println!( + "{:p}", + _Block_object_assign as unsafe extern "C" fn(_, _, _) + ); + println!("{:p}", _Block_object_dispose as unsafe extern "C" fn(_, _)); + println!("{:p}", _Block_release as unsafe extern "C" fn(_)); + } +} diff --git a/crates/block2/src/lib.rs b/crates/block2/src/lib.rs index a313eee7e..94c6c8b55 100644 --- a/crates/block2/src/lib.rs +++ b/crates/block2/src/lib.rs @@ -12,6 +12,10 @@ //! (Note that while this library can be used separately from Objective-C, //! they're most commonly used together). //! +//! [lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html +//! [ABI]: http://clang.llvm.org/docs/Block-ABI-Apple.html +//! +//! //! ## Invoking blocks //! //! The [`Block`] struct is used for invoking blocks from Objective-C. For @@ -38,6 +42,7 @@ //! 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 @@ -71,8 +76,93 @@ //! assert_eq!(unsafe { MY_BLOCK.call(()) }, 10.0); //! ``` //! -//! [lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html -//! [ABI]: http://clang.llvm.org/docs/Block-ABI-Apple.html +//! +//! ## Specifying a runtime +//! +//! Different runtime implementations exist and act in slightly different ways +//! (and have several different helper functions), the most important aspect +//! being that the libraries are named differently, so we must take that into +//! account when linking. +//! +//! You can choose the desired runtime by using the relevant cargo feature +//! flags, see the following sections (you might have to disable the default +//! `"apple"` feature first). +//! +//! +//! ### Apple's [`libclosure`](https://github.com/apple-oss-distributions/libclosure) +//! +//! - Feature flag: `apple`. +//! +//! This is the most common an most sophisticated runtime, and it has quite a +//! lot more features than the specification mandates. +//! +//! The minimum required operating system versions are as follows (though in +//! practice Rust itself requires higher versions than this): +//! +//! - macOS: `10.6` +//! - iOS/iPadOS: `3.2` +//! - tvOS: `1.0` +//! - watchOS: `1.0` +//! +//! **This is used by default**, so you do not need to specify a runtime if +//! you're using this crate on of these platforms. +//! +//! +//! ### LLVM `compiler-rt`'s [`libBlocksRuntime`](https://github.com/llvm/llvm-project/tree/release/13.x/compiler-rt/lib/BlocksRuntime) +//! +//! - Feature flag: `compiler-rt`. +//! +//! This is a copy of Apple's older (around macOS 10.6) runtime, and is now +//! used in [Swift's `libdispatch`] and [Swift's Foundation] as well. +//! +//! The runtime and associated headers can be installed on many Linux systems +//! with the `libblocksruntime-dev` package. +//! +//! Using this runtime probably won't work together with `objc2` crate. +//! +//! [Swift's `libdispatch`]: https://github.com/apple/swift-corelibs-libdispatch/tree/swift-5.5.1-RELEASE/src/BlocksRuntime +//! [Swift's Foundation]: https://github.com/apple/swift-corelibs-foundation/tree/swift-5.5.1-RELEASE/Sources/BlocksRuntime +//! +//! +//! ### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2) +//! +//! - Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0` +//! and `gnustep-2-1` depending on the version you're using. +//! +//! GNUStep is a bit odd, because it bundles blocks support into its +//! Objective-C runtime. This means we have to link to `libobjc`, and this is +//! done by depending on the `objc2` crate. A bit unorthodox, yes, but it +//! works. +//! +//! Sources: +//! +//! - [`Block.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_runtime.h) +//! - [`Block_private.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_private.h) +//! +//! +//! ### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC) +//! +//! - Feature flag: `unstable-winobjc`. +//! +//! **Unstable: Hasn't been tested on Windows yet!** +//! +//! [A fork](https://github.com/microsoft/libobjc2) based on GNUStep's `libobjc2` +//! version 1.8. +//! +//! +//! ### [`ObjFW`](https://github.com/ObjFW/ObjFW) +//! +//! - Feature flag: `unstable-objfw`. +//! +//! **Unstable: Doesn't work yet!** +//! +//! +//! ## C Compiler configuration +//! +//! To our knowledge, only Clang supports blocks. To compile C or Objective-C +//! sources using these features, you should set [the `-fblocks` flag][flag]. +//! +//! [flag]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fblocks #![no_std] #![warn(elided_lifetimes_in_paths)] @@ -88,17 +178,62 @@ #![warn(clippy::missing_panics_doc)] // Update in Cargo.toml as well. #![doc(html_root_url = "https://docs.rs/block2/0.4.0")] +#![cfg_attr(feature = "unstable-docsrs", feature(doc_auto_cfg, doc_cfg_hide))] +#![cfg_attr(feature = "unstable-docsrs", doc(cfg_hide(doc)))] extern crate alloc; extern crate std; +#[cfg(doctest)] +#[doc = include_str!("../README.md")] +extern "C" {} + #[cfg(not(feature = "std"))] compile_error!("The `std` feature currently must be enabled."); -#[cfg(doctest)] -#[doc = include_str!("../README.md")] +#[cfg(not(any( + feature = "apple", + feature = "compiler-rt", + feature = "gnustep-1-7", + feature = "unstable-objfw", +)))] +compile_error!("A runtime must be selected"); + +#[cfg(any( + all(feature = "apple", feature = "compiler-rt"), + all(feature = "compiler-rt", feature = "gnustep-1-7"), + all(feature = "gnustep-1-7", feature = "unstable-objfw"), + all(feature = "unstable-objfw", feature = "apple"), + all(feature = "apple", feature = "gnustep-1-7"), + all(feature = "compiler-rt", feature = "unstable-objfw"), +))] +compile_error!("Only one runtime may be selected"); + +#[cfg(feature = "unstable-objfw")] +compile_error!("ObjFW is not yet supported"); + +// Link to `libclosure` (internally called `libsystem_blocks.dylib`), which is +// exported by `libSystem.dylib`. +// +// Note: Don't get confused by the presence of `System.framework`, it is a +// deprecated wrapper over the dynamic library, so we'd rather use the latter. +// +// Alternative: Only link to `libsystem_blocks.dylib`: +// println!("cargo:rustc-link-search=native=/usr/lib/system"); +// println!("cargo:rustc-link-lib=dylib=system_blocks"); +#[cfg_attr(feature = "apple", link(name = "System", kind = "dylib"))] +// Link to `libBlocksRuntime`. +#[cfg_attr(feature = "compiler-rt", link(name = "BlocksRuntime", kind = "dylib"))] +// Link to `libobjfw`, which provides the blocks implementation. +#[cfg_attr(feature = "unstable-objfw", link(name = "objfw", kind = "dylib"))] extern "C" {} +// Don't link to anything on GNUStep; objc2 already does that for us! +// +// We do want to ensure linkage actually happens, though. +#[cfg(feature = "gnustep-1-7")] +extern crate objc2 as _; + mod abi; mod block; mod concrete_block; @@ -107,9 +242,9 @@ pub mod ffi; mod global; mod rc_block; -pub use block::{Block, BlockArguments}; #[doc(hidden)] -pub use block_sys::Block_layout as __Block_layout; +pub use abi::Block_layout as __Block_layout; +pub use block::{Block, BlockArguments}; pub use concrete_block::{ConcreteBlock, IntoConcreteBlock}; pub use global::GlobalBlock; pub use rc_block::RcBlock; diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 8ec52d52a..9a6991791 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -39,7 +39,6 @@ gnustep-2-1 = ["gnustep-2-0", "block2/gnustep-2-1", "objc2/gnustep-2-1", "icrate [dependencies] block2 = { path = "../block2", default-features = false } -block-sys = { path = "../block-sys", default-features = false } objc-sys = { path = "../objc-sys", default-features = false } objc2 = { path = "../objc2", default-features = false } icrate = { path = "../icrate", default-features = false } diff --git a/crates/tests/build.rs b/crates/tests/build.rs index b0836ecda..093cf4ca1 100644 --- a/crates/tests/build.rs +++ b/crates/tests/build.rs @@ -8,14 +8,19 @@ fn main() { builder.file("extern/block_utils.c"); println!("cargo:rerun-if-changed=extern/block_utils.c"); - for flag in env::var("DEP_BLOCK_0_2_CC_ARGS").unwrap().split(' ') { + for flag in env::var("DEP_OBJC_0_3_CC_ARGS").unwrap().split(' ') { builder.flag(flag); } - for flag in env::var("DEP_OBJC_0_3_CC_ARGS").unwrap().split(' ') { - builder.flag(flag); + if cfg!(feature = "gnustep-1-7") && !cfg!(feature = "gnustep-2-0") { + builder.include("compat-headers/gnustep-pre-2-0"); } + if cfg!(feature = "unstable-objfw") { + builder.include("compat-headers/objfw"); + } + + builder.flag("-fblocks"); builder.flag("-fno-objc-arc"); builder.flag("-xobjective-c"); @@ -29,9 +34,7 @@ fn main() { println!("cargo:rerun-if-changed=extern/encode_utils.m"); println!("cargo:rerun-if-changed=extern/test_object.m"); - for flag in env::var("DEP_BLOCK_0_2_CC_ARGS").unwrap().split(' ') { - builder.flag(flag); - } + builder.flag("-fblocks"); for flag in env::var("DEP_OBJC_0_3_CC_ARGS").unwrap().split(' ') { builder.flag(flag); diff --git a/crates/block-sys/compat-headers/gnustep/Block.h b/crates/tests/compat-headers/gnustep-pre-2-0/Block.h similarity index 100% rename from crates/block-sys/compat-headers/gnustep/Block.h rename to crates/tests/compat-headers/gnustep-pre-2-0/Block.h diff --git a/crates/block-sys/compat-headers/gnustep/Block_private.h b/crates/tests/compat-headers/gnustep-pre-2-0/Block_private.h similarity index 100% rename from crates/block-sys/compat-headers/gnustep/Block_private.h rename to crates/tests/compat-headers/gnustep-pre-2-0/Block_private.h diff --git a/crates/block-sys/compat-headers/objfw/Block.h b/crates/tests/compat-headers/objfw/Block.h similarity index 100% rename from crates/block-sys/compat-headers/objfw/Block.h rename to crates/tests/compat-headers/objfw/Block.h From 056e5788968a41a06a6b05804131cd7ff251f36f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 6 Jan 2024 17:06:18 +0100 Subject: [PATCH 4/4] Use ptr::addr_of! to avoid creating references to block ISAs --- crates/block2/src/concrete_block.rs | 2 +- crates/block2/src/debug.rs | 13 ++++++++----- crates/block2/src/ffi.rs | 8 +++++--- crates/block2/src/global.rs | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/block2/src/concrete_block.rs b/crates/block2/src/concrete_block.rs index d2884e342..87dc7e11f 100644 --- a/crates/block2/src/concrete_block.rs +++ b/crates/block2/src/concrete_block.rs @@ -215,7 +215,7 @@ impl ConcreteBlock { /// correct arguments. unsafe fn with_invoke(invoke: unsafe extern "C" fn(), closure: F) -> Self { let layout = abi::Block_layout { - isa: unsafe { &ffi::_NSConcreteStackBlock }, + isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) }, flags: Self::FLAGS, reserved: 0, invoke: Some(invoke), diff --git a/crates/block2/src/debug.rs b/crates/block2/src/debug.rs index 430a87d98..212dc5c46 100644 --- a/crates/block2/src/debug.rs +++ b/crates/block2/src/debug.rs @@ -11,11 +11,14 @@ struct Isa(*const ffi::Class); impl Isa { fn is_global(self) -> bool { - ptr::eq(unsafe { &ffi::_NSConcreteGlobalBlock }, self.0) + ptr::eq( + unsafe { ptr::addr_of!(ffi::_NSConcreteGlobalBlock) }, + self.0, + ) } fn is_stack(self) -> bool { - ptr::eq(unsafe { &ffi::_NSConcreteStackBlock }, self.0) + ptr::eq(unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) }, self.0) } } @@ -216,13 +219,13 @@ mod tests { #[test] fn test_isa() { - let isa = Isa(unsafe { &ffi::_NSConcreteGlobalBlock }); + let isa = Isa(unsafe { ptr::addr_of!(ffi::_NSConcreteGlobalBlock) }); assert!(isa.is_global()); assert!(!isa.is_stack()); - let isa = Isa(unsafe { &ffi::_NSConcreteStackBlock }); + let isa = Isa(unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) }); assert!(!isa.is_global()); assert!(isa.is_stack()); - let isa = Isa(unsafe { &ffi::private::_NSConcreteMallocBlock }); + let isa = Isa(unsafe { ptr::addr_of!(ffi::private::_NSConcreteMallocBlock) }); assert!(!isa.is_global()); assert!(!isa.is_stack()); let isa = Isa(ptr::null()); diff --git a/crates/block2/src/ffi.rs b/crates/block2/src/ffi.rs index a8776d177..92477a613 100644 --- a/crates/block2/src/ffi.rs +++ b/crates/block2/src/ffi.rs @@ -153,9 +153,11 @@ mod tests { #[test] fn test_linkable() { - println!("{:p}", unsafe { &_NSConcreteGlobalBlock }); - println!("{:p}", unsafe { &_NSConcreteStackBlock }); - println!("{:p}", unsafe { &private::_NSConcreteMallocBlock }); + println!("{:?}", unsafe { ptr::addr_of!(_NSConcreteGlobalBlock) }); + println!("{:?}", unsafe { ptr::addr_of!(_NSConcreteStackBlock) }); + println!("{:?}", unsafe { + ptr::addr_of!(private::_NSConcreteMallocBlock) + }); println!("{:p}", _Block_copy as unsafe extern "C" fn(_) -> _); println!( "{:p}", diff --git a/crates/block2/src/global.rs b/crates/block2/src/global.rs index 47c409b24..adca770d4 100644 --- a/crates/block2/src/global.rs +++ b/crates/block2/src/global.rs @@ -171,7 +171,7 @@ macro_rules! global_block { #[allow(unused_unsafe)] $vis static $name: $crate::GlobalBlock<($($t,)*) $(, $r)?> = unsafe { let mut layout = $crate::GlobalBlock::<($($t,)*) $(, $r)?>::__DEFAULT_LAYOUT; - layout.isa = &$crate::ffi::_NSConcreteGlobalBlock; + layout.isa = ::core::ptr::addr_of!($crate::ffi::_NSConcreteGlobalBlock); layout.invoke = ::core::option::Option::Some({ unsafe extern "C" fn inner(_: *mut $crate::__Block_layout, $($a: $t),*) $(-> $r)? { $body