Skip to content

Commit 53cbfc6

Browse files
authored
Merge pull request #522 from madsmtm/redo-ivars-partial
Prerequisites for redoing ivars
2 parents 045c53f + 44e8bf8 commit 53cbfc6

28 files changed

+210
-168
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ jobs:
584584
ASMFLAGS: ${{ matrix.cflags }}
585585
LDFLAGS: ${{ matrix.cflags }}
586586
ARGS: --no-default-features --features=std,${{ matrix.runtime }}
587+
# http://wiki.gnustep.org/index.php/Building_GNUstep_under_Debian_FreeBSD#installing_gnustep-make
588+
RUNTIME_VERSION: gnustep-${{ matrix.libobjc2 }}
587589

588590
steps:
589591
- uses: actions/checkout@v3

crates/header-translator/src/rust_type.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ impl AttributeParser<'_, '_> {
122122
}
123123
}
124124

125+
/// We completely ignore `__kindof` in Rust as it is done in Swift, since
126+
/// it only exists to allow legacy Objective-C code to continue compiling.
127+
///
128+
/// See <https://lapcatsoftware.com/articles/kindof.html>
125129
fn is_kindof(&mut self, position: ParsePosition) -> bool {
126130
self.strip("__kindof", position)
127131
}

crates/objc-sys/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ fn main() {
171171
// The fragile runtime is expected on i686-apple-darwin, see:
172172
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Darwin.h#L228-L231
173173
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/ToolChains/Clang.cpp#L3639-L3640
174+
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html
174175
(MacOS(version), "x86") => format!("macosx-fragile-{version}"),
175176
(MacOS(version), _) => format!("macosx-{version}"),
176177
(IOS(version), _) => format!("ios-{version}"),

crates/objc-sys/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
//!
33
//! These bindings contain almost no documentation, so it is highly
44
//! recommended to read the documentation of the original libraries:
5-
//! - Apple's [official documentation][apple].
5+
//! - Apple's [documentation about the Objective-C runtime][runtime-guide].
6+
//! - Apple's [runtime reference][apple].
67
//! - Apple's `objc4` [source code][objc4], in particular `runtime.h`.
78
//! - GNUStep's `libobjc2` [source code][libobjc2], in particular `runtime.h`.
89
//!
910
//! See also the [`README.md`](https://crates.io/crates/objc-sys) for more
1011
//! background information, and for how to configure the desired runtime.
1112
//!
13+
//! [runtime-guide]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
1214
//! [apple]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
1315
//! [libobjc2]: https://github.com/gnustep/libobjc2/tree/v2.1/objc
1416
//! [objc4]: https://github.com/apple-oss-distributions/objc4

crates/objc-sys/src/various.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ extern_c_unwind! {
4545
extern_c! {
4646
#[cfg(any(doc, not(objfw)))]
4747
pub fn imp_getBlock(imp: IMP) -> *mut objc_object;
48+
// See also <https://landonf.org/code/objc/imp_implementationWithBlock.20110413.html>
4849
#[cfg(any(doc, not(objfw)))]
4950
pub fn imp_implementationWithBlock(block: *mut objc_object) -> IMP;
5051
#[cfg(any(doc, not(objfw)))]

crates/objc2/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5050
`MethodImplementation` to `Return` and `Arguments`.
5151
* **BREAKING**: Make `rc::Allocated` allowed to be `NULL` internally, such
5252
that uses of `Option<Allocated<T>>` is now simply `Allocated<T>`.
53+
* `AnyObject::class` now returns a `'static` reference to the class.
5354

5455
### Deprecated
5556
* Soft deprecated using `msg_send!` without a comma between arguments (i.e.

crates/objc2/src/declare/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ impl ClassBuilder {
419419

420420
let c_name = CString::new(name).unwrap();
421421
let encoding = CString::new(encoding.to_string()).unwrap();
422+
423+
// Note: The Objective-C runtime contains functionality to do stuff
424+
// with "instance variable layouts", but we don't have to touch any of
425+
// that, it was only used in the garbage-collecting runtime.
422426
let success = Bool::from_raw(unsafe {
423427
ffi::class_addIvar(
424428
self.as_mut_ptr(),

crates/objc2/src/macros/extern_class.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,11 @@ macro_rules! __inner_extern_class {
338338
$(const NAME: &'static str = $name_const:expr;)?
339339
}
340340
) => {
341-
$crate::__emit_struct! {
342-
($(#[$m])*)
343-
($v)
344-
($name<$($t_struct $(: $(?$b_sized_struct)? $($b_struct)? $(= $default)?)?),*>)
345-
(
346-
$superclass_field: $superclass_field_ty,
347-
$($fields)*
348-
)
341+
$(#[$m])*
342+
#[repr(C)]
343+
$v struct $name<$($t_struct $(: $(?$b_sized_struct)? $($b_struct)? $(= $default)?)?),*> {
344+
$superclass_field: $superclass_field_ty,
345+
$($fields)*
349346
}
350347

351348
$crate::__extern_class_impl_traits! {

crates/objc2/src/runtime/message_receiver.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ macro_rules! conditional_try {
3535
}};
3636
}
3737

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

@@ -206,13 +219,21 @@ mod msg_send_primitive {
206219
sel: Sel,
207220
args: A,
208221
) -> R {
209-
// If `receiver` is NULL, objc_msg_lookup will return a standard C-method
210-
// taking two arguments, the receiver and the selector. Transmuting and
211-
// calling such a function with multiple parameters is UB, so instead we
212-
// return NULL directly.
222+
// If `receiver` is NULL, objc_msg_lookup will return a standard
223+
// C-method taking two arguments, the receiver and the selector.
224+
//
225+
// Transmuting and calling such a function with multiple parameters is
226+
// safe as long as the return value is a primitive (and e.g. not a big
227+
// struct or array).
228+
//
229+
// However, when the return value is a floating point value, the float
230+
// will end up as some undefined value, usually NaN, which is
231+
// incompatible with Apple's platforms. As such, we insert this extra
232+
// NULL check here.
213233
if receiver.is_null() {
214234
// SAFETY: Caller guarantees that messages to NULL-receivers only
215-
// return pointers, and a mem::zeroed pointer is just a NULL-pointer.
235+
// return pointers or primitive values, and a mem::zeroed pointer
236+
// / primitive is just a NULL-pointer or a zeroed primitive.
216237
return unsafe { mem::zeroed() };
217238
}
218239

@@ -323,13 +344,13 @@ pub unsafe trait MessageReceiver: private::Sealed + Sized {
323344
/// Sends a message to the receiver with the given selector and arguments.
324345
///
325346
/// The correct version of `objc_msgSend` will be chosen based on the
326-
/// return type. For more information, see the section on "Sending
327-
/// Messages" in Apple's [documentation][runtime].
347+
/// return type. For more information, see [the Messaging section in
348+
/// Apple's Objective-C Runtime Programming Guide][guide-messaging].
328349
///
329350
/// If the selector is known at compile-time, it is recommended to use the
330351
/// [`msg_send!`] macro rather than this method.
331352
///
332-
/// [runtime]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
353+
/// [guide-messaging]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html
333354
///
334355
///
335356
/// # Safety

crates/objc2/src/runtime/mod.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ impl UnwindSafe for Ivar {}
300300
impl RefUnwindSafe for Ivar {}
301301

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

383384
impl Method {
385+
#[inline]
384386
pub(crate) fn as_ptr(&self) -> *const ffi::objc_method {
385387
let ptr: *const Self = self;
386388
ptr.cast()
387389
}
388390

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

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

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

576580
impl AnyClass {
581+
#[inline]
577582
pub(crate) fn as_ptr(&self) -> *const ffi::objc_class {
578583
let ptr: *const Self = self;
579584
ptr.cast()
@@ -603,6 +608,7 @@ impl AnyClass {
603608
}
604609

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

716-
#[allow(unused)]
717-
#[doc(alias = "class_getIvarLayout")]
718-
fn instance_variable_layout(&self) -> Option<&[u8]> {
719-
let layout: *const c_char = unsafe { ffi::class_getIvarLayout(self.as_ptr()).cast() };
720-
if layout.is_null() {
721-
None
722-
} else {
723-
Some(unsafe { CStr::from_ptr(layout) }.to_bytes())
724-
}
725-
}
726-
727722
#[allow(unused)]
728723
#[doc(alias = "class_getClassVariable")]
729724
fn class_variable(&self, name: &str) -> Option<&Ivar> {
@@ -745,6 +740,7 @@ impl AnyClass {
745740
}
746741

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

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

875870
impl AnyProtocol {
871+
#[inline]
876872
pub(crate) fn as_ptr(&self) -> *const ffi::objc_protocol {
877873
let ptr: *const Self = self;
878874
ptr.cast()
@@ -913,6 +909,7 @@ impl AnyProtocol {
913909
}
914910

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

10591056
impl AnyObject {
1057+
#[inline]
10601058
pub(crate) fn as_ptr(&self) -> *const ffi::objc_object {
10611059
let ptr: *const Self = self;
10621060
ptr.cast()
10631061
}
10641062

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

@@ -1285,7 +1286,7 @@ mod tests {
12851286
use super::*;
12861287
use crate::runtime::MessageReceiver;
12871288
use crate::test_utils;
1288-
use crate::{msg_send, sel};
1289+
use crate::{class, msg_send, sel};
12891290

12901291
#[test]
12911292
fn test_selector() {
@@ -1576,4 +1577,22 @@ mod tests {
15761577
assert_eq!(size_of::<Ivar>(), 0);
15771578
assert_eq!(size_of::<Method>(), 0);
15781579
}
1580+
1581+
fn get_ivar_layout(cls: &AnyClass) -> *const u8 {
1582+
let cls: *const AnyClass = cls;
1583+
unsafe { ffi::class_getIvarLayout(cls.cast()) }
1584+
}
1585+
1586+
#[test]
1587+
#[cfg_attr(
1588+
feature = "gnustep-1-7",
1589+
ignore = "ivar layout is still used on GNUStep"
1590+
)]
1591+
fn test_layout_does_not_matter_any_longer() {
1592+
assert!(get_ivar_layout(class!(NSObject)).is_null());
1593+
assert!(get_ivar_layout(class!(NSArray)).is_null());
1594+
assert!(get_ivar_layout(class!(NSException)).is_null());
1595+
assert!(get_ivar_layout(class!(NSNumber)).is_null());
1596+
assert!(get_ivar_layout(class!(NSString)).is_null());
1597+
}
15791598
}

crates/objc2/src/runtime/nsobject.rs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,21 @@ use crate::runtime::{AnyClass, AnyObject, ProtocolObject};
77
use crate::{extern_methods, msg_send, msg_send_id, Message};
88
use crate::{ClassType, ProtocolType};
99

10-
crate::__emit_struct! {
11-
(
12-
/// The root class of most Objective-C class hierarchies.
13-
///
14-
/// This represents the [`NSObject` class][cls]. The name "NSObject" also
15-
/// refers to a protocol, see [`NSObjectProtocol`] for that.
16-
///
17-
/// Since this class is only available with the `Foundation` framework,
18-
/// `objc2` links to it for you.
19-
///
20-
/// This is exported under `icrate::Foundation::NSObject`, you probably
21-
/// want to use that path instead.
22-
///
23-
/// [cls]: https://developer.apple.com/documentation/objectivec/nsobject?language=objc
24-
)
25-
(pub)
26-
(NSObject)
27-
(
28-
__inner: AnyObject,
29-
)
10+
/// The root class of most Objective-C class hierarchies.
11+
///
12+
/// This represents the [`NSObject` class][cls]. The name "NSObject" also
13+
/// refers to a protocol, see [`NSObjectProtocol`] for that.
14+
///
15+
/// Since this class is only available with the `Foundation` framework,
16+
/// `objc2` links to it for you.
17+
///
18+
/// This is exported under `icrate::Foundation::NSObject`, you probably
19+
/// want to use that path instead.
20+
///
21+
/// [cls]: https://developer.apple.com/documentation/objectivec/nsobject?language=objc
22+
#[repr(C)]
23+
pub struct NSObject {
24+
__inner: AnyObject,
3025
}
3126

3227
crate::__extern_class_impl_traits! {

0 commit comments

Comments
 (0)