Skip to content

Commit 3a43983

Browse files
BatmanAoDJohnTitor
andauthored
Changes for c_unwind (#365)
Co-authored-by: Yuki Okushi <[email protected]>
1 parent 16ad267 commit 3a43983

File tree

1 file changed

+107
-6
lines changed

1 file changed

+107
-6
lines changed

src/ffi.md

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -718,17 +718,118 @@ void register(int (*f)(int (*)(int), int)) {
718718
719719
No `transmute` required!
720720
721-
## FFI and panics
721+
## FFI and unwinding
722722
723-
It’s important to be mindful of `panic!`s when working with FFI. A `panic!`
724-
across an FFI boundary is undefined behavior. If you’re writing code that may
725-
panic, you should run it in a closure with [`catch_unwind`]:
723+
It’s important to be mindful of unwinding when working with FFI. Each
724+
non-`Rust` ABI comes in two variants, one with `-unwind` suffix and one without. If
725+
you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI
726+
boundary, that boundary must use the appropriate `-unwind` ABI string (note
727+
that compiling with `panic=abort` will still cause `panic!` to immediately
728+
abort the process, regardless of which ABI is specified by the function that
729+
`panic`s).
730+
731+
Conversely, if you do not expect unwinding to cross an ABI boundary, use one of
732+
the non-`unwind` ABI strings (other than `Rust`, which always permits
733+
unwinding). If an unwinding operation does encounter an ABI boundary that is
734+
not permitted to unwind, the behavior depends on the source of the unwinding
735+
(Rust `panic` or a foreign exception):
736+
737+
* `panic` will cause the process to safely abort.
738+
* A foreign exception entering Rust will cause undefined behavior.
739+
740+
Note that the interaction of `catch_unwind` with foreign exceptions **is
741+
undefined**, as is the interaction of `panic` with foreign exception-catching
742+
mechanisms (notably C++'s `try`/`catch`).
743+
744+
### Rust `panic` with `"C-unwind"`
745+
746+
<!-- ignore: using unstable feature -->
747+
```rust,ignore
748+
#[no_mangle]
749+
extern "C-unwind" fn example() {
750+
panic!("Uh oh");
751+
}
752+
```
753+
754+
This function (when compiled with `panic=unwind`) is permitted to unwind C++
755+
stack frames.
756+
757+
```text
758+
[Rust function with `catch_unwind`, which stops the unwinding]
759+
|
760+
...
761+
|
762+
[C++ frames]
763+
| ^
764+
| (calls) | (unwinding
765+
v | goes this
766+
[Rust function `example`] | way)
767+
| |
768+
+--- rust function panics --+
769+
```
770+
771+
If the C++ frames have objects, their destructors will be called.
772+
773+
### C++ `throw` with `"C-unwind"`
774+
775+
<!-- ignore: using unstable feature -->
776+
```rust,ignore
777+
#[link(...)]
778+
extern "C-unwind" {
779+
// A C++ function that may throw an exception
780+
fn may_throw();
781+
}
782+
783+
#[no_mangle]
784+
extern "C-unwind" fn rust_passthrough() {
785+
let b = Box::new(5);
786+
unsafe { may_throw(); }
787+
println!("{:?}", &b);
788+
}
789+
```
790+
791+
A C++ function with a `try` block may invoke `rust_passthrough` and `catch` an
792+
exception thrown by `may_throw`.
793+
794+
```text
795+
[C++ function with `try` block that invokes `rust_passthrough`]
796+
|
797+
...
798+
|
799+
[Rust function `rust_passthrough`]
800+
| ^
801+
| (calls) | (unwinding
802+
v | goes this
803+
[C++ function `may_throw`] | way)
804+
| |
805+
+--- C++ function throws ----+
806+
```
807+
808+
If `may_throw` does throw an exception, `b` will be dropped. Otherwise, `5`
809+
will be printed.
810+
811+
### `panic` can be stopped at an ABI boundary
812+
813+
```rust
814+
#[no_mangle]
815+
extern "C" fn assert_nonzero(input: u32) {
816+
assert!(input != 0)
817+
}
818+
```
819+
820+
If `assert_nonzero` is called with the argument `0`, the runtime is guaranteed
821+
to (safely) abort the process, whether or not compiled with `panic=abort`.
822+
823+
### Catching `panic` preemptively
824+
825+
If you are writing Rust code that may panic, and you don't wish to abort the
826+
process if it panics, you must use [`catch_unwind`]:
726827

727828
```rust
728829
use std::panic::catch_unwind;
729830

730831
#[no_mangle]
731-
pub extern fn oh_no() -> i32 {
832+
pub extern "C" fn oh_no() -> i32 {
732833
let result = catch_unwind(|| {
733834
panic!("Oops!");
734835
});
@@ -742,7 +843,7 @@ fn main() {}
742843
```
743844

744845
Please note that [`catch_unwind`] will only catch unwinding panics, not
745-
those who abort the process. See the documentation of [`catch_unwind`]
846+
those that abort the process. See the documentation of [`catch_unwind`]
746847
for more information.
747848

748849
[`catch_unwind`]: ../std/panic/fn.catch_unwind.html

0 commit comments

Comments
 (0)