Repository: asomers/mockall (Rust)

Issue:
extern C functions need to be C-unwind in Rust 1.81.0
A recent change to rustc (https://github.com/rust-lang/rust/issues/74990) changed the behavior of unwinding from a Rust function across an `extern C` ABI change.  Now, that will cause an abort.  This is causing Mockall's CI tests to fail.  The possible solutions are either:
* Require users to specify `extern C-unwind` instead.  This is annoying for users, and it might not be correct for all non-mock functions.
* Automatically define the mock functions as `C-unwind`.  This is much better.  However, it causes two new problems:
  - A function pointer to the original C function will now have a different type from a function pointer to the mock function, because the ABI is part of the function pointer's type.
  - `C-unwind` does not yet work for variadic functions.
* Somehow figure out how to make Mockall work with `panic=abort`.  It's not a problem, as long as no tests are `should_panic` and no tests ever fail :wink: .


Discussion:

Patch:
diff --git a/.cirrus.yml b/.cirrus.yml
index 87d843ad..cc9fe4f8 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -14,7 +14,7 @@ common: &COMMON
 task:
   name: MSRV
   container:
-    image: rust:1.70.0
+    image: rust:1.71.0
   cargo_lock_script:
     - cp Cargo.lock.msrv Cargo.lock
   << : *COMMON
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26e46842..eacc3b54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,13 +13,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Changed
 
-- Raised MSRV to 1.70.0 to remove `lazy_static` dependency
-  ([#550](https://github.com/asomers/mockall/pull/550))
+- Raised MSRV to 1.71.0 due to the `C-unwind` ABI.
+  ([#585](https://github.com/asomers/mockall/pull/585))
 
 - No longer poison a Context object's internal `Mutex` when panicing.  This
   requires the "nightly" feature.
   ([#527](https://github.com/asomers/mockall/pull/527))
 
+### Fixed
+
+- Fixed panicing within mocked `extern "C"` functions, for example due to
+  unsatisfied expectations, with Rust 1.81.0 or newer.
+  ([#585](https://github.com/asomers/mockall/pull/585))
+
 ## [ 0.12.1 ] - 2023-12-21
 
 ### Fixed
diff --git a/README.md b/README.md
index 6dbace06..dfc12898 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ See the [API docs](https://docs.rs/mockall) for more information.
 
 # Minimum Supported Rust Version (MSRV)
 
-Mockall is supported on Rust 1.70.0 and higher.  Mockall's MSRV will not be
+Mockall is supported on Rust 1.71.0 and higher.  Mockall's MSRV will not be
 changed in the future without bumping the major or minor version.
 
 # License
diff --git a/mockall/Cargo.toml b/mockall/Cargo.toml
index fbed4983..7e58e825 100644
--- a/mockall/Cargo.toml
+++ b/mockall/Cargo.toml
@@ -9,7 +9,7 @@ categories = ["development-tools::testing"]
 keywords = ["mock", "mocking", "testing"]
 documentation = "https://docs.rs/mockall"
 edition = "2021"
-rust-version = "1.70"
+rust-version = "1.71"
 description = """
 A powerful mock object library for Rust.
 """
diff --git a/mockall_derive/src/mock_item.rs b/mockall_derive/src/mock_item.rs
index bcbe70fb..daf8ac5a 100644
--- a/mockall_derive/src/mock_item.rs
+++ b/mockall_derive/src/mock_item.rs
@@ -95,7 +95,28 @@ impl From<MockableModule> for MockItemModule {
 
                             // Set the ABI to match the ForeignMod's ABI
                             // for proper function linkage with external code.
-                            f.sig.abi = Some(ifm.abi.clone());
+                            //
+                            // BUT, use C-unwind instead of C, so we can panic
+                            // from the mock function (rust-lang/rust #74990)
+                            //
+                            // BUT, don't use C-unwind for variadic functions,
+                            // because it doesn't work yet (rust-lang/rust
+                            // #126836)
+                            let needs_c_unwind = if let Some(n) = &ifm.abi.name
+                            {
+                                n.value() == "C" && f.sig.variadic.is_none()
+                            } else {
+                                false
+                            };
+                            f.sig.abi = Some(if needs_c_unwind {
+                                Abi {
+                                    extern_token:ifm.abi.extern_token,
+                                    name: Some(LitStr::new("C-unwind",
+                                                           ifm.abi.name.span()))
+                                }
+                            } else {
+                                ifm.abi.clone()
+                            });
 
                             let mf = mock_function::Builder::new(&f.sig, &f.vis)
                                 .attrs(&f.attrs)


Test patch:
diff --git a/mockall/tests/automock_foreign_c.rs b/mockall/tests/automock_foreign_c.rs
index 07eb47da..864bc77f 100644
--- a/mockall/tests/automock_foreign_c.rs
+++ b/mockall/tests/automock_foreign_c.rs
@@ -47,6 +47,6 @@ fn c_abi() {
     let _m = FOO_MTX.lock();
     let ctx = mock_ffi::foo_context();
     ctx.expect().returning(i64::from);
-    let p: unsafe extern "C" fn(u32) -> i64 = mock_ffi::foo;
+    let p: unsafe extern "C-unwind" fn(u32) -> i64 = mock_ffi::foo;
     assert_eq!(42, unsafe{p(42)});
 }
diff --git a/mockall/tests/automock_foreign_c_variadic.rs b/mockall/tests/automock_foreign_c_variadic.rs
index 71a62ed5..5b6e4c26 100644
--- a/mockall/tests/automock_foreign_c_variadic.rs
+++ b/mockall/tests/automock_foreign_c_variadic.rs
@@ -13,11 +13,24 @@ pub mod ffi {
     }
 }
 
+#[cfg(feature = "nightly")]
+static FOO_MTX: std::sync::Mutex<()> = std::sync::Mutex::new(());
+
 #[test]
 #[cfg(feature = "nightly")]
 #[cfg_attr(miri, ignore)]
 fn mocked_c_variadic() {
+    let _m = FOO_MTX.lock();
     let ctx = mock_ffi::foo_context();
     ctx.expect().returning(|x, y| x * y);
     assert_eq!(6, unsafe{mock_ffi::foo(2, 3, 1, 4, 1)});
 }
+
+#[test]
+#[cfg(feature = "nightly")]
+#[cfg_attr(miri, ignore)]
+#[ignore = "https://github.com/rust-lang/rust/issues/126836"]
+fn panics() {
+    let _m = FOO_MTX.lock();
+    unsafe{ mock_ffi::foo(1,2,3) };
+}