1
1
# This repo is about reproducible memory leak in objc_foundation`s INSstring.as_str().
2
2
3
-
4
-
5
3
## how to see a leak
6
4
7
5
``` shell
@@ -21,7 +19,7 @@ pub fn leak(str: &str) {
21
19
22
20
## why it produces a leak?
23
21
24
- INSString.as_str() internaly uses UTF8String property of NSString.
22
+ INSString.as_str() internally uses UTF8String property of NSString.
25
23
[ Apple doc] ( https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc )
26
24
says that the memory behind this pointer has a lifetime shorter than a lifetime of an NSString itself.
27
25
But 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
36
34
Yes. NSString::getCString ([ Apple doc] ( https://developer.apple.com/documentation/foundation/nsstring/1415702-getcstring ) ) and we can use it like this:
37
35
38
36
``` 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 ()
55
53
}
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 ()
56
59
}
57
60
}
58
61
```
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
60
63
``` shell
61
64
cargo instruments -t Allocations
62
65
```
@@ -67,6 +70,11 @@ cargo becnh
67
70
```
68
71
You will see that performance even better.
69
72
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
+
70
78
71
79
The only problem I see with this solution is that it has a different return type (String instead of &str).
72
80
If you know how to fix that, or any other idea on how to do things better - please let me know.
0 commit comments