Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prerequisites for redoing ivars #522

Merged
merged 6 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ jobs:
ASMFLAGS: ${{ matrix.cflags }}
LDFLAGS: ${{ matrix.cflags }}
ARGS: --no-default-features --features=std,${{ matrix.runtime }}
# http://wiki.gnustep.org/index.php/Building_GNUstep_under_Debian_FreeBSD#installing_gnustep-make
RUNTIME_VERSION: gnustep-${{ matrix.libobjc2 }}

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 4 additions & 0 deletions crates/header-translator/src/rust_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ impl AttributeParser<'_, '_> {
}
}

/// We completely ignore `__kindof` in Rust as it is done in Swift, since
/// it only exists to allow legacy Objective-C code to continue compiling.
///
/// See <https://lapcatsoftware.com/articles/kindof.html>
fn is_kindof(&mut self, position: ParsePosition) -> bool {
self.strip("__kindof", position)
}
Expand Down
1 change: 1 addition & 0 deletions crates/objc-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ fn main() {
// The fragile runtime is expected on i686-apple-darwin, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Darwin.h#L228-L231
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Clang.cpp#L3639-L3640
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html
(MacOS(version), "x86") => format!("macosx-fragile-{version}"),
(MacOS(version), _) => format!("macosx-{version}"),
(IOS(version), _) => format!("ios-{version}"),
Expand Down
4 changes: 3 additions & 1 deletion crates/objc-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
//!
//! These bindings contain almost no documentation, so it is highly
//! recommended to read the documentation of the original libraries:
//! - Apple's [official documentation][apple].
//! - Apple's [documentation about the Objective-C runtime][runtime-guide].
//! - Apple's [runtime reference][apple].
//! - Apple's `objc4` [source code][objc4], in particular `runtime.h`.
//! - GNUStep's `libobjc2` [source code][libobjc2], in particular `runtime.h`.
//!
//! See also the [`README.md`](https://crates.io/crates/objc-sys) for more
//! background information, and for how to configure the desired runtime.
//!
//! [runtime-guide]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
//! [apple]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
//! [libobjc2]: https://github.com/gnustep/libobjc2/tree/v2.1/objc
//! [objc4]: https://github.com/apple-oss-distributions/objc4
Expand Down
1 change: 1 addition & 0 deletions crates/objc-sys/src/various.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ extern_c_unwind! {
extern_c! {
#[cfg(any(doc, not(objfw)))]
pub fn imp_getBlock(imp: IMP) -> *mut objc_object;
// See also <https://landonf.org/code/objc/imp_implementationWithBlock.20110413.html>
#[cfg(any(doc, not(objfw)))]
pub fn imp_implementationWithBlock(block: *mut objc_object) -> IMP;
#[cfg(any(doc, not(objfw)))]
Expand Down
1 change: 1 addition & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
`MethodImplementation` to `Return` and `Arguments`.
* **BREAKING**: Make `rc::Allocated` allowed to be `NULL` internally, such
that uses of `Option<Allocated<T>>` is now simply `Allocated<T>`.
* `AnyObject::class` now returns a `'static` reference to the class.

### Deprecated
* Soft deprecated using `msg_send!` without a comma between arguments (i.e.
Expand Down
4 changes: 4 additions & 0 deletions crates/objc2/src/declare/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,10 @@ impl ClassBuilder {

let c_name = CString::new(name).unwrap();
let encoding = CString::new(encoding.to_string()).unwrap();

// Note: The Objective-C runtime contains functionality to do stuff
// with "instance variable layouts", but we don't have to touch any of
// that, it was only used in the garbage-collecting runtime.
let success = Bool::from_raw(unsafe {
ffi::class_addIvar(
self.as_mut_ptr(),
Expand Down
13 changes: 5 additions & 8 deletions crates/objc2/src/macros/extern_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,11 @@ macro_rules! __inner_extern_class {
$(const NAME: &'static str = $name_const:expr;)?
}
) => {
$crate::__emit_struct! {
($(#[$m])*)
($v)
($name<$($t_struct $(: $(?$b_sized_struct)? $($b_struct)? $(= $default)?)?),*>)
(
$superclass_field: $superclass_field_ty,
$($fields)*
)
$(#[$m])*
#[repr(C)]
$v struct $name<$($t_struct $(: $(?$b_sized_struct)? $($b_struct)? $(= $default)?)?),*> {
$superclass_field: $superclass_field_ty,
$($fields)*
}

$crate::__extern_class_impl_traits! {
Expand Down
37 changes: 29 additions & 8 deletions crates/objc2/src/runtime/message_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ macro_rules! conditional_try {
}};
}

// More information on how objc_msgSend works:
// <https://web.archive.org/web/20200118080513/http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/>
// <https://www.mikeash.com/pyblog/objc_msgsends-new-prototype.html>
// <https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html>
#[cfg(feature = "apple")]
mod msg_send_primitive {
#[allow(unused_imports)]
Expand Down Expand Up @@ -154,6 +158,15 @@ mod msg_send_primitive {
args: A,
) -> R {
let msg_send_fn = R::MSG_SEND;
// Note: Modern Objective-C compilers have a workaround to ensure that
// messages to `nil` with a struct return produces `mem::zeroed()`,
// see:
// <https://www.sealiesoftware.com/blog/archive/2012/2/29/objc_explain_return_value_of_message_to_nil.html>
//
// We _could_ technically do something similar, but since we're
// disallowing messages to `nil` with `debug_assertions` enabled
// anyhow, and since Rust has a much stronger type-system that
// disallows NULL/nil in most cases, we won't bother supporting it.
unsafe { A::__invoke(msg_send_fn, receiver, sel, args) }
}

Expand Down Expand Up @@ -206,13 +219,21 @@ mod msg_send_primitive {
sel: Sel,
args: A,
) -> R {
// If `receiver` is NULL, objc_msg_lookup will return a standard C-method
// taking two arguments, the receiver and the selector. Transmuting and
// calling such a function with multiple parameters is UB, so instead we
// return NULL directly.
// If `receiver` is NULL, objc_msg_lookup will return a standard
// C-method taking two arguments, the receiver and the selector.
//
// Transmuting and calling such a function with multiple parameters is
// safe as long as the return value is a primitive (and e.g. not a big
// struct or array).
//
// However, when the return value is a floating point value, the float
// will end up as some undefined value, usually NaN, which is
// incompatible with Apple's platforms. As such, we insert this extra
// NULL check here.
if receiver.is_null() {
// SAFETY: Caller guarantees that messages to NULL-receivers only
// return pointers, and a mem::zeroed pointer is just a NULL-pointer.
// return pointers or primitive values, and a mem::zeroed pointer
// / primitive is just a NULL-pointer or a zeroed primitive.
return unsafe { mem::zeroed() };
}

Expand Down Expand Up @@ -323,13 +344,13 @@ pub unsafe trait MessageReceiver: private::Sealed + Sized {
/// Sends a message to the receiver with the given selector and arguments.
///
/// The correct version of `objc_msgSend` will be chosen based on the
/// return type. For more information, see the section on "Sending
/// Messages" in Apple's [documentation][runtime].
/// return type. For more information, see [the Messaging section in
/// Apple's Objective-C Runtime Programming Guide][guide-messaging].
///
/// If the selector is known at compile-time, it is recommended to use the
/// [`msg_send!`] macro rather than this method.
///
/// [runtime]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
/// [guide-messaging]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html
///
///
/// # Safety
Expand Down
51 changes: 35 additions & 16 deletions crates/objc2/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ impl UnwindSafe for Ivar {}
impl RefUnwindSafe for Ivar {}

impl Ivar {
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::objc_ivar {
let ptr: *const Self = self;
ptr.cast()
Expand Down Expand Up @@ -381,18 +382,21 @@ impl UnwindSafe for Method {}
impl RefUnwindSafe for Method {}

impl Method {
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::objc_method {
let ptr: *const Self = self;
ptr.cast()
}

// Note: We don't take `&mut` here, since the operations on methods work
// atomically.
#[inline]
pub(crate) fn as_mut_ptr(&self) -> *mut ffi::objc_method {
self.as_ptr() as _
}

/// Returns the name of self.
#[inline]
#[doc(alias = "method_getName")]
pub fn name(&self) -> Sel {
unsafe { Sel::from_ptr(ffi::method_getName(self.as_ptr())).unwrap() }
Expand Down Expand Up @@ -446,6 +450,7 @@ impl Method {
}

/// Returns the number of arguments accepted by self.
#[inline]
#[doc(alias = "method_getNumberOfArguments")]
pub fn arguments_count(&self) -> usize {
unsafe { ffi::method_getNumberOfArguments(self.as_ptr()) as usize }
Expand Down Expand Up @@ -483,7 +488,6 @@ impl Method {
///
/// A common mistake would be expecting e.g. a pointer to not be null,
/// where the null case was handled before.
#[inline]
#[doc(alias = "method_setImplementation")]
pub unsafe fn set_implementation(&self, imp: Imp) -> Imp {
// SAFETY: The new impl is not NULL, and the rest is upheld by the
Expand Down Expand Up @@ -574,6 +578,7 @@ impl RefUnwindSafe for AnyClass {}
// Note that Unpin is not applicable.

impl AnyClass {
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::objc_class {
let ptr: *const Self = self;
ptr.cast()
Expand Down Expand Up @@ -603,6 +608,7 @@ impl AnyClass {
}

/// Returns the total number of registered classes.
#[inline]
#[doc(alias = "objc_getClassList")]
pub fn classes_count() -> usize {
unsafe { ffi::objc_getClassList(ptr::null_mut(), 0) as usize }
Expand Down Expand Up @@ -713,17 +719,6 @@ impl AnyClass {
}
}

#[allow(unused)]
#[doc(alias = "class_getIvarLayout")]
fn instance_variable_layout(&self) -> Option<&[u8]> {
let layout: *const c_char = unsafe { ffi::class_getIvarLayout(self.as_ptr()).cast() };
if layout.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(layout) }.to_bytes())
}
}

#[allow(unused)]
#[doc(alias = "class_getClassVariable")]
fn class_variable(&self, name: &str) -> Option<&Ivar> {
Expand All @@ -745,6 +740,7 @@ impl AnyClass {
}

/// Checks whether this class conforms to the specified protocol.
#[inline]
#[doc(alias = "class_conformsToProtocol")]
pub fn conforms_to(&self, proto: &AnyProtocol) -> bool {
unsafe {
Expand Down Expand Up @@ -798,7 +794,6 @@ impl AnyClass {
// fn properties(&self) -> Malloc<[&Property]>;
// unsafe fn replace_method(&self, name: Sel, imp: Imp, types: &str) -> Imp;
// unsafe fn replace_property(&self, name: &str, attributes: &[ffi::objc_property_attribute_t]);
// unsafe fn set_ivar_layout(&mut self, layout: &[u8]);
// fn method_imp(&self, name: Sel) -> Imp; // + _stret

// fn get_version(&self) -> u32;
Expand Down Expand Up @@ -873,6 +868,7 @@ impl RefUnwindSafe for AnyProtocol {}
// Note that Unpin is not applicable.

impl AnyProtocol {
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::objc_protocol {
let ptr: *const Self = self;
ptr.cast()
Expand Down Expand Up @@ -913,6 +909,7 @@ impl AnyProtocol {
}

/// Checks whether this protocol conforms to the specified protocol.
#[inline]
#[doc(alias = "protocol_conformsToProtocol")]
pub fn conforms_to(&self, proto: &AnyProtocol) -> bool {
unsafe {
Expand Down Expand Up @@ -1057,16 +1054,20 @@ unsafe impl RefEncode for AnyObject {
unsafe impl Message for AnyObject {}

impl AnyObject {
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::objc_object {
let ptr: *const Self = self;
ptr.cast()
}

/// Dynamically find the class of this object.
#[inline]
#[doc(alias = "object_getClass")]
pub fn class(&self) -> &AnyClass {
pub fn class(&self) -> &'static AnyClass {
let ptr: *const AnyClass = unsafe { ffi::object_getClass(self.as_ptr()) }.cast();
// SAFETY: The class is not NULL because the object is not NULL.
// SAFETY: The class is not NULL because the object is not NULL, and
// it is safe as `'static` since classes are static, and it could be
// retrieved via. `AnyClass::get(self.class().name())` anyhow.
unsafe { ptr.as_ref().unwrap_unchecked() }
}

Expand Down Expand Up @@ -1285,7 +1286,7 @@ mod tests {
use super::*;
use crate::runtime::MessageReceiver;
use crate::test_utils;
use crate::{msg_send, sel};
use crate::{class, msg_send, sel};

#[test]
fn test_selector() {
Expand Down Expand Up @@ -1576,4 +1577,22 @@ mod tests {
assert_eq!(size_of::<Ivar>(), 0);
assert_eq!(size_of::<Method>(), 0);
}

fn get_ivar_layout(cls: &AnyClass) -> *const u8 {
let cls: *const AnyClass = cls;
unsafe { ffi::class_getIvarLayout(cls.cast()) }
}

#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "ivar layout is still used on GNUStep"
)]
fn test_layout_does_not_matter_any_longer() {
assert!(get_ivar_layout(class!(NSObject)).is_null());
assert!(get_ivar_layout(class!(NSArray)).is_null());
assert!(get_ivar_layout(class!(NSException)).is_null());
assert!(get_ivar_layout(class!(NSNumber)).is_null());
assert!(get_ivar_layout(class!(NSString)).is_null());
}
}
35 changes: 15 additions & 20 deletions crates/objc2/src/runtime/nsobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,21 @@ use crate::runtime::{AnyClass, AnyObject, ProtocolObject};
use crate::{extern_methods, msg_send, msg_send_id, Message};
use crate::{ClassType, ProtocolType};

crate::__emit_struct! {
(
/// The root class of most Objective-C class hierarchies.
///
/// This represents the [`NSObject` class][cls]. The name "NSObject" also
/// refers to a protocol, see [`NSObjectProtocol`] for that.
///
/// Since this class is only available with the `Foundation` framework,
/// `objc2` links to it for you.
///
/// This is exported under `icrate::Foundation::NSObject`, you probably
/// want to use that path instead.
///
/// [cls]: https://developer.apple.com/documentation/objectivec/nsobject?language=objc
)
(pub)
(NSObject)
(
__inner: AnyObject,
)
/// The root class of most Objective-C class hierarchies.
///
/// This represents the [`NSObject` class][cls]. The name "NSObject" also
/// refers to a protocol, see [`NSObjectProtocol`] for that.
///
/// Since this class is only available with the `Foundation` framework,
/// `objc2` links to it for you.
///
/// This is exported under `icrate::Foundation::NSObject`, you probably
/// want to use that path instead.
///
/// [cls]: https://developer.apple.com/documentation/objectivec/nsobject?language=objc
#[repr(C)]
pub struct NSObject {
__inner: AnyObject,
}

crate::__extern_class_impl_traits! {
Expand Down
Loading