11# This repo is about reproducible memory leak in objc_foundation`s INSstring.as_str().
22
3-
4-
53## how to see a leak
64
75``` shell
@@ -21,7 +19,7 @@ pub fn leak(str: &str) {
2119
2220## why it produces a leak?
2321
24- INSString.as_str() internaly uses UTF8String property of NSString.
22+ INSString.as_str() internally uses UTF8String property of NSString.
2523[ Apple doc] ( https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc )
2624 says that the memory behind this pointer has a lifetime shorter than a lifetime of an NSString itself.
2725But apparently, this is not entirely true. At least, this statement is not valid for strings that contain
@@ -36,27 +34,32 @@ So in the end, the actual leak occurs not in INSString.as_str() but, I guess, in
3634Yes. NSString::getCString ([ Apple doc] ( https://developer.apple.com/documentation/foundation/nsstring/1415702-getcstring ) ) and we can use it like this:
3735
3836``` rust
39- pub fn nsstring_to_rust_string (nsstring : Id <NSString >) -> String {
40- unsafe {
41- let string_size : usize = msg_send! [ nsstring , lengthOfBytesUsingEncoding : 4 ];
42- // + 1 is because getCString returns null terminated string
43- let buffer = libc :: malloc ( string_size + 1 ) as * mut c_char ;
44- let is_success : bool = msg_send! [ nsstring , getCString : buffer maxLength : string_size + 1 encoding : 4 ] ;
45- if is_success {
46- // CString will take care of memory from now on
47- CString :: from_raw ( buffer ) . to_str () . unwrap () . to_owned ()
48- } else {
49- // In case getCString failed there is no point in creating CString
50- // So we must free memory
51- libc :: free (buffer as * mut c_void );
52- // Original NSString::as_str() swallows all the errors.
53- // Not sure if that is the correct approach, but we also don`t have errors here.
54- "" . to_string ()
37+ pub fn convert_with_vec (nsstring : Id <NSString >) -> String {
38+ let string_size : usize = unsafe { msg_send! [ nsstring , lengthOfBytesUsingEncoding : 4 ] };
39+ let mut buffer : Vec < u8 > = vec! [ 0_ u8 ; string_size + 1 ];
40+ let is_success : bool = unsafe {
41+ msg_send! [ nsstring , getCString : buffer . as_mut_ptr () maxLength : string_size + 1 encoding : 4 ]
42+ } ;
43+ if is_success {
44+ // before from_vec_with_nul can be used https://github.com/rust-lang/rust/pull/89292
45+ // nul termination from the buffer should be removed by hands
46+ buffer . pop ();
47+
48+ unsafe {
49+ CString :: from_vec_unchecked (buffer )
50+ . to_str ()
51+ . unwrap ()
52+ . to_string ()
5553 }
54+ } else {
55+ // In case getCString failed there is no point in creating CString
56+ // Original NSString::as_str() swallows all the errors.
57+ // Not sure if that is the correct approach, but we also don`t have errors here.
58+ "" . to_string ()
5659 }
5760}
5861```
59- If you change in main.rs leak to no_leak and again will run again
62+ If you change in main.rs leak to no_leak_vec and will run again
6063``` shell
6164cargo instruments -t Allocations
6265```
@@ -67,6 +70,11 @@ cargo becnh
6770```
6871You will see that performance even better.
6972
73+ ``` shell
74+ to string/old time: [12.855 us 12.961 us 13.071 us]
75+ to string/new vec time: [10.477 us 10.586 us 10.699 us]
76+ ```
77+
7078
7179The only problem I see with this solution is that it has a different return type (String instead of &str).
7280If you know how to fix that, or any other idea on how to do things better - please let me know.
0 commit comments