lib: catch unwinds in RepoCheckoutFilter

This commit is contained in:
Felix Krull 2019-06-13 19:31:37 +02:00 committed by Colin Walters
parent 315cd5394e
commit 87b34be855
2 changed files with 36 additions and 4 deletions

View File

@ -91,7 +91,7 @@ impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOpt
if let Some(filter) = &self.filter { if let Some(filter) = &self.filter {
options.filter_user_data = filter.to_glib_none().0; 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( Stash(
@ -187,7 +187,7 @@ mod tests {
); );
assert_eq!((*ptr).unused_ints, [0; 6]); assert_eq!((*ptr).unused_ints, [0; 6]);
assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]); 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!( assert_eq!(
(*ptr).filter_user_data, (*ptr).filter_user_data,
options.filter.as_ref().unwrap().to_glib_none().0, options.filter.as_ref().unwrap().to_glib_none().0,

View File

@ -3,7 +3,10 @@ use glib::translate::*;
use glib_sys::gpointer; use glib_sys::gpointer;
use libc::c_char; use libc::c_char;
use ostree_sys::*; use ostree_sys::*;
use std::any::Any;
use std::panic::catch_unwind;
use std::path::{Path, PathBuf}; 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 /// 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. /// function is called for every directory and file in the dirtree.
@ -62,13 +65,12 @@ impl FromGlibPtrNone<gpointer> for &RepoCheckoutFilter {
/// ///
/// # Panics /// # Panics
/// If any parameter is a null pointer, the function 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, repo: *mut OstreeRepo,
path: *const c_char, path: *const c_char,
stat: *mut libc::stat, stat: *mut libc::stat,
user_data: gpointer, user_data: gpointer,
) -> OstreeRepoCheckoutFilterResult { ) -> OstreeRepoCheckoutFilterResult {
// TODO: handle unwinding
// We can't guarantee it's a valid pointer, but we can make sure it's not null. // We can't guarantee it's a valid pointer, but we can make sure it's not null.
assert!(!stat.is_null()); assert!(!stat.is_null());
let stat = &*stat; let stat = &*stat;
@ -85,6 +87,36 @@ pub(super) unsafe extern "C" fn filter_trampoline(
result.to_glib() 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<dyn Any>) {
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::<String>() {
s
} else {
"UNABLE TO SHOW VALUE OF PANIC"
}
};
eprintln!("Panic value: {}", msg);
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;