Skip to content

Commit a9933ec

Browse files
committed
Allow creating and loading instance variables with the same name
1 parent 71bd307 commit a9933ec

File tree

16 files changed

+857
-962
lines changed

16 files changed

+857
-962
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/objc2/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
8585
implement `IsAllowedMutable`.
8686
* Disallow the ability to use non-`Self`-like types as the receiver in
8787
`declare_class!`.
88+
* Allow adding instance variables with the same name on Apple platforms.
89+
* **BREAKING**: Make loading instance variables robust and sound in the face
90+
of instance variables with the same name.
91+
92+
To read or write the instance variable for an object, you should now use the
93+
`load`, `load_ptr` and `load_mut` methods on `Ivar`, instead of the `ivar`,
94+
`ivar_ptr` and `ivar_mut` methods on `AnyObject`.
95+
96+
This _is_ more verbose, but it also ensures that the class for the instance
97+
variable you're loading is the same as the one the instance variable you
98+
want to access is defined on.
99+
100+
```rust
101+
// Before
102+
let number = unsafe { *obj.ivar::<u32>("number") };
103+
104+
// After
105+
let ivar = cls.instance_variable("number").unwrap();
106+
let number = unsafe { *ivar.load::<u32>(&obj) };
107+
```
88108

89109
### Removed
90110
* **BREAKING**: Removed `ProtocolType` implementation for `NSObject`.

crates/objc2/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ objc2-proc-macros = { path = "../objc2-proc-macros", version = "0.1.1", optional
108108
[dev-dependencies]
109109
iai = { version = "0.1", git = "https://github.com/madsmtm/iai", branch = "callgrind" }
110110
static_assertions = "1.1.0"
111+
memoffset = "0.9.0"
111112

112113
[[bench]]
113114
name = "autorelease"

crates/objc2/examples/class_with_lifetime.rs

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,19 @@
44
use std::marker::PhantomData;
55
use std::sync::Once;
66

7-
use objc2::declare::{ClassBuilder, Ivar, IvarEncode, IvarType};
7+
use objc2::declare::ClassBuilder;
88
use objc2::mutability::Mutable;
99
use objc2::rc::Id;
1010
use objc2::runtime::{AnyClass, NSObject, Sel};
1111
use objc2::{msg_send, msg_send_id, sel};
1212
use objc2::{ClassType, Encoding, Message, RefEncode};
1313

14-
/// Helper type for the instance variable
15-
struct NumberIvar<'a> {
16-
// Doesn't actually matter what we put here, but we have to use the
17-
// lifetime parameter somehow
18-
p: PhantomData<&'a mut u8>,
19-
}
20-
21-
unsafe impl<'a> IvarType for NumberIvar<'a> {
22-
type Type = IvarEncode<&'a mut u8>;
23-
const NAME: &'static str = "_number_ptr";
24-
}
25-
2614
/// Struct that represents our custom object.
2715
#[repr(C)]
2816
pub struct MyObject<'a> {
2917
// Required to give MyObject the proper layout
3018
superclass: NSObject,
31-
// SAFETY: The ivar is declared below, and is properly initialized in the
32-
// designated initializer.
33-
//
34-
// Note! Attempting to acess the ivar before it has been initialized is
35-
// undefined behaviour!
36-
number: Ivar<NumberIvar<'a>>,
19+
p: PhantomData<&'a mut u8>,
3720
}
3821

3922
unsafe impl RefEncode for MyObject<'_> {
@@ -50,8 +33,12 @@ impl<'a> MyObject<'a> {
5033
) -> Option<&'s mut Self> {
5134
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
5235
this.map(|this| {
53-
// Properly initialize the number reference
54-
Ivar::write(&mut this.number, ptr.expect("got NULL number ptr"));
36+
let ivar = Self::class().instance_variable("number").unwrap();
37+
// SAFETY: The ivar is added with the same type below
38+
unsafe {
39+
ivar.load_ptr::<&mut u8>(&this.superclass)
40+
.write(ptr.expect("got NULL number ptr"))
41+
};
5542
this
5643
})
5744
}
@@ -62,12 +49,16 @@ impl<'a> MyObject<'a> {
6249
unsafe { msg_send_id![Self::alloc(), initWithPtr: number] }
6350
}
6451

