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