rust-objc-foundation
rust-objc-foundation copied to clipboard
Violating soundness requirements
Treating Foundation objects as rust references is hard. Currently this crate can be used to violate soundness rules within safe code, which could result in undefined behavior.
One problem right now comes from NSCopying:
let mut string1 = NSString::from_str("Hello, world!");
let string2 = string1.copy();
let s1: &mut NSString = &mut string1;
let s2: &NSString = &string2;
println!("{:p}", s1);
println!("{:p}", s2);
This code results in an owned Id and a ShareId of the same object, allowing an &mut reference to an address while a & reference exists for the same address. This violates the aliasing requirements.
It may be the case that it's just too difficult to safely treat Objective-C objects as rust references and this crate is a failed experiment.
As far as I can see, this is only a problem with the specific copy implementation, since it is essentially just a retain, which as you illustrate allows us to go from Id<T, Owned> -> &T -> Id<T, Owned>, and thereby violating the aliasing requirements.
In step 2 (&T -> Id<T, Owned>), the lifetime information is discarded - but if we preserved it, the problem would go away. For instance, it could be solved like this:
pub struct CopiedId<'a, T> {
id: Id<T>,
lifetime: PhantomData<&'a T>,
}
impl<'a, T> Deref<Target = T> for CopiedId<'a, T> {
...
}
pub trait INSCopying: INSObject {
type Output: INSObject;
fn copy<'a>(&'a self) -> CopiedId<'a, Self::Output> { }
}
Or alternatively, if this is a more common problem, we could change objc_id::Id to always take a lifetime parameter (that would just be 'static most of the time):
pub struct Id<'a, T, O = Owned> { }
I've outlined these to solve the general cases, but in this instance NSString is immutable, and hence there's never a need for Id<NSString, Owned> - so the from_str implementation could just return ShareId<NSString>, and the problem would be solved!