Panics and Exceptions in C++/Rust FFI bindings

This document explains what Crubit's rs_bindings_from_cc and cc_bindings_from_rs tools do to handle Rust panics and C++ exceptions.

Aborting on panic

“Alright so I'm panicking, what else is there to do?”
-- “The Hitchhiker’s Guide to the Galaxy” by Douglas Adams

Rust libraries built with -Cpanic=abort terminate their process upon panic and there isn‘t much else to do. Similarly, C++ libraries built with Clang’s -fno-exceptions flag terminate the process when a C++ exception is thrown.

In build environments that use the flags above, Rust panics and C++ exceptions don‘t unwind the callstack looking for appropriate handlers. This means that bindings generated by Crubit’s rs_bindings_from_cc and cc_bindings_from_rs tools don't need to do anything special to handle panics and/or exceptions. In particular, Rust calls into C++ (and C++ calls into Rust) can just use the “C” ABI and assume that no panics and no exceptions will ever need to unwind across the FFI boundary.

Currently Crubit-generated bindings only support -Cpanic=abort, -fno-exceptions environment. See the “Exceptions” section in the Google C++ Style Guide for discussion of some of the pros and cons of an -fno-exceptions environment.

Cross-language unwinding

TODO(b/254049425): Add support for cross-FFI unwinding of Rust panics and C++ exceptions.

“Who said anything about panicking?” snapped Arthur. “This is still just the culture shock. You wait till I‘ve settled down into the situation and found my bearings. Then I’ll start panicking.”
-- “The Hitchhiker’s Guide to the Galaxy” by Douglas Adams

Crubit doesn't currently support unwinding of Rust panics or C++ exceptions across the FFI boundary. In other words, the generated bindings are only safe when -Cpanic=abort and -fno-exceptions are used - other configurations may lead to Undefined Behavior:

  • C++ exception unwinding through Rust frames leads to Undefined Behavior (based on this part of RFC 2945).

  • Rust panic unwinding through C++ frames leads to Undefined Behavior. Note that RFC 2945 defines some of that behavior: “with the panic=unwind runtime, panic! will cause an abort if it would otherwise “escape” from a function defined with extern "C".”

Below is an incomplete, tentative list of steps needed to add Crubit support for cross-language unwinding of panics and exceptions:

  • Support building Crubit's automated tests with -Cpanic=unwind and/or -fexceptions. Use this support to add test coverage for:

    • Propagating a Rust panic across C++ frames and catching it in another Rust frame.
    • Propagating a C++ exception across Rust frames and catching it in another C++ frame.
  • If one or both languages don't abort on unwind, then modify the generated Rust code to use the new "C-unwind" ABI string proposed by RFC 2945 (instead of using the old "C" ABI string):

    • rs_bindings_from_cc should use "C-unwind" ABI when declaring C++ thunks in mod detail in the generated ..._rs_api.rs
    • cc_bindings_from_rs should use "C-unwind" ABI when defining C++ thunks in ..._cc_api_impl.rs
  • Investigate if Crubit needs to modify the generated C++ code. For example:

    • C++ thunk definitions generated by rs_bindings_from_cc in ..._rs_api_impl.cc.
    • C++ declarations generated by cc_bindings_from_rs in ..._cc_api.h.