Skip to content

Commit 5ca96d1

Browse files
committed
tmp
1 parent cd7b9d4 commit 5ca96d1

File tree

7 files changed

+169
-54
lines changed

7 files changed

+169
-54
lines changed

objc2/src/rc/autorelease.rs

+21-24
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,23 @@ impl AutoreleasePool {
5858
Self { context }
5959
}
6060

61+
/// This will be removed in a future version.
62+
#[cfg_attr(
63+
all(debug_assertions, not(feature = "unstable_autoreleasesafe")),
64+
inline
65+
)]
66+
#[doc(hidden)]
67+
pub fn __verify_is_inner(&self) {
68+
#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))]
69+
POOLS.with(|c| {
70+
assert_eq!(
71+
c.borrow().last(),
72+
Some(&self.context),
73+
"Tried to use lifetime from pool that was not innermost"
74+
)
75+
});
76+
}
77+
6178
/// Returns a shared reference to the given autoreleased pointer object.
6279
///
6380
/// This is the preferred way to make references from autoreleased
@@ -70,20 +87,10 @@ impl AutoreleasePool {
7087
///
7188
/// This is equivalent to `&*ptr`, and shares the unsafety of that, except
7289
/// the lifetime is bound to the pool instead of being unbounded.
73-
#[cfg_attr(
74-
all(debug_assertions, not(feature = "unstable_autoreleasesafe")),
75-
inline
76-
)]
90+
#[inline]
7791
#[allow(clippy::needless_lifetimes)]
7892
pub unsafe fn ptr_as_ref<'p, T>(&'p self, ptr: *const T) -> &'p T {
79-
#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))]
80-
POOLS.with(|c| {
81-
assert_eq!(
82-
c.borrow().last(),
83-
Some(&self.context),
84-
"Tried to create shared reference with a lifetime from a pool that was not the innermost pool"
85-
)
86-
});
93+
self.__verify_is_inner();
8794
// SAFETY: Checked by the caller
8895
&*ptr
8996
}
@@ -100,21 +107,11 @@ impl AutoreleasePool {
100107
///
101108
/// This is equivalent to `&mut *ptr`, and shares the unsafety of that,
102109
/// except the lifetime is bound to the pool instead of being unbounded.
103-
#[cfg_attr(
104-
all(debug_assertions, not(feature = "unstable_autoreleasesafe")),
105-
inline
106-
)]
110+
#[inline]
107111
#[allow(clippy::needless_lifetimes)]
108112
#[allow(clippy::mut_from_ref)]
109113
pub unsafe fn ptr_as_mut<'p, T>(&'p self, ptr: *mut T) -> &'p mut T {
110-
#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))]
111-
POOLS.with(|c| {
112-
assert_eq!(
113-
c.borrow().last(),
114-
Some(&self.context),
115-
"Tried to create unique reference with a lifetime from a pool that was not the innermost pool")
116-
}
117-
);
114+
self.__verify_is_inner();
118115
// SAFETY: Checked by the caller
119116
&mut *ptr
120117
}

