Skip to main content

clack_plugin/extensions/
wrapper.rs

1//! Utilities to manipulate Plugin instances from an FFI context.
2//!
3//! These unsafe utilities are targeted at extension implementors. Most `clack-plugin` users do not
4//! have to use those utilities to use extensions, see `clack-extensions` instead.
5
6use crate::host::HostSharedHandle;
7use crate::internal_utils::UnsafeOptionCell;
8use crate::plugin::{Plugin, PluginAudioProcessor, PluginBoxInner, PluginError, logging};
9use crate::process::PluginAudioConfiguration;
10use clap_sys::ext::log::*;
11use clap_sys::plugin::clap_plugin;
12use std::borrow::Cow;
13use std::error::Error;
14use std::ffi::{CStr, CString};
15use std::fmt::{Display, Formatter};
16use std::io::Write;
17use std::panic::AssertUnwindSafe;
18use std::pin::Pin;
19use std::ptr::NonNull;
20
21#[cfg(not(test))]
22#[allow(unused)]
23pub(crate) use std::panic::catch_unwind as handle_panic;
24
25#[cfg(test)]
26#[inline]
27#[allow(unused)]
28pub(crate) fn handle_panic<F: FnOnce() -> R, R>(f: F) -> std::thread::Result<R> {
29    Ok(f())
30}
31
32/// A wrapper around a `clack` plugin of a given type.
33///
34/// This wrapper allows access to a plugin's [`Shared`](Plugin::Shared),
35/// [`MainThread`](Plugin::MainThread), and [`AudioProcessor`](Plugin::AudioProcessor) structs, while
36/// also handling common FFI issues, such as error management and unwind safety.
37///
38/// The only way to access an instance of `PluginWrapper` is through the
39/// [`handle`](PluginWrapper::handle) function.
40pub struct PluginWrapper<'a, P: Plugin> {
41    audio_processor: UnsafeOptionCell<P::AudioProcessor<'a>>,
42    main_thread: P::MainThread<'a>,
43    shared: Pin<Box<P::Shared<'a>>>,
44    host: HostSharedHandle<'a>,
45}
46
47impl<'a, P: Plugin> PluginWrapper<'a, P> {
48    /// # Safety
49    ///
50    /// `shared` and `main_thread` must be related and correctly initialized.
51    pub(crate) unsafe fn new(
52        host: HostSharedHandle<'a>,
53        shared: Pin<Box<P::Shared<'a>>>,
54        main_thread: P::MainThread<'a>,
55    ) -> Self {
56        Self {
57            host,
58            shared,
59            main_thread,
60            audio_processor: UnsafeOptionCell::new(),
61        }
62    }
63
64    /// # Safety
65    /// Caller must ensure this method is only called on main thread and has exclusivity
66    pub(crate) unsafe fn activate(
67        &self,
68        audio_config: PluginAudioConfiguration,
69    ) -> Result<(), PluginWrapperError> {
70        if self.is_active() {
71            return Err(PluginWrapperError::ActivatedPlugin);
72        }
73
74        let shared = &*(self.shared() as *const _);
75        let host = self.host;
76
77        let processor = P::AudioProcessor::activate(
78            host.as_audio_processor_unchecked(),
79            self.main_thread(),
80            shared,
81            audio_config,
82        )?;
83
84        // SAFETY: It is up to the caller to ensure this is never called simultaneously with deactivate()
85        self.audio_processor.put(processor);
86
87        Ok(())
88    }
89
90    /// # Safety
91    /// Caller must ensure this method is only called on main thread, and has exclusivity on it
92    pub(crate) unsafe fn deactivate(&self) -> Result<(), PluginWrapperError> {
93        match self.audio_processor.take() {
94            None => Err(PluginWrapperError::DeactivatedPlugin),
95            Some(audio_processor) => {
96                audio_processor.deactivate(self.main_thread());
97
98                Ok(())
99            }
100        }
101    }
102
103    /// Returns if the current plugin has been activated or not.
104    #[inline]
105    pub fn is_active(&self) -> bool {
106        self.audio_processor.is_some()
107    }
108
109    /// Returns a reference to a plugin's [`Shared`](Plugin::Shared) struct.
110    ///
111    /// This is always safe to call in any context, since the `Shared` struct is required to
112    /// implement `Sync`.
113    #[inline]
114    pub fn shared(&self) -> &P::Shared<'a> {
115        &self.shared
116    }
117
118    /// Returns a raw, non-null pointer to the plugin's [`MainThread`](Plugin::MainThread)
119    /// struct.
120    ///
121    /// # Safety
122    /// The caller must ensure this method is only called on the main thread.
123    ///
124    /// The pointer is safe to mutably dereference, as long as the caller ensures it is not being
125    /// aliased, as per usual safety rules.
126    #[inline]
127    pub unsafe fn main_thread(&self) -> &'_ P::MainThread<'a> {
128        &self.main_thread
129    }
130
131    /// Returns a raw, non-null pointer to the plugin's audio processor
132    /// (i.e. [`Plugin`](PluginAudioProcessor)) struct.
133    ///
134    /// # Errors
135    ///
136    /// This method will return `PluginWrapperError::DeactivatedPlugin` if the plugin has not been
137    /// activated before calling this method.
138    ///
139    /// This is an extra safety check which ensures that hosts correctly activated plugins before
140    /// calling any audio-thread method.
141    ///
142    /// # Safety
143    /// The caller must ensure this method is only called on the audio thread.
144    ///
145    /// The pointer is safe to mutably dereference, as long as the caller ensures it is not being
146    /// aliased, as per usual safety rules.
147    #[inline]
148    pub unsafe fn audio_processor(
149        &self,
150    ) -> Result<NonNull<P::AudioProcessor<'a>>, PluginWrapperError> {
151        self.audio_processor
152            .as_ptr()
153            // SAFETY: pointer has been created from reference, it cannot be null.
154            .ok_or(PluginWrapperError::DeactivatedPlugin)
155    }
156
157    /// Provides a shared reference to a plugin wrapper of a given type, to the given handler
158    /// closure.
159    ///
160    /// Besides providing a reference, this function does a few extra safety checks:
161    ///
162    /// * The given `clap_plugin` pointer is null-checked, as well as some other host-provided
163    ///   pointers;
164    /// * The handler is wrapped in [`std::panic::catch_unwind`];
165    /// * Any [`PluginWrapperError`] returned by the handler is caught.
166    ///
167    /// If any of the above safety check fails, an error message is logged (using the standard CLAP
168    /// logging extension). If logging is unavailable or fails for any reason, the error message is
169    /// written to `stderr` as a fallback.
170    ///
171    /// Note that some safety checks (e.g. the `clap_plugin` pointer null-checks) may result in the
172    /// closure never being called, and an error being returned only. Users of this function must
173    /// not rely on the completion of this closure for safety, and must handle this function
174    /// returning `None` gracefully.
175    ///
176    /// If all goes well, the return value of the handler closure is forwarded and returned by this
177    /// function.
178    ///
179    /// # Errors
180    ///
181    /// If any safety check failed, or any error or panic occurred inside the handler closure, this
182    /// function returns `None`, and the error message is logged.
183    ///
184    /// # Safety
185    ///
186    /// The given plugin type `P` **must** be the correct type for the received pointer. Otherwise,
187    /// incorrect casts will occur, which will lead to Undefined Behavior.
188    ///
189    /// The `plugin` pointer must also point to a valid instance of `clap_plugin`, as provided by
190    /// the CLAP Host. While this function does a couple of simple safety checks, only a few common
191    /// cases are actually covered (i.e. null checks), and those **must not** be relied upon: those
192    /// checks only exist to help debugging faulty hosts.
193    ///
194    /// # Example
195    ///
196    /// This is the implementation of the [`on_main_thread`](crate::plugin::PluginMainThread::on_main_thread)
197    /// callback's C wrapper.
198    ///
199    /// This method is guaranteed by the CLAP specification to be only called on the main thread.
200    ///
201    /// ```
202    /// use clap_sys::plugin::clap_plugin;
203    /// use clack_plugin::plugin::{Plugin, PluginMainThread};
204    /// use clack_plugin::extensions::wrapper::PluginWrapper;
205    ///
206    /// unsafe extern "C" fn on_main_thread<P: Plugin>(plugin: *const clap_plugin) {
207    ///   PluginWrapper::<P>::handle(plugin, |p| {
208    ///     p.main_thread().on_main_thread();
209    ///     Ok(())
210    ///   });
211    /// }
212    /// ```
213    pub unsafe fn handle<T, F>(plugin: *const clap_plugin, handler: F) -> Option<T>
214    where
215        F: FnOnce(&PluginWrapper<'a, P>) -> Result<T, PluginWrapperError>,
216    {
217        match Self::from_raw(plugin).and_then(|p| Self::handle_panic(p, handler)) {
218            Ok(value) => Some(value),
219            Err(e) => {
220                logging::plugin_log::<P>(plugin, &e);
221
222                None
223            }
224        }
225    }
226
227    /// # Safety
228    /// The plugin pointer must be valid
229    pub(crate) unsafe fn handle_plugin_data<T, F>(
230        plugin: *const clap_plugin,
231        handler: F,
232    ) -> Option<T>
233    where
234        F: FnOnce(NonNull<PluginBoxInner<'a, P>>) -> Result<T, PluginWrapperError>,
235    {
236        match Self::plugin_data_from_raw(plugin).and_then(|p| Self::handle_panic(p, handler)) {
237            Ok(value) => Some(value),
238            Err(e) => {
239                logging::plugin_log::<P>(plugin, &e);
240
241                None
242            }
243        }
244    }
245
246    /// # Safety
247    /// The plugin pointer must be valid
248    unsafe fn from_raw<'p>(raw: *const clap_plugin) -> Result<&'p Self, PluginWrapperError> {
249        raw.as_ref()
250            .ok_or(PluginWrapperError::NullPluginInstance)?
251            .plugin_data
252            .cast::<PluginBoxInner<'a, P>>()
253            .as_ref()
254            .ok_or(PluginWrapperError::AlreadyDestroyed)?
255            .wrapper()
256    }
257
258    /// # Safety
259    /// The plugin pointer must be valid
260    unsafe fn plugin_data_from_raw(
261        raw: *const clap_plugin,
262    ) -> Result<NonNull<PluginBoxInner<'a, P>>, PluginWrapperError> {
263        let data = raw
264            .as_ref()
265            .ok_or(PluginWrapperError::NullPluginInstance)?
266            .plugin_data
267            .cast::<PluginBoxInner<'a, P>>();
268
269        NonNull::new(data).ok_or(PluginWrapperError::AlreadyDestroyed)
270    }
271
272    #[inline]
273    fn handle_panic<Pa, T, F>(parameter: Pa, handler: F) -> Result<T, PluginWrapperError>
274    where
275        F: FnOnce(Pa) -> Result<T, PluginWrapperError>,
276    {
277        handle_panic(AssertUnwindSafe(|| handler(parameter)))
278            .map_err(|_| PluginWrapperError::Panic)?
279    }
280}
281
282// SAFETY: the wrapper itself can be shared and used across threads, accessing any inner part that
283// isn't requires unsafe.
284unsafe impl<P: Plugin> Send for PluginWrapper<'_, P> {}
285// SAFETY: the wrapper itself can be shared and used across threads, accessing any inner part that
286// isn't requires unsafe.
287unsafe impl<P: Plugin> Sync for PluginWrapper<'_, P> {}
288
289/// Errors raised by a [`PluginWrapper`].
290#[derive(Debug)]
291#[non_exhaustive]
292pub enum PluginWrapperError {
293    /// The `clap_plugin` raw pointer was null.
294    NullPluginInstance,
295    /// The `clap_plugin.plugin_data` raw pointer was null, which indicates the instance was already
296    /// destroyed.
297    AlreadyDestroyed,
298    /// An unexpectedly null raw pointer was encountered.
299    ///
300    /// The given string may contain more information about which pointer was found to be null.
301    NulPtr(&'static str),
302    /// An invalid parameter value was encountered.
303    ///
304    /// The given string may contain more information about which parameter was found to be invalid.
305    InvalidParameter(&'static str),
306    /// The plugin was not properly initialized (i.e. `init` was not called).
307    UninitializedPlugin,
308    /// The host tried to call a plugin method while `init` is running.
309    PluginCalledDuringInitialization,
310    /// The host tried to call a plugin method while `destroy` is running.
311    Destroying,
312    /// The plugin's initialization (`init`) has failed.
313    InitializationAlreadyFailed,
314    /// The plugin is already initialized (i.e. a second call to `init` was attempted).
315    AlreadyInitialized,
316    /// An attempt was made to call `activate` on an already activated plugin.
317    ActivatedPlugin,
318    /// An attempt was made to call an audio-thread function while the plugin was deactivated
319    /// (e.g. without previously calling `activate`).
320    DeactivatedPlugin,
321    /// A function which requires the plugin to be deactivated was called while the plugin was still
322    /// active.
323    DeactivationRequiredForFunction(&'static str),
324    /// The plugin panicked during a function call.
325    Panic,
326    /// A given [`PluginError`] was raised during a function call.
327    Plugin(PluginError),
328    /// Bad UTF-8.
329    StringEncoding(std::str::Utf8Error),
330    /// Plugin returned a malformed C string.
331    InvalidCString(std::ffi::NulError),
332    /// Encountered an error while trying to format another [`PluginWrapperError`]
333    ErrorFormatError(std::io::Error),
334    /// A generic or custom error of a given severity.
335    Error(clap_log_severity, Box<dyn Error>),
336    /// A constant string message to be displayed.
337    Message(clap_log_severity, &'static str),
338}
339
340impl PluginWrapperError {
341    /// Returns the severity of this error.
342    ///
343    /// This is mainly useful for logging.
344    ///
345    /// # Example
346    ///
347    /// ```
348    /// use clap_sys::ext::log::CLAP_LOG_PLUGIN_MISBEHAVING;
349    /// use clack_plugin::extensions::wrapper::PluginWrapperError;
350    /// let error = PluginWrapperError::Panic;
351    ///
352    /// assert_eq!(error.severity(), CLAP_LOG_PLUGIN_MISBEHAVING);
353    /// ```
354    pub fn severity(&self) -> clap_log_severity {
355        match self {
356            PluginWrapperError::Plugin(_) => CLAP_LOG_ERROR,
357            PluginWrapperError::Panic => CLAP_LOG_PLUGIN_MISBEHAVING,
358            PluginWrapperError::Error(s, _) => *s,
359            PluginWrapperError::Message(s, _) => *s,
360            _ => CLAP_LOG_HOST_MISBEHAVING,
361        }
362    }
363
364    /// If this error holds an [`io::Error`](std::io::Error), returns its raw OS error.
365    ///
366    /// If not applicable, this returns `None` instead.
367    pub fn os_error_code(&self) -> Option<i32> {
368        let e = match self {
369            PluginWrapperError::Error(_, e) => e,
370            _ => return None,
371        };
372
373        e.downcast_ref::<std::io::Error>()?.raw_os_error()
374    }
375
376    /// Formats this error into either a [`CStr`] or [`CString`].
377    ///
378    /// This will handle formatting failures and return an appropriate message in that case, so
379    /// unlike the [`Display`] implementation, this will always return a displayable error message.
380    pub fn format_cstr(&self) -> Cow<'static, CStr> {
381        let mut buf = Vec::new();
382        match buf.write_fmt(format_args!("{}", self)) {
383            Ok(()) => {}
384            Err(e) => {
385                // Stop this here to avoid infinitely failing to format the failing error message
386                if matches!(self, PluginWrapperError::ErrorFormatError(_)) {
387                    return c"Failed to format error".into();
388                }
389
390                return PluginWrapperError::ErrorFormatError(e).format_cstr();
391            }
392        };
393
394        // Truncate until the first NULL byte just in case. This is better than returning no error message.
395        if let Some(idx) = buf.iter().position(|&b| b == 0) {
396            buf.truncate(idx);
397        }
398
399        // This should not be able to happen thanks to the above check, but let's put it here just in case
400        let Ok(str) = CString::new(buf) else {
401            return c"Error format failure: NulError".into();
402        };
403
404        str.into()
405    }
406
407    /// Returns a closure that maps an error to a [`PluginWrapperError::Error`] error of a given
408    /// severity.
409    ///
410    /// This is a useful helper method when paired with [`Result::map_err`].
411    ///
412    /// # Example
413    /// ```
414    /// use clap_sys::ext::log::CLAP_LOG_PLUGIN_MISBEHAVING;
415    /// use clack_plugin::extensions::wrapper::PluginWrapperError;
416    ///
417    /// let x: Result<(), _> = Err(std::env::VarError::NotPresent); // Some random error type
418    /// let clap_error = x.map_err(PluginWrapperError::with_severity(CLAP_LOG_PLUGIN_MISBEHAVING));
419    ///
420    /// assert_eq!(clap_error.unwrap_err().severity(), CLAP_LOG_PLUGIN_MISBEHAVING);
421    /// ```
422    #[inline]
423    pub fn with_severity<E: 'static + Error>(
424        severity: clap_log_severity,
425    ) -> impl Fn(E) -> PluginWrapperError {
426        move |e| PluginWrapperError::Error(severity, Box::new(e))
427    }
428}
429
430impl From<PluginError> for PluginWrapperError {
431    #[inline]
432    fn from(e: PluginError) -> Self {
433        PluginWrapperError::Plugin(e)
434    }
435}
436
437impl Display for PluginWrapperError {
438    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
439        use PluginWrapperError::*;
440
441        match self {
442            NullPluginInstance => {
443                f.write_str("Plugin method was called with null clap_plugin pointer")
444            }
445            AlreadyDestroyed => f.write_str(
446                "Plugin instance was already destroyed (clap_plugin.plugin_data pointer is null)",
447            ),
448            PluginCalledDuringInitialization => {
449                f.write_str("Host tried to call plugin function during initialization")
450            }
451            InitializationAlreadyFailed => f.write_str("Plugin initialization has already failed"),
452            AlreadyInitialized => f.write_str("Plugin is already initialized"),
453            Destroying => f.write_str("Plugin is being destroyed"),
454            NulPtr(ptr_name) => {
455                write!(f, "Plugin method was called with null {ptr_name} pointer")
456            }
457            InvalidParameter(p) => {
458                write!(f, "Received invalid parameter '{p}'")
459            }
460            UninitializedPlugin => f.write_str("Plugin was not properly initialized before use"),
461            ActivatedPlugin => f.write_str("Plugin was already activated"),
462            DeactivatedPlugin => {
463                f.write_str("Plugin was not activated before calling a audio-thread method")
464            }
465            DeactivationRequiredForFunction(function) => write!(
466                f,
467                "Host attempted to call '{function}' while plugin was still active"
468            ),
469            StringEncoding(e) => {
470                write!(
471                    f,
472                    "Encountered string containing invalid UTF-8 at position {}.",
473                    e.valid_up_to()
474                )
475            }
476            InvalidCString(e) => {
477                write!(
478                    f,
479                    "Encountered string containing a NUL byte at position {}.",
480                    e.nul_position()
481                )
482            }
483            ErrorFormatError(e) => {
484                write!(f, "Error while formatting plugin error: '{e}'")
485            }
486            Plugin(e) => Display::fmt(&e, f),
487            Error(_, e) => Display::fmt(e, f),
488            Message(_, msg) => f.write_str(msg),
489            Panic => f.write_str("Plugin panicked"),
490        }
491    }
492}
493
494impl Error for PluginWrapperError {}