Skip to content

Commit 475e7cc

Browse files
committed
Fix exception memory management
1 parent 624dab1 commit 475e7cc

File tree

5 files changed

+52
-39
lines changed

5 files changed

+52
-39
lines changed

crates/objc-sys/build.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,7 @@ fn main() {
210210
// - ...
211211
//
212212
// TODO: -fobjc-weak ?
213-
let mut cc_args = format!(
214-
"-fobjc-arc -fobjc-arc-exceptions -fobjc-exceptions -fobjc-runtime={clang_runtime}"
215-
);
213+
let mut cc_args = format!("-fobjc-exceptions -fobjc-runtime={clang_runtime}");
216214

217215
if let Runtime::ObjFW(_) = &runtime {
218216
// Add compability headers to make `#include <objc/objc.h>` work.
@@ -221,8 +219,6 @@ fn main() {
221219
cc_args.push_str(compat_headers.to_str().unwrap());
222220
}
223221

224-
println!("cargo:cc_args={cc_args}"); // DEP_OBJC_[version]_CC_ARGS
225-
226222
if let Runtime::ObjFW(_) = &runtime {
227223
// Link to libobjfw-rt
228224
println!("cargo:rustc-link-lib=dylib=objfw-rt");
@@ -250,10 +246,18 @@ fn main() {
250246
let mut builder = cc::Build::new();
251247
builder.file("extern/exception.m");
252248

249+
// Compile with exceptions enabled and with the correct runtime, but
250+
// _without ARC_!
253251
for flag in cc_args.split(' ') {
254252
builder.flag(flag);
255253
}
256254

257255
builder.compile("librust_objc_sys_0_3_try_catch_exception.a");
258256
}
257+
258+
// Add this to the `CC` args _after_ we've omitted it when compiling
259+
// `extern/exception.m`.
260+
cc_args.push_str(" -fobjc-arc -fobjc-arc-exceptions");
261+
262+
println!("cargo:cc_args={cc_args}"); // DEP_OBJC_[version]_CC_ARGS
259263
}

crates/objc-sys/extern/exception.m

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212
unsigned char rust_objc_sys_0_3_try_catch_exception(void (*f)(void *), void *context, id *error) {
1313
@try {
1414
f(context);
15-
if (error) {
16-
*error = (id)0; // nil
17-
}
1815
return 0;
1916
} @catch (id exception) {
20-
if (error) {
21-
*error = objc_retain(exception);
22-
}
17+
// The exception is retained while inside the `@catch` block, but is
18+
// not guaranteed to be so outside of it; so hence we must do it here!
19+
*error = objc_retain(exception);
2320
return 1;
2421
}
2522
}

crates/objc2/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6868

6969
### Fixed
7070
* Fixed using autorelease pools on 32bit macOS and older macOS versions.
71+
* Fixed memory leaks in and improved performance of `exception::catch`.
7172

7273
### Removed
7374
* **BREAKING**: Removed `rc::SliceId`, since it is trivially implementable

crates/objc2/src/exception.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Exception>
237237
// SAFETY:
238238
// The exception is always a valid object or NULL.
239239
//
240+
// Since we do a retain inside `extern/exception.m`, the object has
241+
// +1 retain count.
242+
//
240243
// Code throwing an exception know that they don't hold sole access to
241244
// that object any more, so even if the type was originally mutable,
242245
// it is okay to create a new `Id` to it here.

crates/tests/src/exception.rs

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,39 @@ fn throw_catch_raise_catch() {
2222
let reason = NSString::from_str("def");
2323

2424
let exc = NSException::new(&name, Some(&reason), None).unwrap();
25-
// TODO: Investigate why this is required on GNUStep!
26-
let _exc2 = exc.clone();
2725

28-
assert_retain_count(&exc, 2);
29-
30-
// TODO: Investigate this!
31-
let extra_retain = usize::from(cfg!(all(
32-
feature = "apple",
33-
target_os = "macos",
34-
target_arch = "x86"
35-
)));
26+
assert_retain_count(&exc, 1);
3627

3728
let exc = autoreleasepool(|_| {
3829
let exc = NSException::into_exception(exc);
3930
let res = unsafe { catch(|| throw(exc)) };
4031
let exc = res.unwrap_err().unwrap();
4132
let exc = NSException::from_exception(exc).unwrap();
4233

43-
assert_retain_count(&exc, 3 + extra_retain);
34+
assert_retain_count(&exc, 1);
35+
exc
36+
});
37+
38+
assert_retain_count(&exc, 1);
39+
40+
let exc = autoreleasepool(|_| {
41+
let inner = || {
42+
autoreleasepool(|pool| {
43+
let exc = Id::autorelease(exc, pool);
44+
unsafe { exc.raise() }
45+
})
46+
};
47+
48+
let res = unsafe { catch(inner) };
49+
let exc = NSException::from_exception(res.unwrap_err().unwrap()).unwrap();
50+
51+
// Undesired: The inner pool _should_ have been drained on unwind, but
52+
// it isn't, see `rc::Pool::drain`.
53+
assert_retain_count(&exc, 2);
4454
exc
4555
});
4656

47-
assert_retain_count(&exc, 2 + extra_retain);
57+
assert_retain_count(&exc, 1);
4858

4959
assert_eq!(exc.name(), name);
5060
assert_eq!(exc.reason().unwrap(), reason);
@@ -85,28 +95,26 @@ fn raise_catch() {
8595
let exc = NSException::new(&name, Some(&reason), None).unwrap();
8696
assert_retain_count(&exc, 1);
8797

88-
let res = autoreleasepool(|_| {
89-
let res = unsafe {
90-
catch(|| {
91-
if exc.name() == name {
92-
exc.raise();
93-
} else {
94-
4
95-
}
96-
})
98+
let exc = autoreleasepool(|pool| {
99+
let exc = Id::autorelease(exc, pool);
100+
let inner = || {
101+
if exc.name() == name {
102+
unsafe { exc.raise() };
103+
} else {
104+
42
105+
}
97106
};
98-
assert_retain_count(&exc, 3);
107+
let res = unsafe { catch(inner) }.unwrap_err().unwrap();
108+
assert_retain_count(&exc, 2);
99109
res
100110
});
101111

102-
assert_retain_count(&exc, 2);
103-
104-
let obj = res.unwrap_err().unwrap();
112+
assert_retain_count(&exc, 1);
105113

106-
assert_eq!(format!("{obj}"), "def");
114+
assert_eq!(format!("{exc}"), "def");
107115
assert_eq!(
108-
format!("{obj:?}"),
109-
format!("exception <NSException: {:p}> 'abc' reason:def", &*obj)
116+
format!("{exc:?}"),
117+
format!("exception <NSException: {:p}> 'abc' reason:def", &*exc)
110118
);
111119
}
112120

0 commit comments

Comments
 (0)