objc2_foundation/examples/basic_usage.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use objc2::rc::autoreleasepool;
12
use objc2_foundation::{
23
INSArray, INSCopying, INSDictionary, INSObject, INSString, NSArray, NSDictionary, NSObject,
34
NSString,
@@ -25,9 +26,12 @@ fn main() {
2526

2627
// Create an NSString from a str slice
2728
let string = NSString::from_str("Hello, world!");
28-
println!("{}", string.as_str());
29-
let string2 = string.copy();
30-
println!("{}", string2.as_str());
29+
// Use an autoreleasepool to get the `str` contents of the NSString
30+
autoreleasepool(|pool| {
31+
println!("{}", string.as_str(pool));
32+
let string2 = string.copy();
33+
println!("{}", string2.as_str(pool));
34+
});
3135

3236
// Create a dictionary mapping strings to objects
3337
let keys = &[&*string];

objc2_foundation/src/array.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ mod tests {
414414

415415
use super::{INSArray, INSMutableArray, NSArray, NSMutableArray};
416416
use crate::{INSObject, INSString, NSObject, NSString};
417-
use objc2::rc::{Id, Owned};
417+
use objc2::rc::{autoreleasepool, Id, Owned};
418418

419419
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
420420
let mut vec = Vec::with_capacity(len);
@@ -527,8 +527,10 @@ mod tests {
527527
let strings = vec![NSString::from_str("hello"), NSString::from_str("hi")];
528528
let mut strings = NSMutableArray::from_vec(strings);
529529

530-
strings.sort_by(|s1, s2| s1.as_str().len().cmp(&s2.as_str().len()));
531-
assert_eq!(strings[0].as_str(), "hi");
532-
assert_eq!(strings[1].as_str(), "hello");
530+
autoreleasepool(|pool| {
531+
strings.sort_by(|s1, s2| s1.as_str(pool).len().cmp(&s2.as_str(pool).len()));
532+
assert_eq!(strings[0].as_str(pool), "hi");
533+
assert_eq!(strings[1].as_str(pool), "hello");
534+
});
533535
}
534536
}

objc2_foundation/src/dictionary.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ impl<'a, K: INSObject, V: INSObject> Index<&'a K> for NSDictionary<K, V> {
167167
#[cfg(test)]
168168
mod tests {
169169
use alloc::vec;
170-
use objc2::rc::{Id, Shared};
170+
use objc2::rc::{autoreleasepool, Id, Shared};
171171

172172
use super::{INSDictionary, NSDictionary};
173173
use crate::{INSArray, INSObject, INSString, NSObject, NSString};
@@ -201,7 +201,9 @@ mod tests {
201201
let keys = dict.keys();
202202

203203
assert_eq!(keys.len(), 1);
204-
assert_eq!(keys[0].as_str(), "abcd");
204+
autoreleasepool(|pool| {
205+
assert_eq!(keys[0].as_str(pool), "abcd");
206+
});
205207
}
206208

207209
#[test]
@@ -219,15 +221,19 @@ mod tests {
219221

220222
assert_eq!(keys.len(), 1);
221223
assert_eq!(objs.len(), 1);
222-
assert_eq!(keys[0].as_str(), "abcd");
224+
autoreleasepool(|pool| {
225+
assert_eq!(keys[0].as_str(pool), "abcd");
226+
});
223227
assert_eq!(objs[0], dict.get(keys[0]).unwrap());
224228
}
225229

226230
#[test]
227231
fn test_key_enumerator() {
228232
let dict = sample_dict("abcd");
229233
assert_eq!(dict.key_enumerator().count(), 1);
230-
assert_eq!(dict.key_enumerator().next().unwrap().as_str(), "abcd");
234+
autoreleasepool(|pool| {
235+
assert_eq!(dict.key_enumerator().next().unwrap().as_str(pool), "abcd");
236+
});
231237
}
232238

233239
#[test]
@@ -242,7 +248,9 @@ mod tests {
242248

243249
let keys = dict.keys_array();
244250
assert_eq!(keys.len(), 1);
245-
assert_eq!(keys[0].as_str(), "abcd");
251+
autoreleasepool(|pool| {
252+
assert_eq!(keys[0].as_str(pool), "abcd");
253+
});
246254

247255
// let objs = INSDictionary::into_values_array(dict);
248256
// assert_eq!(objs.len(), 1);

objc2_foundation/src/macros.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ macro_rules! object_struct {
3939
impl ::core::fmt::Debug for $name {
4040
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
4141
use $crate::{INSObject, INSString};
42-
::core::fmt::Debug::fmt(self.description().as_str(), f)
42+
::objc2::rc::autoreleasepool(|pool| {
43+
::core::fmt::Debug::fmt(self.description().as_str(pool), f)
44+
})
4345
}
4446
}
4547
};

objc2_foundation/src/object.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ mod tests {
6262
use super::{INSObject, NSObject};
6363
use crate::{INSString, NSString};
6464
use alloc::format;
65+
use objc2::rc::autoreleasepool;
6566

6667
#[test]
6768
fn test_is_equal() {
@@ -83,7 +84,9 @@ mod tests {
8384
let obj = NSObject::new();
8485
let description = obj.description();
8586
let expected = format!("<NSObject: {:p}>", &*obj);
86-
assert!(description.as_str() == &*expected);
87+
autoreleasepool(|pool| {
88+
assert_eq!(description.as_str(pool), &*expected);
89+
});
8790
}
8891

8992
#[test]

objc2_foundation/src/string.rs

+115-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use core::str;
66
use std::os::raw::c_char;
77

88
use objc2::msg_send;
9+
use objc2::rc::{autoreleasepool, AutoreleasePool};
910
use objc2::rc::{Id, Owned, Shared};
1011

1112
use super::INSObject;
@@ -57,16 +58,62 @@ pub trait INSString: INSObject {
5758
self.len() == 0
5859
}
5960

60-
fn as_str(&self) -> &str {
61-
let bytes = unsafe {
62-
let bytes: *const c_char = msg_send![self, UTF8String];
63-
bytes as *const u8
64-
};
61+
// #[cfg_attr(
62+
// not(target_vendor = "apple"),
63+
// doc("unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };")
64+
// )]
65+
/// TODO
66+
///
67+
/// ```
68+
/// # use objc2::rc::autoreleasepool;
69+
/// # use objc2_foundation::{INSObject, INSString, NSString};
70+
/// autoreleasepool(|pool| {
71+
/// let ns_string = NSString::new();
72+
/// let s = ns_string.as_str(pool);
73+
/// drop(ns_string);
74+
/// println!("{}", s);
75+
/// });
76+
/// ```
77+
///
78+
/// ```
79+
/// # use objc2::rc::autoreleasepool;
80+
/// # use objc2_foundation::{INSObject, INSString, NSString};
81+
/// let ns_string = NSString::new();
82+
/// let s = autoreleasepool(|pool| ns_string.as_str(pool));
83+
/// ```
84+
fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str {
85+
// This is necessary until `auto` types stabilizes.
86+
pool.__verify_is_inner();
87+
88+
// The documentation on `UTF8String` is a bit sparse, but with
89+
// educated guesses and testing I've determined that NSString stores
90+
// a pointer to the string data, sometimes with an UTF-8 encoding,
91+
// (usual for ascii data), sometimes in other encodings (UTF-16?).
92+
//
93+
// `UTF8String` then checks the internal encoding:
94+
// - If the data is UTF-8 encoded, it returns the internal pointer.
95+
// - If the data is in another encoding, it creates a new allocation,
96+
// writes the UTF-8 representation of the string into it,
97+
// autoreleases the allocation and returns a pointer to it.
98+
//
99+
// So the lifetime of the returned pointer is either the same as the
100+
// NSString OR the lifetime of the innermost @autoreleasepool.
101+
let bytes: *const c_char = unsafe { msg_send![self, UTF8String] };
102+
let bytes = bytes as *const u8;
65103
let len = self.len();
66-
unsafe {
67-
let bytes = slice::from_raw_parts(bytes, len);
68-
str::from_utf8(bytes).unwrap()
69-
}
104+
105+
// SAFETY:
106+
// The held AutoreleasePool is the innermost, and the reference is
107+
// constrained both by the pool and the NSString.
108+
//
109+
// `len` is the length of the string in the UTF-8 encoding.
110+
//
111+
// `bytes` is a null-terminated C string (with length = len + 1), so
112+
// it is never a NULL pointer.
113+
let bytes: &'r [u8] = unsafe { slice::from_raw_parts(bytes, len) };
114+
115+
// TODO: Always UTF-8, so should we use `from_utf8_unchecked`?
116+
str::from_utf8(bytes).unwrap()
70117
}
71118

72119
fn from_str(string: &str) -> Id<Self, Self::Ownership> {
@@ -95,40 +142,92 @@ impl INSCopying for NSString {
95142

96143
impl fmt::Display for NSString {
97144
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98-
fmt::Display::fmt(self.as_str(), f)
145+
autoreleasepool(|pool| fmt::Display::fmt(self.as_str(pool), f))
99146
}
100147
}
101148

102149
#[cfg(test)]
103150
mod tests {
104-
use super::{INSCopying, INSString, NSString};
151+
use super::*;
105152

106153
#[cfg(not(target_vendor = "apple"))]
107154
#[test]
108155
fn ensure_linkage() {
109156
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
110157
}
111158

159+
#[test]
160+
fn test_empty() {
161+
let s1 = NSString::from_str("");
162+
let s2 = NSString::new();
163+
164+
assert_eq!(s1.len(), 0);
165+
assert_eq!(s2.len(), 0);
166+
167+
assert_eq!(s1, s2);
168+
169+
autoreleasepool(|pool| {
170+
assert_eq!(s1.as_str(pool), "");
171+
assert_eq!(s2.as_str(pool), "");
172+
});
173+
}
174+
112175
#[test]
113176
fn test_utf8() {
114177
let expected = "ประเทศไทย中华Việt Nam";
115178
let s = NSString::from_str(expected);
116-
assert!(s.len() == expected.len());
117-
assert!(s.as_str() == expected);
179+
assert_eq!(s.len(), expected.len());
180+
autoreleasepool(|pool| {
181+
assert_eq!(s.as_str(pool), expected);
182+
});
118183
}
119184

120185
#[test]
121186
fn test_interior_nul() {
122187
let expected = "Hello\0World";
123188
let s = NSString::from_str(expected);
124-
assert!(s.len() == expected.len());
125-
assert!(s.as_str() == expected);
189+
assert_eq!(s.len(), expected.len());
190+
autoreleasepool(|pool| {
191+
assert_eq!(s.as_str(pool), expected);
192+
});
126193
}
127194

128195
#[test]
129196
fn test_copy() {
130197
let s = NSString::from_str("Hello!");
131198
let copied = s.copy();
132-
assert!(copied.as_str() == s.as_str());
199+
autoreleasepool(|pool| {
200+
assert_eq!(copied.as_str(pool), s.as_str(pool));
201+
});
202+
}
203+
204+
#[test]
205+
fn test_copy_nsstring_is_same() {
206+
let string1 = NSString::from_str("Hello, world!");
207+
let string2 = string1.copy();
208+
209+
let s1: *const NSString = &*string1;
210+
let s2: *const NSString = &*string2;
211+
212+
assert_eq!(s1, s2, "Cloned NSString didn't have the same address");
213+
}
214+
215+
#[test]
216+
/// Apparently NSString does this for some reason?
217+
fn test_strips_first_leading_zero_width_no_break_space() {
218+
let ns_string = NSString::from_str("\u{feff}");
219+
let expected = "";
220+
autoreleasepool(|pool| {
221+
assert_eq!(ns_string.as_str(pool), expected);
222+
});
223+
assert_eq!(ns_string.len(), 0);
224+
225+
let s = "\u{feff}\u{feff}a\u{feff}";
226+
let expected = "\u{feff}a\u{feff}";
227+
let ns_string = NSString::from_str(s);
228+
autoreleasepool(|pool| {
229+
assert_eq!(ns_string.as_str(pool), expected);
230+
});
231+
assert_eq!(ns_string.len(), expected.len());
133232
}
134233
}

0 commit comments

Comments
 (0)