65-
pub fn get(&self) -> &u8 {
66-
&self.number
52+
pub fn get(&self) -> u8 {
53+
let ivar = Self::class().instance_variable("number").unwrap();
54+
// SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr`
55+
unsafe { **ivar.load::<&mut u8>(&self.superclass) }
6756
}
6857

6958
pub fn set(&mut self, number: u8) {
70-
**self.number = number;
59+
let ivar = Self::class().instance_variable("number").unwrap();
60+
// SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr`
61+
unsafe { **ivar.load_mut::<&mut u8>(&mut self.superclass) = number };
7162
}
7263
}
7364

@@ -84,7 +75,7 @@ unsafe impl<'a> ClassType for MyObject<'a> {
8475
let superclass = NSObject::class();
8576
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();
8677

87-
builder.add_static_ivar::<NumberIvar<'a>>();
78+
builder.add_ivar::<&mut u8>("number");
8879

8980
unsafe {
9081
builder.add_method(
@@ -112,7 +103,7 @@ fn main() {
112103
let mut number = 54;
113104

114105
let mut obj = MyObject::new(&mut number);
115-
assert_eq!(*obj.get(), 54);
106+
assert_eq!(obj.get(), 54);
116107

117108
// It is not possible to convert to `Id<NSObject>`, since that would loose
118109
// the lifetime information that `MyObject` stores.
@@ -131,7 +122,7 @@ fn main() {
131122

132123
// But we can now mutate the referenced `number`
133124
obj.set(7);
134-
assert_eq!(*obj.get(), 7);
125+
assert_eq!(obj.get(), 7);
135126

136127
drop(obj);
137128
// And now that we've dropped `obj`, we can access `number` again

crates/objc2/examples/introspection.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ fn main() {
4949

5050
println!("NSObject address: {obj:p}");
5151

52-
// Access an ivar of the object
53-
//
54-
// As before, you should not rely on the `isa` ivar being available!
55-
let isa = unsafe { *obj.ivar::<*const AnyClass>("isa") };
52+
// Read an ivar on the object
53+
let isa: *const AnyClass = unsafe { *ivar.load(&obj) };
5654
println!("NSObject isa: {isa:?}");
5755
}

crates/objc2/src/declare/ivar.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::ops::{Deref, DerefMut};
55
use core::ptr::{self, NonNull};
66

77
use crate::encode::Encode;
8-
use crate::runtime::{ivar_offset, AnyObject};
8+
use crate::runtime::AnyObject;
99

1010
pub(crate) mod private {
1111
pub trait Sealed {}
@@ -92,9 +92,13 @@ pub unsafe trait IvarType {
9292
const NAME: &'static str;
9393

9494
#[doc(hidden)]
95-
unsafe fn __offset(ptr: NonNull<AnyObject>) -> isize {
96-
let obj = unsafe { ptr.as_ref() };
97-
ivar_offset(obj.class(), Self::NAME, &Self::Type::ENCODING)
95+
unsafe fn __ivar_ptr(ptr: NonNull<AnyObject>) -> NonNull<Self::Type> {
96+
// FIXME: This is currently unsound! Looking up the instance variable
97+
// dynamically will return the wrong variable if two variables with
98+
// the same name exist.
99+
let ivar = unsafe { ptr.as_ref() }.lookup_instance_variable_dynamically(Self::NAME);
100+
ivar.debug_assert_encoding(&Self::Type::ENCODING);
101+
unsafe { AnyObject::ivar_at_offset(ptr, ivar.offset()) }
98102
}
99103
}
100104

@@ -215,9 +219,7 @@ impl<T: IvarType> Ivar<T> {
215219
// Note: We technically don't have provenance over the object, nor the
216220
// ivar, but the object doesn't have provenance over the ivar either,
217221
// so that is fine.
218-
let offset = unsafe { T::__offset(ptr) };
219-
// SAFETY: The offset is valid
220-
unsafe { AnyObject::ivar_at_offset::<T::Type>(ptr, offset) }
222+
unsafe { T::__ivar_ptr(ptr) }
221223
}
222224

223225
/// Get a mutable pointer to the instance variable.
@@ -238,9 +240,7 @@ impl<T: IvarType> Ivar<T> {
238240
let ptr: NonNull<AnyObject> = NonNull::from(self).cast();
239241

240242
// SAFETY: Same as `as_inner_ptr`
241-
let offset = unsafe { T::__offset(ptr) };
242-
// SAFETY: The offset is valid
243-
unsafe { AnyObject::ivar_at_offset::<T::Type>(ptr, offset) }
243+
unsafe { T::__ivar_ptr(ptr) }
244244
}
245245

246246
/// Sets the value of the instance variable.

0 commit comments

Comments
 (0)