From 4f7e15da74e60fbff81b9fa8e67b501f2604af1b Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Sat, 28 May 2022 17:49:34 -0600 Subject: [PATCH 1/6] first draft --- src/ffi.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/src/ffi.md b/src/ffi.md index 45a1f61e..de43e0da 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -718,17 +718,116 @@ void register(int (*f)(int (*)(int), int)) { No `transmute` required! -## FFI and panics +## FFI and unwinding -It’s important to be mindful of `panic!`s when working with FFI. A `panic!` -across an FFI boundary is undefined behavior. If you’re writing code that may -panic, you should run it in a closure with [`catch_unwind`]: +It’s important to be mindful of unwinding when working with FFI. Each +non-`Rust` ABI comes in two variants, one with `-unwind` and one without. If +you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI +boundary, that boundary must use the appropriate `-unwind` ABI string, and you +must not compile with `panic=abort`. + +Conversely, if you do not expect unwinding to cross an ABI boundary, use one of +the non-`unwind` ABI strings (other than `Rust`, which always permits +unwinding). If an unwinding operation does encounter an ABI boundary that is +not permitted to unwind, the behavior depends on whether the Rust code was +compiled with `panic=abort`: + +* With `panic=unwind`, the process will safely abort. +* With `panic=abort`, the behavior is undefined. This is only possible with + foreign exceptions, since `panic` will always abort rather than unwind in + this case. + +Note that the interaction of `catch_unwind` with foreign exceptions **is +undefined**, as is the interaction of `panic` with foreign exception-catching +mechanisms (notably C++'s `try`/`catch`). + +### Rust `panic` with `"C-unwind"` + +```rust +#[no_mangle] +extern "C-unwind" fn example() { + panic!("Uh oh"); +} +``` + +This function (when compiled with `panic=unwind`) is permitted to unwind C++ +stack frames. + +``` +[Rust function with `catch_unwind`, which stops the unwinding] + | + ... + | +[C++ frames] + | ^ + | (calls) | (unwinding + v | goes this +[Rust function `example`] | way) + | | + +--- rust function panics --+ +``` + +If the C++ frames have objects, their destructors will be called. + +### C++ `throw` with `"C-unwind"` + +```rust +#[link(...)] +extern "C-unwind" { + // A C++ function that may throw an exception + fn may_throw(); +} + +#[no_mangle] +extern "C-unwind" fn rust_passthrough() { + let b = Box::new(5); + unsafe { may_throw(); } + println!("{:?}", &b); +} +``` + +A C++ function with a `try` block may invoke `rust_passthrough` and `catch` an +exception thrown by `may_throw`. + +``` +[C++ function with `try` block that invokes `rust_passthrough`] + | + ... + | +[Rust function `rust_passthrough`] + | ^ + | (calls) | (unwinding + v | goes this +[C++ function `may_throw`] | way) + | | + +--- C++ function throws ----+ +``` + +If `may_throw` does throw an exception, `b` will be dropped. Otherwise, `5` +will be printed. + +### `panic` can be stopped at an ABI boundary + +```rust +#[no_mangle] +extern "C" fn assert_nonzero(input: u32) { + assert!(input != 0) +} +``` + +If `assert_nonzero` is called with the argument `0`, the runtime is guaranteed +to (safely) abort the process, whether or not compiled with `panic=abort`. + +### Catching `panic` preemptively + +If you are writing Rust code that may panic, and you don't wish to abort the +process if it panics, you must use [`catch_unwind`]: ```rust use std::panic::catch_unwind; #[no_mangle] -pub extern fn oh_no() -> i32 { +pub extern "C" fn oh_no() -> i32 { let result = catch_unwind(|| { panic!("Oops!"); }); From 8a9684abc313fbd2a1ef2993795344480ae43fca Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Sat, 28 May 2022 18:43:55 -0600 Subject: [PATCH 2/6] rephrase --- src/ffi.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ffi.md b/src/ffi.md index de43e0da..ae548c60 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -723,19 +723,19 @@ No `transmute` required! It’s important to be mindful of unwinding when working with FFI. Each non-`Rust` ABI comes in two variants, one with `-unwind` and one without. If you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI -boundary, that boundary must use the appropriate `-unwind` ABI string, and you -must not compile with `panic=abort`. +boundary, that boundary must use the appropriate `-unwind` ABI string. (Note +that compiling with `panic=abort` will still cause `panic!` to immediately +abort the process, regardless of which ABI is specified by the function that +`panic`s.) Conversely, if you do not expect unwinding to cross an ABI boundary, use one of the non-`unwind` ABI strings (other than `Rust`, which always permits unwinding). If an unwinding operation does encounter an ABI boundary that is -not permitted to unwind, the behavior depends on whether the Rust code was -compiled with `panic=abort`: +not permitted to unwind, the behavior depends on the source of the unwinding +(Rust `panic` or a foreign exception): -* With `panic=unwind`, the process will safely abort. -* With `panic=abort`, the behavior is undefined. This is only possible with - foreign exceptions, since `panic` will always abort rather than unwind in - this case. +* `panic` will cause the process to safely abort. +* A foreign exception entering Rust will cause undefined behavior. Note that the interaction of `catch_unwind` with foreign exceptions **is undefined**, as is the interaction of `panic` with foreign exception-catching @@ -841,7 +841,7 @@ fn main() {} ``` Please note that [`catch_unwind`] will only catch unwinding panics, not -those who abort the process. See the documentation of [`catch_unwind`] +those that abort the process. See the documentation of [`catch_unwind`] for more information. [`catch_unwind`]: ../std/panic/fn.catch_unwind.html From 81bf0b71a5a63840a661981dad19c1b317ba163e Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Sun, 29 May 2022 10:45:31 -0600 Subject: [PATCH 3/6] PR review: parenthetical as part of sentence Co-authored-by: Yuki Okushi --- src/ffi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ffi.md b/src/ffi.md index ae548c60..fd941a45 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -723,10 +723,10 @@ No `transmute` required! It’s important to be mindful of unwinding when working with FFI. Each non-`Rust` ABI comes in two variants, one with `-unwind` and one without. If you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI -boundary, that boundary must use the appropriate `-unwind` ABI string. (Note +boundary, that boundary must use the appropriate `-unwind` ABI string (note that compiling with `panic=abort` will still cause `panic!` to immediately abort the process, regardless of which ABI is specified by the function that -`panic`s.) +`panic`s). Conversely, if you do not expect unwinding to cross an ABI boundary, use one of the non-`unwind` ABI strings (other than `Rust`, which always permits From dcf4dfdf491a9a4420c7919730abb8e187dd4228 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Sun, 29 May 2022 10:46:38 -0600 Subject: [PATCH 4/6] PR suggestion: phrasing Co-authored-by: Yuki Okushi --- src/ffi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ffi.md b/src/ffi.md index fd941a45..8057b961 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -721,7 +721,7 @@ No `transmute` required! ## FFI and unwinding It’s important to be mindful of unwinding when working with FFI. Each -non-`Rust` ABI comes in two variants, one with `-unwind` and one without. If +non-`Rust` ABI comes in two variants, one with `-unwind` suffix and one without. If you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI boundary, that boundary must use the appropriate `-unwind` ABI string (note that compiling with `panic=abort` will still cause `panic!` to immediately From acb3fbd51fd119b5f300a84ac78dc2215b6b4414 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Sun, 29 May 2022 10:47:58 -0600 Subject: [PATCH 5/6] text annotations --- src/ffi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ffi.md b/src/ffi.md index ae548c60..5a1b3e21 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -753,7 +753,7 @@ extern "C-unwind" fn example() { This function (when compiled with `panic=unwind`) is permitted to unwind C++ stack frames. -``` +```text [Rust function with `catch_unwind`, which stops the unwinding] | ... @@ -789,7 +789,7 @@ extern "C-unwind" fn rust_passthrough() { A C++ function with a `try` block may invoke `rust_passthrough` and `catch` an exception thrown by `may_throw`. -``` +```text [C++ function with `try` block that invokes `rust_passthrough`] | ... From f2e0f4f5c332d92b5cf6edd1661073a9a4483a82 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 30 May 2022 16:58:25 +0900 Subject: [PATCH 6/6] Apply suggestions from code review --- src/ffi.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ffi.md b/src/ffi.md index 828a1f8a..9b0ff98e 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -743,7 +743,8 @@ mechanisms (notably C++'s `try`/`catch`). ### Rust `panic` with `"C-unwind"` -```rust + +```rust,ignore #[no_mangle] extern "C-unwind" fn example() { panic!("Uh oh"); @@ -771,7 +772,8 @@ If the C++ frames have objects, their destructors will be called. ### C++ `throw` with `"C-unwind"` -```rust + +```rust,ignore #[link(...)] extern "C-unwind" { // A C++ function that may throw an exception