@@ -6,6 +6,7 @@ use core::str;
6
6
use std:: os:: raw:: c_char;
7
7
8
8
use objc2:: msg_send;
9
+ use objc2:: rc:: { autoreleasepool, AutoreleasePool } ;
9
10
use objc2:: rc:: { Id , Owned , Shared } ;
10
11
11
12
use super :: INSObject ;
@@ -57,16 +58,62 @@ pub trait INSString: INSObject {
57
58
self . len ( ) == 0
58
59
}
59
60
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 ;
65
103
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 ( )
70
117
}
71
118
72
119
fn from_str ( string : & str ) -> Id < Self , Self :: Ownership > {
@@ -95,40 +142,92 @@ impl INSCopying for NSString {
95
142
96
143
impl fmt:: Display for NSString {
97
144
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) )
99
146
}
100
147
}
101
148
102
149
#[ cfg( test) ]
103
150
mod tests {
104
- use super :: { INSCopying , INSString , NSString } ;
151
+ use super :: * ;
105
152
106
153
#[ cfg( not( target_vendor = "apple" ) ) ]
107
154
#[ test]
108
155
fn ensure_linkage ( ) {
109
156
unsafe { objc2:: __gnustep_hack:: get_class_to_force_linkage ( ) } ;
110
157
}
111
158
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
+
112
175
#[ test]
113
176
fn test_utf8 ( ) {
114
177
let expected = "ประเทศไทย中华Việt Nam" ;
115
178
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
+ } ) ;
118
183
}
119
184
120
185
#[ test]
121
186
fn test_interior_nul ( ) {
122
187
let expected = "Hello\0 World" ;
123
188
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
+ } ) ;
126
193
}
127
194
128
195
#[ test]
129
196
fn test_copy ( ) {
130
197
let s = NSString :: from_str ( "Hello!" ) ;
131
198
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( ) ) ;
133
232
}
134
233
}
0 commit comments