Skip to content

Commit 9bac2d3

Browse files
authored
Merge pull request #182 from madsmtm/unsafecell-and-ivars
`UnsafeCell` and ivars
2 parents 2438529 + 5ffd4e4 commit 9bac2d3

File tree

6 files changed

+107
-37
lines changed

6 files changed

+107
-37
lines changed

objc2-foundation/examples/class_with_lifetime.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl<'a> MyObject<'a> {
3434
}
3535

3636
fn get(&self) -> Option<&'a u8> {
37-
unsafe { *self.inner.ivar("_number_ptr") }
37+
unsafe { *self.inner.ivar::<Option<&'a u8>>("_number_ptr") }
3838
}
3939

4040
fn write(&mut self, number: u8) {
@@ -57,9 +57,7 @@ impl<'a> MyObject<'a> {
5757
) -> *mut Object {
5858
let this: *mut Object = unsafe { msg_send![super(this, NSObject::class()), init] };
5959
if let Some(this) = unsafe { this.as_mut() } {
60-
unsafe {
61-
this.set_ivar("_number_ptr", ptr);
62-
}
60+
unsafe { this.set_ivar::<*mut u8>("_number_ptr", ptr) };
6361
}
6462
this
6563
}

objc2/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3131
* Added `"unstable-c-unwind"` feature.
3232
* Added unsafe function `Id::cast` for converting between different types of
3333
objects.
34+
* Added `Object::ivar_ptr` to allow direct access to instance variables
35+
through `&Object`.
3436

3537
### Changed
3638
* **BREAKING:** `Sel` is now required to be non-null, which means that you

objc2/examples/introspection.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ fn main() {
2626
println!("NSObject address: {:p}", obj);
2727

2828
// Access an ivar of the object
29-
// TODO: Fix this!
30-
let isa: *const Class = unsafe { *obj.ivar("isa") };
29+
//
30+
// Note: You should not rely on the `isa` ivar being available,
31+
// this is only for demonstration.
32+
let isa = unsafe { *obj.ivar::<*const Class>("isa") };
3133
println!("NSObject isa: {:?}", isa);
3234

3335
#[cfg(feature = "malloc")]

objc2/src/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
//!
2323
//! // Add an ObjC method for getting the number
2424
//! extern "C" fn my_number_get(this: &Object, _cmd: Sel) -> u32 {
25-
//! unsafe { *this.ivar("_number") }
25+
//! unsafe { *this.ivar::<u32>("_number") }
2626
//! }
2727
//! unsafe {
2828
//! decl.add_method(

objc2/src/runtime.rs

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! For more information on foreign functions, see Apple's documentation:
44
//! <https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html>
55
6+
#[cfg(doc)]
7+
use core::cell::UnsafeCell;
68
use core::fmt;
79
use core::hash;
810
use core::panic::{RefUnwindSafe, UnwindSafe};
@@ -593,6 +595,11 @@ fn ivar_offset<T: Encode>(cls: &Class, name: &str) -> isize {
593595
///
594596
/// `Id<Object, _>` is equivalent to Objective-C's `id`.
595597
///
598+
/// This contains [`UnsafeCell`], and is similar to that in that one can
599+
/// safely access and perform interior mutability on this (both via.
600+
/// [`msg_send!`] and through ivars), so long as Rust's mutability rules are
601+
/// upheld, and that data races are avoided.
602+
///
596603
/// Note: This is intentionally neither [`Sync`], [`Send`], [`UnwindSafe`],
597604
/// [`RefUnwindSafe`] nor [`Unpin`], since that is something that may change
598605
/// depending on the specific subclass. For example, `NSAutoreleasePool` is
@@ -618,19 +625,45 @@ impl Object {
618625
unsafe { ptr.as_ref().unwrap_unchecked() }
619626
}
620627

621-
/// Returns a shared reference to the ivar with the given name.
628+
/// Returns a pointer to the instance variable / ivar with the given name.
629+
///
630+
/// This is similar to [`UnsafeCell::get`], see that for more information
631+
/// on what is and isn't safe to do.
632+
///
633+
/// Usually you will have defined the instance variable yourself with
634+
/// [`ClassBuilder::add_ivar`], the type of the ivar `T` must match the
635+
/// type used in that.
636+
///
637+
/// Attempting to access or modify private implementation details of a
638+
/// class that you do no control using this is not supported, and may
639+
/// invoke undefined behaviour.
640+
///
641+
/// Library implementors are strongly encouraged to expose a safe
642+
/// interface to the ivar.
643+
///
644+
/// [`ClassBuilder::add_ivar`]: crate::declare::ClassBuilder::add_ivar
645+
///
622646
///
623647
/// # Panics
624648
///
625-
/// Panics if the object has no ivar with the given name, or the type
626-
/// encoding of the ivar differs from the type encoding of `T`.
649+
/// May panic if the object has no ivar with the given name. May also
650+
/// panic if the type encoding of the ivar differs from the type encoding
651+
/// of `T`.
652+
///
653+
/// This should purely seen as help while debugging and is not guaranteed
654+
/// (e.g. it may be disabled when `debug_assertions` are off).
655+
///
627656
///
628657
/// # Safety
629658
///
630-
/// The caller must ensure that the ivar is actually of type `T`.
659+
/// The object must have an instance variable with the given name, and it
660+
/// must be of type `T`. Any invariants that the object have assumed about
661+
/// the value of the instance variable must not be violated.
631662
///
632-
/// Library implementors should expose a safe interface to the ivar.
633-
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
663+
/// No thread syncronization is done on accesses to the variable, so you
664+
/// must ensure that any access to the returned pointer do not cause data
665+
/// races, and that Rust's mutability rules are not otherwise violated.
666+
pub unsafe fn ivar_ptr<T: Encode>(&self, name: &str) -> *mut T {
634667
let offset = ivar_offset::<T>(self.class(), name);
635668
let ptr: *const Self = self;
636669

@@ -639,32 +672,56 @@ impl Object {
639672
let ptr = unsafe { ptr.offset(offset) };
640673
let ptr: *const T = ptr.cast();
641674

642-
unsafe { ptr.as_ref().unwrap_unchecked() }
675+
// Safe as *mut T because `self` is `UnsafeCell`
676+
ptr as *mut T
643677
}
644678

645-
/// Use [`ivar`][`Self::ivar`] instead.
679+
/// Returns a reference to the instance variable with the given name.
680+
///
681+
/// See [`Object::ivar_ptr`] for more information, including on when this
682+
/// panics.
683+
///
646684
///
647685
/// # Safety
648686
///
649-
/// Same as [`ivar`][`Self::ivar`].
687+
/// The object must have an instance variable with the given name, and it
688+
/// must be of type `T`.
689+
///
690+
/// No thread syncronization is done, so you must ensure that no other
691+
/// thread is concurrently mutating the variable. This requirement can be
692+
/// considered upheld if all mutation happens through [`Object::ivar_mut`]
693+
/// (since that takes `&mut self`).
694+
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
695+
// SAFETY: Upheld by caller.
696+
unsafe { self.ivar_ptr::<T>(name).as_ref().unwrap_unchecked() }
697+
}
698+
699+
/// Use [`Object::ivar`] instead.
700+
///
701+
///
702+
/// # Safety
703+
///
704+
/// See [`Object::ivar`].
650705
#[deprecated = "Use `Object::ivar` instead."]
651706
pub unsafe fn get_ivar<T: Encode>(&self, name: &str) -> &T {
652707
// SAFETY: Upheld by caller
653-
unsafe { self.ivar(name) }
708+
unsafe { self.ivar::<T>(name) }
654709
}
655710

656711
/// Returns a mutable reference to the ivar with the given name.
657712
///
658-
/// # Panics
713+
/// See [`Object::ivar_ptr`] for more information, including on when this
714+
/// panics.
659715
///
660-
/// Panics if the object has no ivar with the given name, or the type
661-
/// encoding of the ivar differs from the type encoding of `T`.
662716
///
663717
/// # Safety
664718
///
665-
/// The caller must ensure that the ivar is actually of type `T`.
719+
/// The object must have an instance variable with the given name, and it
720+
/// must be of type `T`.
666721
///
667-
/// Library implementors should expose a safe interface to the ivar.
722+
/// This access happens through `&mut self`, which means we know it to be
723+
/// the only reference, hence you do not need to do any work to ensure
724+
/// that data races do not happen.
668725
pub unsafe fn ivar_mut<T: Encode>(&mut self, name: &str) -> &mut T {
669726
let offset = ivar_offset::<T>(self.class(), name);
670727
let ptr: *mut Self = self;
@@ -677,29 +734,27 @@ impl Object {
677734
unsafe { ptr.as_mut().unwrap_unchecked() }
678735
}
679736

680-
/// Use [`ivar_mut`](`Self::ivar_mut`) instead.
737+
/// Use [`Object::ivar_mut`] instead.
738+
///
681739
///
682740
/// # Safety
683741
///
684-
/// Same as [`ivar_mut`][`Self::ivar_mut`].
742+
/// Same as [`Object::ivar_mut`].
685743
#[deprecated = "Use `Object::ivar_mut` instead."]
686744
pub unsafe fn get_mut_ivar<T: Encode>(&mut self, name: &str) -> &mut T {
687745
// SAFETY: Upheld by caller
688-
unsafe { self.ivar_mut(name) }
746+
unsafe { self.ivar_mut::<T>(name) }
689747
}
690748

691749
/// Sets the value of the ivar with the given name.
692750
///
693-
/// # Panics
751+
/// This is just a helpful shorthand for [`Object::ivar_mut`], see that
752+
/// for more information.
694753
///
695-
/// Panics if the object has no ivar with the given name, or the type
696-
/// encoding of the ivar differs from the type encoding of `T`.
697754
///
698755
/// # Safety
699756
///
700-
/// The caller must ensure that the ivar is actually of type `T`.
701-
///
702-
/// Library implementors should expose a safe interface to the ivar.
757+
/// Same as [`Object::ivar_mut`].
703758
pub unsafe fn set_ivar<T: Encode>(&mut self, name: &str, value: T) {
704759
// SAFETY: Invariants upheld by caller
705760
unsafe { *self.ivar_mut::<T>(name) = value };
@@ -851,13 +906,28 @@ mod tests {
851906
fn test_object() {
852907
let mut obj = test_utils::custom_object();
853908
assert_eq!(obj.class(), test_utils::custom_class());
854-
let result: u32 = unsafe {
855-
obj.set_ivar("_foo", 4u32);
856-
*obj.ivar("_foo")
909+
910+
let result = unsafe {
911+
obj.set_ivar::<u32>("_foo", 4);
912+
*obj.ivar::<u32>("_foo")
857913
};
858914
assert_eq!(result, 4);
859915
}
860916

917+
#[test]
918+
#[should_panic = "Ivar unknown not found on class CustomObject"]
919+
fn test_object_ivar_unknown() {
920+
let obj = test_utils::custom_object();
921+
let _ = unsafe { *obj.ivar::<u32>("unknown") };
922+
}
923+
924+
#[test]
925+
#[should_panic = "assertion failed: T::ENCODING.equivalent_to_str(ivar.type_encoding())"]
926+
fn test_object_ivar_wrong_type() {
927+
let obj = test_utils::custom_object();
928+
let _ = unsafe { *obj.ivar::<u8>("_foo") };
929+
}
930+
861931
#[test]
862932
fn test_encode() {
863933
fn assert_enc<T: Encode>(expected: &str) {

objc2/src/test_utils.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ pub(crate) fn custom_class() -> &'static Class {
108108
}
109109

110110
extern "C" fn custom_obj_set_foo(this: &mut Object, _cmd: Sel, foo: u32) {
111-
unsafe {
112-
this.set_ivar::<u32>("_foo", foo);
113-
}
111+
unsafe { this.set_ivar::<u32>("_foo", foo) }
114112
}
115113

116114
extern "C" fn custom_obj_get_foo(this: &Object, _cmd: Sel) -> u32 {

0 commit comments

Comments
 (0)