-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Fix memory leak if C++ catches a Rust panic and discards it #67711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
10720b4
46f5226
c15ad84
4361192
838e387
ed217a5
757ed07
3a02576
8f60db8
4f163af
25519e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,7 @@ use unwind as uw; | |
#[repr(C)] | ||
struct Exception { | ||
_uwe: uw::_Unwind_Exception, | ||
cause: Option<Box<dyn Any + Send>>, | ||
cause: Box<dyn Any + Send>, | ||
} | ||
|
||
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 { | ||
|
@@ -67,7 +67,7 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 { | |
exception_cleanup, | ||
private: [0; uw::unwinder_private_data_size], | ||
}, | ||
cause: Some(data), | ||
cause: data, | ||
}); | ||
let exception_param = Box::into_raw(exception) as *mut uw::_Unwind_Exception; | ||
return uw::_Unwind_RaiseException(exception_param) as u32; | ||
|
@@ -78,6 +78,7 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 { | |
) { | ||
unsafe { | ||
let _: Box<Exception> = Box::from_raw(exception as *mut Exception); | ||
super::__rust_drop_panic(); | ||
} | ||
} | ||
} | ||
|
@@ -87,10 +88,8 @@ pub fn payload() -> *mut u8 { | |
} | ||
|
||
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> { | ||
let my_ep = ptr as *mut Exception; | ||
let cause = (*my_ep).cause.take(); | ||
uw::_Unwind_DeleteException(ptr as *mut _); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Who calls DeleteException on the exception? There's not a drop impl on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm flagging this as temporarily unresolved to leave a comment here too since this was a question I had. Could a comment be left indicating that we're explicitly not calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The documentation for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sure sorry I'm not questioning the validity of this deletion, just un-resolving it so my request for a comment in the code didn't show up as collapsed. |
||
cause.unwrap() | ||
let exception = Box::from_raw(ptr as *mut Exception); | ||
exception.cause | ||
} | ||
|
||
// Rust's exception class identifier. This is used by personality routines to | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -922,40 +922,61 @@ fn codegen_msvc_try( | |
// #include <stdint.h> | ||
// | ||
// struct rust_panic { | ||
// rust_panic(const rust_panic&); | ||
// ~rust_panic(); | ||
// | ||
// uint64_t x[2]; | ||
// } | ||
// | ||
// int bar(void (*foo)(void), uint64_t *ret) { | ||
// try { | ||
// foo(); | ||
// return 0; | ||
// } catch(rust_panic a) { | ||
// } catch(rust_panic& a) { | ||
// ret[0] = a.x[0]; | ||
// ret[1] = a.x[1]; | ||
// a.x[0] = 0; | ||
// return 1; | ||
// } | ||
// } | ||
// | ||
// More information can be found in libstd's seh.rs implementation. | ||
let i64_2 = bx.type_array(bx.type_i64(), 2); | ||
let i64_align = bx.tcx().data_layout.i64_align.abi; | ||
let slot = bx.alloca(i64_2, i64_align); | ||
let i64_2_ptr = bx.type_ptr_to(i64_2); | ||
let ptr_align = bx.tcx().data_layout.pointer_align.abi; | ||
let slot = bx.alloca(i64_2_ptr, ptr_align); | ||
bx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), None); | ||
|
||
normal.ret(bx.const_i32(0)); | ||
|
||
let cs = catchswitch.catch_switch(None, None, 1); | ||
catchswitch.add_handler(cs, catchpad.llbb()); | ||
|
||
// The flag value of 8 indicates that we are catching the exception by | ||
// reference instead of by value. We can't use catch by value because | ||
// that requires copying the exception object, which we don't support | ||
// since our exception object effectively contains a Box. | ||
// | ||
// Source: MicrosoftCXXABI::getAddrOfCXXCatchHandlerType in clang | ||
let flags = bx.const_i32(8); | ||
let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() { | ||
Some(did) => bx.get_static(did), | ||
None => bug!("eh_catch_typeinfo not defined, but needed for SEH unwinding"), | ||
}; | ||
let funclet = catchpad.catch_pad(cs, &[tydesc, bx.const_i32(0), slot]); | ||
let funclet = catchpad.catch_pad(cs, &[tydesc, flags, slot]); | ||
|
||
let payload = catchpad.load(slot, i64_align); | ||
let i64_align = bx.tcx().data_layout.i64_align.abi; | ||
let payload_ptr = catchpad.load(slot, ptr_align); | ||
let payload = catchpad.load(payload_ptr, i64_align); | ||
Amanieu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let local_ptr = catchpad.bitcast(local_ptr, bx.type_ptr_to(i64_2)); | ||
catchpad.store(payload, local_ptr, i64_align); | ||
|
||
// Clear the first word of the exception so avoid double-dropping it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make sure I understand what's going on here, we catch the exception by reference now which means that the system unwinder will always invoke the destructor function listed. For us that destructor function will skip its actual work since the first slot will be set to zero (as configured here). If that's true, how come we're catching by reference? Why not continue to catch by-value and just skip the destructor for Rust exceptions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we catch by value then C++ will give the catch a copy (not a move) of the exception object. It will still call the exception destructor after the catch ends. Catching by reference avoids this copy (which won't work anyways since our copy constructor panics). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok, makes sense. Does C++ never catch by value or in any way try to invoke the copy constructor? Also, can you inline these words into the code comments as well? |
||
// This will be read by the destructor which is implicitly called at the | ||
// end of the catch block by the runtime. | ||
let payload_0_ptr = catchpad.inbounds_gep(payload_ptr, &[bx.const_i32(0), bx.const_i32(0)]); | ||
catchpad.store(bx.const_u64(0), payload_0_ptr, i64_align); | ||
|
||
catchpad.catch_ret(&funclet, caught.llbb()); | ||
|
||
caught.ret(bx.const_i32(1)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading over this it seemed odd that the
Option
went away below but seemed to be added here, but this is foremcc.rs
so I'm not reviewing it too too closely.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an option here because the C++ runtime will invoke the destructor during
__cxa_end_catch
. We need to ensure that the exception object containsNone
at that point to avoid a double-free.