diff --git a/rust-bindings/rust/src/repo_checkout_at_options.rs b/rust-bindings/rust/src/repo_checkout_at_options.rs index 4b49e2c6..a1cd37d9 100644 --- a/rust-bindings/rust/src/repo_checkout_at_options.rs +++ b/rust-bindings/rust/src/repo_checkout_at_options.rs @@ -91,7 +91,7 @@ impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOpt if let Some(filter) = &self.filter { options.filter_user_data = filter.to_glib_none().0; - options.filter = Some(repo_checkout_filter::filter_trampoline); + options.filter = Some(repo_checkout_filter::filter_trampoline_unwindsafe); } Stash( @@ -187,7 +187,7 @@ mod tests { ); assert_eq!((*ptr).unused_ints, [0; 6]); assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]); - assert!((*ptr).filter == Some(repo_checkout_filter::filter_trampoline)); + assert!((*ptr).filter == Some(repo_checkout_filter::filter_trampoline_unwindsafe)); assert_eq!( (*ptr).filter_user_data, options.filter.as_ref().unwrap().to_glib_none().0, diff --git a/rust-bindings/rust/src/repo_checkout_at_options/repo_checkout_filter.rs b/rust-bindings/rust/src/repo_checkout_at_options/repo_checkout_filter.rs index 44a3a7f3..cb8190e4 100644 --- a/rust-bindings/rust/src/repo_checkout_at_options/repo_checkout_filter.rs +++ b/rust-bindings/rust/src/repo_checkout_at_options/repo_checkout_filter.rs @@ -3,7 +3,10 @@ use glib::translate::*; use glib_sys::gpointer; use libc::c_char; use ostree_sys::*; +use std::any::Any; +use std::panic::catch_unwind; use std::path::{Path, PathBuf}; +use std::process::abort; /// A filter callback to decide which files to checkout from a [Repo](struct.Repo.html). The /// function is called for every directory and file in the dirtree. @@ -62,13 +65,12 @@ impl FromGlibPtrNone for &RepoCheckoutFilter { /// /// # Panics /// If any parameter is a null pointer, the function panics. -pub(super) unsafe extern "C" fn filter_trampoline( +unsafe extern "C" fn filter_trampoline( repo: *mut OstreeRepo, path: *const c_char, stat: *mut libc::stat, user_data: gpointer, ) -> OstreeRepoCheckoutFilterResult { - // TODO: handle unwinding // We can't guarantee it's a valid pointer, but we can make sure it's not null. assert!(!stat.is_null()); let stat = &*stat; @@ -85,6 +87,36 @@ pub(super) unsafe extern "C" fn filter_trampoline( result.to_glib() } +pub(super) unsafe extern "C" fn filter_trampoline_unwindsafe( + repo: *mut OstreeRepo, + path: *const c_char, + stat: *mut libc::stat, + user_data: gpointer, +) -> OstreeRepoCheckoutFilterResult { + // Unwinding across an FFI boundary is Undefined Behavior and we have no other way to communicate + // the error. We abort() safely to avoid further problems. + let result = catch_unwind(move || filter_trampoline(repo, path, stat, user_data)); + result.unwrap_or_else(|panic| { + print_panic(panic); + abort() + }) +} + +fn print_panic(panic: Box) { + eprintln!("A Rust callback invoked by C code panicked."); + eprintln!("Unwinding across FFI boundaries is Undefined Behavior so abort() will be called."); + let msg = { + if let Some(s) = panic.as_ref().downcast_ref::<&str>() { + s + } else if let Some(s) = panic.as_ref().downcast_ref::() { + s + } else { + "UNABLE TO SHOW VALUE OF PANIC" + } + }; + eprintln!("Panic value: {}", msg); +} + #[cfg(test)] mod tests { use super::*;