diff --git a/.github/workflows/gnustep.yml b/.github/workflows/gnustep.yml index 68f8aabde..6c40f3f14 100644 --- a/.github/workflows/gnustep.yml +++ b/.github/workflows/gnustep.yml @@ -101,18 +101,17 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --verbose --no-default-features + args: --verbose --no-default-features --features gnustep-1-9 - - name: Test GNUStep + - name: Test without features uses: actions-rs/cargo@v1 with: command: test - # Temporary fix - args: --verbose --no-fail-fast --no-default-features --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation + args: --verbose --no-fail-fast --no-default-features --features gnustep-1-9 - - name: Test GNUStep with features + - name: Test with features uses: actions-rs/cargo@v1 with: command: test - # Temporary fix - args: --verbose --no-fail-fast --no-default-features --features exception,verify_message --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation + # Not using --all-features because some features are nightly-only + args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message diff --git a/Cargo.toml b/Cargo.toml index cdd936995..e5e47d14f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "objc2", "objc2_block", + "objc2_block_sys", "objc2_encode", "objc2_exception", "objc2_foundation", diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs index e97e6b207..50352b4aa 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -75,10 +75,6 @@ extern crate std; #[doc = include_str!("../README.md")] extern "C" {} -#[cfg(doctest)] -#[doc = include_str!("../../README.md")] -extern "C" {} - pub use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode}; pub use crate::message::{Message, MessageArguments, MessageError, MessageReceiver}; diff --git a/objc2_block/Cargo.toml b/objc2_block/Cargo.toml index 046b02fac..9f4ad1cb3 100644 --- a/objc2_block/Cargo.toml +++ b/objc2_block/Cargo.toml @@ -23,6 +23,7 @@ exclude = [ [dependencies] objc2_encode = { path = "../objc2_encode" } +objc2_block_sys = { path = "../objc2_block_sys" } [dev-dependencies] objc2_test_utils = { path = "../objc2_test_utils" } diff --git a/objc2_block/src/lib.rs b/objc2_block/src/lib.rs index e8178cef4..1c6ea8c97 100644 --- a/objc2_block/src/lib.rs +++ b/objc2_block/src/lib.rs @@ -55,7 +55,6 @@ extern crate std; #[cfg(test)] mod test_utils; -use alloc::boxed::Box; use core::ffi::c_void; use core::marker::PhantomData; use core::mem; @@ -63,26 +62,9 @@ use core::ops::{Deref, DerefMut}; use core::ptr; use std::os::raw::{c_int, c_ulong}; +pub use objc2_block_sys as ffi; use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode}; -// TODO: Replace with `objc2::Class` -#[repr(C)] -struct ClassInternal { - _priv: [u8; 0], -} - -#[cfg_attr(target_vendor = "apple", link(name = "System", kind = "dylib"))] -#[cfg_attr( - not(target_vendor = "apple"), - 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. @@ -151,7 +133,7 @@ block_args_impl!( #[repr(C)] struct BlockBase { - isa: *const ClassInternal, + isa: *const ffi::Class, flags: c_int, _reserved: c_int, invoke: unsafe extern "C" fn(*mut Block, ...) -> R, @@ -207,7 +189,7 @@ impl RcBlock { /// /// 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; + let ptr = ffi::_Block_copy(ptr as *const c_void) as *mut Block; RcBlock { ptr } } } @@ -229,7 +211,7 @@ impl Deref for RcBlock { impl Drop for RcBlock { fn drop(&mut self) { unsafe { - _Block_release(self.ptr as *const c_void); + ffi::_Block_release(self.ptr as *const c_void); } } } @@ -366,7 +348,7 @@ concrete_block_impl!( #[repr(C)] pub struct ConcreteBlock { base: BlockBase, - descriptor: Box>>, + descriptor: *const BlockDescriptor>, closure: F, } @@ -391,19 +373,25 @@ where } impl ConcreteBlock { + const DESCRIPTOR: BlockDescriptor = BlockDescriptor { + _reserved: 0, + block_size: mem::size_of::() as c_ulong, + copy_helper: block_context_copy::, + dispose_helper: block_context_dispose::, + }; + /// 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, + isa: &ffi::_NSConcreteStackBlock, + flags: ffi::BLOCK_HAS_COPY_DISPOSE, _reserved: 0, invoke: mem::transmute(invoke), }, - descriptor: Box::new(BlockDescriptor::new()), + descriptor: &Self::DESCRIPTOR, closure, } } @@ -469,17 +457,6 @@ struct BlockDescriptor { 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}; diff --git a/objc2_block_sys/Cargo.toml b/objc2_block_sys/Cargo.toml new file mode 100644 index 000000000..691e5b8ec --- /dev/null +++ b/objc2_block_sys/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "objc2_block_sys" +version = "0.0.0" # Remember to update html_root_url in lib.rs +authors = ["Mads Marquart "] +edition = "2018" + +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/objc2_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" +build = "build.rs" + +[features] +# Link to Apple's libclosure (exists in libSystem) +# +# This is the default on Apple platforms +apple = [] + +# Link to libBlocksRuntime from compiler-rt +# +# This is the default on non-Apple platforms +compiler-rt = [] + +# Link to GNUStep's libobjc2 (which contains the block implementation) +gnustep-1-7 = ["objc2_sys"] +gnustep-1-8 = ["gnustep-1-7"] +gnustep-1-9 = ["gnustep-1-8"] +gnustep-2-0 = ["gnustep-1-9"] +gnustep-2-1 = ["gnustep-2-0"] + +# Link to Microsoft's libobjc2 +winobjc = ["gnustep-1-8"] + +# TODO +objfw = [] + +[dependencies] +objc2_sys = { path = "../objc2_sys", optional = true } diff --git a/objc2_block_sys/README.md b/objc2_block_sys/README.md new file mode 100644 index 000000000..3bddf8487 --- /dev/null +++ b/objc2_block_sys/README.md @@ -0,0 +1,111 @@ +# `objc2_block_sys` + +[![Latest version](https://badgen.net/crates/v/objc2_block_sys)](https://crates.io/crates/objc2_block_sys) +[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt) +[![Documentation](https://docs.rs/objc2_block_sys/badge.svg)](https://docs.rs/objc2_block_sys/) +[![Apple CI](https://github.com/madsmtm/objc2/actions/workflows/apple.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/apple.yml) +[![GNUStep CI](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml) + +Raw Rust bindings to Apple's C language extension of blocks + +## Runtime Support + +This library is basically just a raw interface to the aptly specified [Blocks +ABI](https://clang.llvm.org/docs/Block-ABI-Apple.html). 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. + +The user can choose the desired runtime by using the relevant cargo feature +flags, see the following sections: + + +### Apple's [`libclosure`](https://opensource.apple.com/source/libclosure/) + +- Feature flag: `apple`. + +This is naturally the most sophisticated runtime, and it has quite a lot more +features than the specification mandates. This is used by default on Apple +platforms when no feature flags are specified. + +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. + +A git mirror of the sources is available [here](https://github.com/madsmtm/libclosure). + + +### 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 the default runtime on all non-Apple platforms when no feature flags +are specified. + +This is effectively just 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. + +This can be easily used on many Linux systems with the `libblocksruntime-dev` +package. + +[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_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: `winobjc`. + +Essentially just [a fork](https://github.com/microsoft/libobjc2) based on +GNUStep's `libobjc2` version 1.8. + + +### [`ObjFW`](https://github.com/ObjFW/ObjFW) (WIP) + +- Feature flag: `objfw`. + +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_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_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/objc2_block_sys/build.rs b/objc2_block_sys/build.rs new file mode 100644 index 000000000..50d3cae76 --- /dev/null +++ b/objc2_block_sys/build.rs @@ -0,0 +1,63 @@ +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 mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some(); + let mut compiler_rt = env::var_os("CARGO_FEATURE_COMPILER_RT").is_some(); + let gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some(); + let objfw = env::var_os("CARGO_FEATURE_OBJFW").is_some(); + + if let (false, false, false, false) = (apple, compiler_rt, gnustep, objfw) { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + if let "macos" | "ios" | "tvos" | "watchos" = &*target_os { + apple = true; + // Add cheaty #[cfg(feature = "apple")] directive + println!("cargo:rustc-cfg=feature=\"apple\""); + } else { + compiler_rt = true; + // Add cheaty #[cfg(feature = "compiler-rt")] directive + println!("cargo:rustc-cfg=feature=\"compiler-rt\""); + } + } + + 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 that System.framework is just a deprecated wrapper over the + // dynamic library. + 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; objc2_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_some() { + let compat_headers = + Path::new(env!("CARGO_MANIFEST_DIR")).join("gnustep-compat-headers"); + cc_args.push_str(" -I"); + cc_args.push_str(compat_headers.to_str().unwrap()); + } + } + (false, false, false, true) => unimplemented!(), + // Checked in if-let above + (false, false, false, false) => unreachable!(), + (_, _, _, _) => panic!("Invalid feature combination; only one runtime may be selected!"), + } + + // Add DEP_BLOCK_CC_ARGS + println!("cargo:cc_args={}", cc_args); +} diff --git a/objc2_block_sys/gnustep-compat-headers/Block.h b/objc2_block_sys/gnustep-compat-headers/Block.h new file mode 100644 index 000000000..253e36595 --- /dev/null +++ b/objc2_block_sys/gnustep-compat-headers/Block.h @@ -0,0 +1 @@ +#include diff --git a/objc2_block_sys/gnustep-compat-headers/Block_private.h b/objc2_block_sys/gnustep-compat-headers/Block_private.h new file mode 100644 index 000000000..c565ce718 --- /dev/null +++ b/objc2_block_sys/gnustep-compat-headers/Block_private.h @@ -0,0 +1 @@ +#include diff --git a/objc2_block_sys/src/lib.rs b/objc2_block_sys/src/lib.rs new file mode 100644 index 000000000..3281071e0 --- /dev/null +++ b/objc2_block_sys/src/lib.rs @@ -0,0 +1,389 @@ +//! # 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` for more info. +//! +//! [ABI]: https://clang.llvm.org/docs/Block-ABI-Apple.html + +// Update in Cargo.toml as well. +#![doc(html_root_url = "https://docs.rs/objc2_block_sys/0.0.0")] + +// Ensure linkage actually happens +#[cfg(feature = "gnustep-1-7")] +extern crate objc2_sys; + +#[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 objc2_sys::OpaqueData + _opaque: PhantomData<(UnsafeCell<*const ()>, PhantomPinned)>, +} + +/// Block descriptor flags. +/// Values for Block_layout->flags to describe block objects +#[allow(non_camel_case_types)] +pub type block_flags = i32; + +#[cfg(feature = "apple")] +pub const BLOCK_DEALLOCATING: block_flags = 0x0001; + +/// 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. +#[cfg(feature = "gnustep-1-7")] +pub const BLOCK_REFCOUNT_MASK: block_flags = 0x00ffffff; +#[cfg(any(feature = "compiler-rt", feature = "objfw"))] +pub const BLOCK_REFCOUNT_MASK: block_flags = 0xffff; +#[cfg(feature = "apple")] +/// runtime +pub const BLOCK_REFCOUNT_MASK: block_flags = 0xfffe; + +#[cfg(feature = "apple")] +/// compiler +pub const BLOCK_INLINE_LAYOUT_STRING: block_flags = 1 << 21; + +#[cfg(feature = "apple")] +/// compiler +pub const BLOCK_SMALL_DESCRIPTOR: block_flags = 1 << 22; + +#[cfg(feature = "apple")] // Part of ABI? +/// compiler +pub const BLOCK_IS_NOESCAPE: block_flags = 1 << 23; + +#[cfg(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(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(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(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; + + 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(feature = "apple")] +extern "C" { + // Whether the return value of the block is on the stack. + // macOS 10.7 + // 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 + // 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 + // 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 + // pub fn _Block_tryRetain(block: *const c_void) -> bool; + + // Callable only from the ARR weak subsystem while in exclusion zone + // macOS 10.7 + // pub fn _Block_isDeallocating(block: *const c_void) -> bool; +} + +#[cfg(any(feature = "apple", feature = "compiler-rt"))] +extern "C" { + // the raw data space for runtime classes for blocks + // class+meta used for stack, malloc, and collectable based blocks + + pub static _NSConcreteAutoBlock: Class; + pub static _NSConcreteFinalizingBlock: Class; + pub static _NSConcreteWeakBlockVariable: Class; + + pub fn Block_size(block: *mut c_void) -> c_ulong; // usize +} + +#[cfg(any(feature = "apple", feature = "gnustep-1-7"))] +extern "C" { + // indicates whether block was compiled with compiler that sets the ABI + // related metadata bits + // macOS 10.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 + // 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: *mut 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: + /// ```ignore + /// 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 it is safe to access this through just `Block_descriptor_header`. + pub descriptor: *mut Block_descriptor_header, +} + +#[repr(C)] +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)] +#[cfg(not(feature = "objfw"))] +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 inner: Block_descriptor, + + /// Objective-C type encoding of the block. + #[doc(alias = "signature")] + pub encoding: *const c_char, +} + +// #[cfg(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://opensource.apple.com/source/libdispatch/libdispatch-84.5/src/once.c.auto.html + +/// 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: *mut c_void, + /// 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(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 inner: Block_byref, + pub layout: *const c_char, +} diff --git a/objc2_foundation/Cargo.toml b/objc2_foundation/Cargo.toml index 746a45933..8be17605f 100644 --- a/objc2_foundation/Cargo.toml +++ b/objc2_foundation/Cargo.toml @@ -16,6 +16,8 @@ repository = "https://github.com/madsmtm/objc2" documentation = "https://docs.rs/objc2_foundation/" license = "MIT" +build = "build.rs" + [features] default = ["block"] # Provided as a way to cut down on dependencies @@ -24,3 +26,4 @@ block = ["objc2_block"] [dependencies] objc2_block = { path = "../objc2_block", optional = true } objc2 = { path = "../objc2" } +objc2_sys = { path = "../objc2_sys" } diff --git a/objc2_foundation/build.rs b/objc2_foundation/build.rs new file mode 100644 index 000000000..70fdc688a --- /dev/null +++ b/objc2_foundation/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + // The script doesn't depend on our code + println!("cargo:rerun-if-changed=build.rs"); + + let runtime = env::var("DEP_OBJC_RUNTIME").unwrap(); + println!("cargo:rustc-cfg={}", runtime); +} diff --git a/objc2_foundation/src/data.rs b/objc2_foundation/src/data.rs index 33cb925e6..e9d417b3b 100644 --- a/objc2_foundation/src/data.rs +++ b/objc2_foundation/src/data.rs @@ -7,8 +7,6 @@ use core::{ffi::c_void, ptr::NonNull}; use super::{INSCopying, INSMutableCopying, INSObject, NSRange}; use objc2::msg_send; use objc2::rc::{Id, Owned}; -#[cfg(feature = "block")] -use objc2_block::{Block, ConcreteBlock}; pub trait INSData: INSObject { fn len(&self) -> usize { @@ -22,12 +20,11 @@ pub trait INSData: INSObject { fn bytes(&self) -> &[u8] { let ptr: *const c_void = unsafe { msg_send![self, bytes] }; // The bytes pointer may be null for length zero - let (ptr, len) = if ptr.is_null() { - (0x1 as *const u8, 0) + if ptr.is_null() { + &[] } else { - (ptr as *const u8, self.len()) - }; - unsafe { slice::from_raw_parts(ptr, len) } + unsafe { slice::from_raw_parts(ptr as *const u8, self.len()) } + } } fn with_bytes(bytes: &[u8]) -> Id { @@ -46,6 +43,8 @@ pub trait INSData: INSObject { #[cfg(feature = "block")] fn from_vec(bytes: Vec) -> Id { + use objc2_block::{Block, ConcreteBlock}; + let capacity = bytes.capacity(); let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe { // Recreate the Vec and let it drop @@ -56,6 +55,22 @@ pub trait INSData: INSObject { let mut bytes = bytes; let bytes_ptr = bytes.as_mut_ptr() as *mut c_void; + + // GNUStep's NSData `initWithBytesNoCopy:length:deallocator:` has a + // bug; it forgets to assign the input buffer and length to the + // instance before it swizzles to NSDataWithDeallocatorBlock. + // See https://github.com/gnustep/libs-base/pull/213 + // So we just use NSDataWithDeallocatorBlock directly. + #[cfg(gnustep)] + let cls = { + let cls = Self::class(); + if cls == objc2::class!(NSData) { + objc2::class!(NSDataWithDeallocatorBlock) + } else { + cls + } + }; + #[cfg(not(gnustep))] let cls = Self::class(); unsafe { let obj: *mut Self = msg_send![cls, alloc]; @@ -216,6 +231,6 @@ mod tests { let bytes_ptr = bytes.as_ptr(); let data = NSData::from_vec(bytes); - assert!(data.bytes().as_ptr() == bytes_ptr); + assert_eq!(data.bytes().as_ptr(), bytes_ptr); } } diff --git a/objc2_test_utils/Cargo.toml b/objc2_test_utils/Cargo.toml index aea262997..77869e511 100644 --- a/objc2_test_utils/Cargo.toml +++ b/objc2_test_utils/Cargo.toml @@ -18,5 +18,8 @@ license = "MIT" build = "build.rs" +[dependencies] +objc2_block_sys = { path = "../objc2_block_sys" } + [build-dependencies] cc = "1.0" diff --git a/objc2_test_utils/build.rs b/objc2_test_utils/build.rs index 1da9e08c1..b5711d7c0 100644 --- a/objc2_test_utils/build.rs +++ b/objc2_test_utils/build.rs @@ -1,9 +1,15 @@ -use cc; +use std::env; fn main() { - cc::Build::new() - .compiler("clang") - .file("extern/block_utils.c") - .flag("-fblocks") - .compile("libblock_utils.a"); + println!("cargo:rerun-if-changed=extern/block_utils.c"); + + let mut builder = cc::Build::new(); + builder.compiler("clang"); + builder.file("extern/block_utils.c"); + + for flag in env::var("DEP_BLOCK_CC_ARGS").unwrap().split(' ') { + builder.flag(flag); + } + + builder.compile("libblock_utils.a"); } diff --git a/objc2_test_utils/src/lib.rs b/objc2_test_utils/src/lib.rs index d1b9efa8a..2d00e65cb 100644 --- a/objc2_test_utils/src/lib.rs +++ b/objc2_test_utils/src/lib.rs @@ -1,5 +1,8 @@ #![no_std] +// Ensure linkage actually happens +extern crate objc2_block_sys; + /// A block that takes no arguments and returns an integer: `int32_t (^)()`. #[repr(C)] pub struct IntBlock {