Merge branch 'fix/ffi-callback-panic-containment' into 'master'
Contain panics in FFI callbacks See merge request videolan/vlc-rs!26merge-requests/26/merge
commit
9671a7e2b1
15
src/core.rs
15
src/core.rs
|
|
@ -10,7 +10,7 @@ use std::i32;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use libc::{c_void, c_char, c_int};
|
use libc::{c_void, c_char, c_int};
|
||||||
use vlc_sys as sys;
|
use vlc_sys as sys;
|
||||||
use crate::tools::{to_cstr, from_cstr, from_cstr_ref};
|
use crate::tools::{to_cstr, from_cstr, from_cstr_ref, invoke_callback};
|
||||||
use crate::enums::*;
|
use crate::enums::*;
|
||||||
|
|
||||||
/// Retrieve libvlc version.
|
/// Retrieve libvlc version.
|
||||||
|
|
@ -131,7 +131,9 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set logging callback
|
/// Set logging callback.
|
||||||
|
///
|
||||||
|
/// Panics from this callback are caught and do not unwind into LibVLC.
|
||||||
pub fn set_log<F: Fn(LogLevel, Log, Cow<str>) + Send + 'static>(&self, f: F) {
|
pub fn set_log<F: Fn(LogLevel, Log, Cow<str>) + Send + 'static>(&self, f: F) {
|
||||||
let cb: Box<Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static>> = Box::new(Box::new(f));
|
let cb: Box<Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static>> = Box::new(Box::new(f));
|
||||||
|
|
||||||
|
|
@ -157,13 +159,14 @@ impl Drop for Instance {
|
||||||
const BUF_SIZE: usize = 1024; // Write log message to the buffer by vsnprintf.
|
const BUF_SIZE: usize = 1024; // Write log message to the buffer by vsnprintf.
|
||||||
unsafe extern "C" fn logging_cb(
|
unsafe extern "C" fn logging_cb(
|
||||||
data: *mut c_void, level: c_int, ctx: *const sys::libvlc_log_t, fmt: *const c_char, args: sys::VaList) {
|
data: *mut c_void, level: c_int, ctx: *const sys::libvlc_log_t, fmt: *const c_char, args: sys::VaList) {
|
||||||
|
invoke_callback(|| {
|
||||||
let f: &Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static> = ::std::mem::transmute(data);
|
let f: &Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static> = ::std::mem::transmute(data);
|
||||||
let mut buf: [c_char; BUF_SIZE] = [0; BUF_SIZE];
|
let mut buf: [c_char; BUF_SIZE] = [0; BUF_SIZE];
|
||||||
|
|
||||||
sys::vsnprintf(buf.as_mut_ptr(), BUF_SIZE.try_into().unwrap(), fmt, args);
|
sys::vsnprintf(buf.as_mut_ptr(), BUF_SIZE.try_into().unwrap(), fmt, args);
|
||||||
|
|
||||||
f((level as u32).into(), Log{ptr: ctx}, from_cstr_ref(buf.as_ptr()).unwrap());
|
f((level as u32).into(), Log{ptr: ctx}, from_cstr_ref(buf.as_ptr()).unwrap());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of module description.
|
/// List of module description.
|
||||||
|
|
@ -328,6 +331,9 @@ impl<'a> EventManager<'a> {
|
||||||
unsafe { sys::libvlc_event_detach(self.ptr, event_type as i32, Some(event_manager_callback), registered_callback) }
|
unsafe { sys::libvlc_event_detach(self.ptr, event_type as i32, Some(event_manager_callback), registered_callback) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attach an event callback.
|
||||||
|
///
|
||||||
|
/// Panics from this callback are caught and do not unwind into LibVLC.
|
||||||
pub fn attach<F>(&self, event_type: EventType, callback: F) -> Result<*mut c_void, ()>
|
pub fn attach<F>(&self, event_type: EventType, callback: F) -> Result<*mut c_void, ()>
|
||||||
where F: Fn(Event, VLCObject) + Send + 'static
|
where F: Fn(Event, VLCObject) + Send + 'static
|
||||||
{
|
{
|
||||||
|
|
@ -357,9 +363,11 @@ impl<'a> EventManager<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn event_manager_callback(pe: *const sys::libvlc_event_t, data: *mut c_void) {
|
unsafe extern "C" fn event_manager_callback(pe: *const sys::libvlc_event_t, data: *mut c_void) {
|
||||||
|
invoke_callback(|| {
|
||||||
let f: &Box<dyn Fn(Event, VLCObject) + Send + 'static> = ::std::mem::transmute(data);
|
let f: &Box<dyn Fn(Event, VLCObject) + Send + 'static> = ::std::mem::transmute(data);
|
||||||
|
|
||||||
f(conv_event(pe), VLCObject{ ptr: (*pe).p_obj });
|
f(conv_event(pe), VLCObject{ ptr: (*pe).p_obj });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert c-style libvlc_event_t to Event
|
// Convert c-style libvlc_event_t to Event
|
||||||
|
|
@ -579,4 +587,3 @@ impl Log {
|
||||||
self.ptr
|
self.ptr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::Media;
|
||||||
use crate::EventManager;
|
use crate::EventManager;
|
||||||
use libc::{c_void, c_uint};
|
use libc::{c_void, c_uint};
|
||||||
use crate::enums::{State, Position};
|
use crate::enums::{State, Position};
|
||||||
|
use crate::tools::invoke_callback;
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
|
|
||||||
/// A LibVLC media player plays one media (usually in a custom drawable).
|
/// A LibVLC media player plays one media (usually in a custom drawable).
|
||||||
|
|
@ -87,6 +88,9 @@ impl MediaPlayer {
|
||||||
unsafe{ sys::libvlc_media_player_stop(self.ptr) };
|
unsafe{ sys::libvlc_media_player_stop(self.ptr) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set audio callbacks.
|
||||||
|
///
|
||||||
|
/// Panics from these callbacks are caught and do not unwind into LibVLC.
|
||||||
pub fn set_callbacks<F>(
|
pub fn set_callbacks<F>(
|
||||||
&self,
|
&self,
|
||||||
play: F,
|
play: F,
|
||||||
|
|
@ -337,29 +341,38 @@ struct AudioCallbacksData {
|
||||||
|
|
||||||
unsafe extern "C" fn audio_cb_play(
|
unsafe extern "C" fn audio_cb_play(
|
||||||
data: *mut c_void, samples: *const c_void, count: c_uint, pts: i64) {
|
data: *mut c_void, samples: *const c_void, count: c_uint, pts: i64) {
|
||||||
|
invoke_callback(|| {
|
||||||
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
||||||
(data.play)(samples, count, pts);
|
(data.play)(samples, count, pts);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn audio_cb_pause(data: *mut c_void, pts: i64) {
|
unsafe extern "C" fn audio_cb_pause(data: *mut c_void, pts: i64) {
|
||||||
|
invoke_callback(|| {
|
||||||
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
||||||
(data.pause.as_ref().unwrap())(pts);
|
(data.pause.as_ref().unwrap())(pts);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn audio_cb_resume(data: *mut c_void, pts: i64) {
|
unsafe extern "C" fn audio_cb_resume(data: *mut c_void, pts: i64) {
|
||||||
|
invoke_callback(|| {
|
||||||
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
||||||
(data.resume.as_ref().unwrap())(pts);
|
(data.resume.as_ref().unwrap())(pts);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn audio_cb_flush(data: *mut c_void, pts: i64) {
|
unsafe extern "C" fn audio_cb_flush(data: *mut c_void, pts: i64) {
|
||||||
|
invoke_callback(|| {
|
||||||
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
||||||
(data.flush.as_ref().unwrap())(pts);
|
(data.flush.as_ref().unwrap())(pts);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn audio_cb_drain(data: *mut c_void) {
|
unsafe extern "C" fn audio_cb_drain(data: *mut c_void) {
|
||||||
|
invoke_callback(|| {
|
||||||
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
let data: &AudioCallbacksData = transmute(data as *mut AudioCallbacksData);
|
||||||
(data.drain.as_ref().unwrap())();
|
(data.drain.as_ref().unwrap())();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
|
@ -367,4 +380,3 @@ pub struct TrackDescription {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
26
src/tools.rs
26
src/tools.rs
|
|
@ -5,6 +5,7 @@
|
||||||
use std::ffi::{CString, CStr, NulError};
|
use std::ffi::{CString, CStr, NulError};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
use libc::c_char;
|
use libc::c_char;
|
||||||
|
|
||||||
// Convert String to CString.
|
// Convert String to CString.
|
||||||
|
|
@ -41,3 +42,28 @@ pub fn path_to_cstr(path: &Path) -> Result<CString, NulError> {
|
||||||
|
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Invoke a Rust callback from a foreign callback without unwinding into C.
|
||||||
|
///
|
||||||
|
/// LibVLC callback APIs do not provide an error channel, so panics are caught
|
||||||
|
/// and ignored after Rust's panic hook has run.
|
||||||
|
pub(crate) fn invoke_callback<F>(callback: F)
|
||||||
|
where F: FnOnce()
|
||||||
|
{
|
||||||
|
let _ = catch_unwind(AssertUnwindSafe(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::invoke_callback;
|
||||||
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invoke_callback_contains_panics() {
|
||||||
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||||
|
invoke_callback(|| panic!("callback panic"));
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue