Skip to main content

clack_host/extensions/
wrapper.rs

1//! Helper utilities to help implementing the host side of custom CLAP extensions.
2
3use crate::plugin::DestroyLock;
4use crate::prelude::*;
5use crate::util::UnsafeOptionCell;
6use clap_sys::ext::log::{
7    CLAP_LOG_ERROR, CLAP_LOG_HOST_MISBEHAVING, CLAP_LOG_PLUGIN_MISBEHAVING, clap_log_severity,
8};
9use clap_sys::host::clap_host;
10use clap_sys::plugin::clap_plugin;
11use std::borrow::Cow;
12use std::error::Error;
13use std::ffi::{CStr, CString};
14use std::fmt::{Display, Formatter};
15use std::io::Write;
16use std::panic::AssertUnwindSafe;
17use std::pin::Pin;
18use std::ptr::NonNull;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::sync::{Arc, Once, OnceLock};
21
22#[cfg(not(test))]
23#[allow(unused)]
24use std::panic::catch_unwind as handle_panic;
25
26#[cfg(test)]
27#[inline]
28#[allow(unused)]
29fn handle_panic<F: FnOnce() -> R, R>(f: F) -> std::thread::Result<R> {
30    Ok(f())
31}
32
33pub(crate) mod descriptor;
34mod logging;
35
36/// A wrapper around a `clack` host implementation of a given type.
37///
38/// This wrapper allows access to a host's [`Shared`](HostHandlers::Shared),
39/// [`MainThread`](HostHandlers::MainThread), and [`AudioProcessor`](HostHandlers::AudioProcessor) handler structs, while
40/// also handling common FFI issues, such as error management and unwind safety.
41///
42/// The only way to access an instance of `HostWrapper` is through the
43/// [`handle`](HostWrapper::handle) function.
44// Safety note: once this type is constructed, a pointer to it will be given to the plugin instance,
45// which means we can never move this again. This must always exist in a Pin.
46pub struct HostWrapper<H: HostHandlers> {
47    audio_processor: UnsafeOptionCell<<H as HostHandlers>::AudioProcessor<'static>>,
48    main_thread: UnsafeOptionCell<<H as HostHandlers>::MainThread<'static>>,
49    shared: Pin<Box<<H as HostHandlers>::Shared<'static>>>,
50
51    // Init stuff
52    init_guard: Once,
53    init_started: AtomicBool,
54    plugin_ptr: OnceLock<NonNull<clap_plugin>>,
55
56    // Drop stuff
57    destroy_lock: Arc<DestroyLock>,
58}
59
60// SAFETY: The only non-thread-safe methods on this type are unsafe
61unsafe impl<H: HostHandlers> Send for HostWrapper<H> {}
62// SAFETY: The only non-thread-safe methods on this type are unsafe
63unsafe impl<H: HostHandlers> Sync for HostWrapper<H> {}
64
65impl<H: HostHandlers> HostWrapper<H> {
66    /// Provides a shared reference to a host wrapper of a given type, to the given handler
67    /// closure.
68    ///
69    /// Besides providing a reference, this function does a few extra safety checks:
70    ///
71    /// * The given `clap_host` pointer is null-checked, as well as some other host-provided
72    ///   pointers;
73    /// * The handler is wrapped in [`std::panic::catch_unwind`];
74    /// * Any [`HostWrapperError`] returned by the handler is caught.
75    ///
76    /// If any of the above safety check fails, an error message is logged (using the standard CLAP
77    /// logging extension). If logging is unavailable or fails for any reason, the error message is
78    /// written to `stderr` as a fallback.
79    ///
80    /// Note that some safety checks (e.g. the `clap_host` pointer null-checks) may result in the
81    /// closure never being called, and an error being returned only. Users of this function must
82    /// not rely on the completion of this closure for safety, and must handle this function
83    /// returning `None` gracefully.
84    ///
85    /// If all goes well, the return value of the handler closure is forwarded and returned by this
86    /// function.
87    ///
88    /// # Errors
89    ///
90    /// If any safety check failed, or any error or panic occurred inside the handler closure, this
91    /// function returns `None`, and the error message is logged.
92    ///
93    /// # Safety
94    ///
95    /// The given host wrapper type `H` **must** be the correct type for the received pointer. Otherwise,
96    /// incorrect casts will occur, which will lead to Undefined Behavior.
97    ///
98    /// The `host` pointer must also point to a valid instance of `clap_host`, as created by
99    /// the CLAP Host. While this function does a couple of simple safety checks, only a few common
100    /// cases are actually covered (i.e. null checks), and those **must not** be relied upon for safety: those
101    /// checks only exist to help debugging.
102    pub unsafe fn handle<T, F>(host: *const clap_host, handler: F) -> Option<T>
103    where
104        F: FnOnce(&HostWrapper<H>) -> Result<T, HostWrapperError>,
105    {
106        let result = Self::from_raw(host).and_then(|h| {
107            Self::handle_panic(h, |h| {
108                h.ensure_initializing_called();
109                handler(h)
110            })
111        });
112
113        match result {
114            Ok(value) => Some(value),
115            Err(e) => {
116                logging::host_log(host, &e);
117
118                None
119            }
120        }
121    }
122
123    /// Returns a raw, non-null pointer to the host's ([`MainThread`](HostHandlers::MainThread)) struct.
124    ///
125    /// # Safety
126    /// The caller must ensure this method is only called on the main thread.
127    ///
128    /// The pointer is safe to mutably dereference, as long as the caller ensures it is not being
129    /// aliased, as per usual safety rules.
130    #[inline]
131    pub unsafe fn main_thread(&self) -> NonNull<<H as HostHandlers>::MainThread<'_>> {
132        self.main_thread.as_ptr_unchecked().cast()
133    }
134
135    /// Returns a raw, non-null pointer to the host's [`AudioProcessor`](HostHandlers::AudioProcessor)
136    /// struct.
137    ///
138    /// # Errors
139    ///
140    /// This will return [`HostWrapperError::DeactivatedPlugin`] if the plugin was not actually
141    /// activated.
142    ///
143    /// # Safety
144    ///
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<<H as HostHandlers>::AudioProcessor<'_>>, HostWrapperError> {
153        let ptr = self
154            .audio_processor
155            .as_ptr()
156            .ok_or(HostWrapperError::DeactivatedPlugin)?;
157
158        Ok(ptr.cast())
159    }
160
161    /// Returns a shared reference to the host's [`Shared`](HostHandlers::Shared) struct.
162    #[inline]
163    pub fn shared(&self) -> &<H as HostHandlers>::Shared<'_> {
164        // SAFETY: This type guarantees shared is never used mutably
165        unsafe { shrink_shared_ref::<H>(&self.shared) }
166    }
167
168    pub(crate) fn new<FS, FH>(shared: FS, main_thread: FH) -> Pin<Arc<Self>>
169    where
170        FS: for<'s> FnOnce(&'s ()) -> <H as HostHandlers>::Shared<'s>,
171        FH: for<'s> FnOnce(
172            &'s <H as HostHandlers>::Shared<'s>,
173        ) -> <H as HostHandlers>::MainThread<'s>,
174    {
175        // We use Arc only because Box<T> implies Unique<T>, which is not the case since the plugin
176        // will effectively hold a shared pointer to this.
177        let mut wrapper = Arc::new(Self {
178            audio_processor: UnsafeOptionCell::new(),
179            main_thread: UnsafeOptionCell::new(),
180            shared: Box::pin(shared(&())),
181            init_guard: Once::new(),
182            init_started: AtomicBool::new(false),
183            plugin_ptr: OnceLock::new(),
184            destroy_lock: Arc::new(DestroyLock::new()),
185        });
186
187        // PANIC: we have the only Arc copy of this wrapper data.
188        let wrapper_mut = Arc::get_mut(&mut wrapper).unwrap();
189
190        // SAFETY: This type guarantees main thread data cannot outlive shared
191        unsafe {
192            wrapper_mut
193                .main_thread
194                .put(main_thread(extend_shared_ref(&wrapper_mut.shared)));
195        }
196
197        // SAFETY: wrapper is the only reference to the data, we can guarantee it will remain pinned
198        // until drop happens.
199        unsafe { Pin::new_unchecked(wrapper) }
200    }
201
202    /// # Safety
203    /// The pointer and the plugin instance it points to must remain valid for the lifetime of this
204    /// wrapper.
205    pub(crate) unsafe fn created(&self, instance: NonNull<clap_plugin>) {
206        let _ = self.plugin_ptr.set(instance);
207    }
208
209    /// # Safety
210    /// This must only be called on the main thread. User must ensure the provided instance pointer
211    /// is valid.
212    pub(crate) unsafe fn instantiated(&self) {
213        self.ensure_initializing_called();
214        let instance = *self.plugin_ptr.get().unwrap();
215
216        // SAFETY: At this point there is no way main_thread could not have been set.
217        self.main_thread()
218            .as_mut()
219            .initialized(InitializedPluginHandle::new(
220                self.destroy_lock.clone(),
221                instance,
222            ));
223    }
224
225    pub(crate) fn start_instance_destroy(&self) {
226        self.destroy_lock.start_destroying();
227    }
228
229    /// # Safety
230    /// The user must ensure this is only called on the main thread, and not concurrently
231    /// to any other main-thread OR audio-thread method.
232    #[inline]
233    pub(crate) unsafe fn setup_audio_processor<FA>(
234        &self,
235        audio_processor: FA,
236    ) -> Result<(), PluginInstanceError>
237    where
238        FA: for<'a> FnOnce(
239            &'a <H as HostHandlers>::Shared<'a>,
240            &mut <H as HostHandlers>::MainThread<'a>,
241        ) -> <H as HostHandlers>::AudioProcessor<'a>,
242    {
243        if self.audio_processor.is_some() {
244            return Err(PluginInstanceError::AlreadyActivatedPlugin);
245        }
246
247        self.audio_processor.put(audio_processor(
248            // SAFETY: Shared lives at least as long as the audio processor does.
249            unsafe { extend_shared_ref(&self.shared) },
250            // SAFETY: The user enforces that this is only called on the main thread, and
251            // non-concurrently to any other main-thread method.
252            unsafe { self.main_thread().cast().as_mut() },
253        ));
254        Ok(())
255    }
256
257    /// # Safety
258    /// The user must ensure this is only called on the main thread, and not concurrently
259    /// to any other main-thread OR audio-thread method.
260    #[inline]
261    pub(crate) unsafe fn teardown_audio_processor<T>(
262        &self,
263        drop: impl for<'s> FnOnce(
264            <H as HostHandlers>::AudioProcessor<'s>,
265            &mut <H as HostHandlers>::MainThread<'s>,
266        ) -> T,
267    ) -> Result<T, PluginInstanceError> {
268        // SAFETY: The user enforces that this is called and non-concurrently to any other audio-thread method.
269        match self.audio_processor.take() {
270            None => Err(PluginInstanceError::DeactivatedPlugin),
271            Some(audio_processor) => Ok(drop(
272                audio_processor,
273                // SAFETY: The user enforces that this is only called on the main thread, and
274                // non-concurrently to any other main-thread method.
275                unsafe { self.main_thread().cast().as_mut() },
276            )),
277        }
278    }
279
280    /// # Safety
281    /// the user must ensure this is not called concurrently
282    /// to [`Self::setup_audio_processor`] or [`Self::teardown_audio_processor`]
283    #[inline]
284    pub(crate) fn is_active(&self) -> bool {
285        self.audio_processor.is_some()
286    }
287
288    pub(crate) fn handle_panic<T, F, Pa>(param: Pa, handler: F) -> Result<T, HostWrapperError>
289    where
290        F: FnOnce(Pa) -> Result<T, HostWrapperError>,
291    {
292        handle_panic(AssertUnwindSafe(|| handler(param))).map_err(|_| HostWrapperError::Panic)?
293    }
294
295    /// # Safety
296    /// The host pointer must be valid (but can be null)
297    unsafe fn from_raw<'a>(raw: *const clap_host) -> Result<&'a Self, HostWrapperError> {
298        raw.as_ref()
299            .ok_or(HostWrapperError::NullHostInstance)?
300            .host_data
301            .cast::<HostWrapper<H>>()
302            .as_ref()
303            .ok_or(HostWrapperError::NullHostData)
304    }
305
306    fn ensure_initializing_called(&self) {
307        // This can only happen if the plugin tried to call a host method before init().
308        let Some(ptr) = self.plugin_ptr.get().copied() else {
309            return;
310        };
311
312        self.init_guard.call_once_force(|_| {
313            let result =
314                self.init_started
315                    .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);
316
317            // The comparison succeeded, and false was indeed the bool's value
318            if result == Ok(false) {
319                // SAFETY: The pointer is guaranteed to be valid by the caller of created()
320                let handle =
321                    unsafe { InitializingPluginHandle::new(self.destroy_lock.clone(), ptr) };
322                self.shared().initializing(handle);
323            }
324        })
325    }
326}
327
328/// Errors raised by a [`HostWrapper`].
329#[derive(Debug)]
330#[non_exhaustive]
331pub enum HostWrapperError {
332    /// An invalid parameter value was encountered.
333    ///
334    /// The given string may contain more information about which parameter was found to be invalid.
335    InvalidParameter(&'static str),
336    /// The raw `clap_host` pointer was NULL.
337    NullHostInstance,
338    /// The raw `clap_host.host_data` field was NULL.
339    NullHostData,
340    /// A wrapped implementation panicked.
341    Panic,
342    /// Tried to call a method that requires the plugin to be activated, but it wasn't.
343    DeactivatedPlugin,
344    /// Encountered an error while trying to format another [`HostWrapperError`]
345    ErrorFormatError(std::io::Error),
346    /// A generic, type-erased error.
347    Error(Box<dyn Error + 'static>),
348    /// A constant string message to be displayed.
349    Message(&'static str),
350}
351
352impl HostWrapperError {
353    fn severity(&self) -> clap_log_severity {
354        match self {
355            HostWrapperError::NullHostInstance => CLAP_LOG_PLUGIN_MISBEHAVING,
356            HostWrapperError::InvalidParameter(_) => CLAP_LOG_PLUGIN_MISBEHAVING,
357            HostWrapperError::NullHostData => CLAP_LOG_HOST_MISBEHAVING,
358            HostWrapperError::Panic => CLAP_LOG_HOST_MISBEHAVING,
359            HostWrapperError::DeactivatedPlugin => CLAP_LOG_PLUGIN_MISBEHAVING,
360            HostWrapperError::ErrorFormatError(_) => CLAP_LOG_HOST_MISBEHAVING,
361            _ => CLAP_LOG_ERROR,
362        }
363    }
364
365    /// Formats this error into either a [`CStr`] or [`CString`].
366    ///
367    /// This will handle formatting failures and return an appropriate message in that case, so
368    /// unlike the [`Display`] implementation, this will always return a displayable error message.
369    pub fn format_cstr(&self) -> Cow<'static, CStr> {
370        let mut buf = Vec::new();
371        match buf.write_fmt(format_args!("{}", self)) {
372            Ok(()) => {}
373            Err(e) => {
374                // Stop this here to avoid infinitely failing to format the failing error message
375                if matches!(self, HostWrapperError::ErrorFormatError(_)) {
376                    return c"Failed to format error".into();
377                }
378
379                return HostWrapperError::ErrorFormatError(e).format_cstr();
380            }
381        };
382
383        // Truncate until the first NULL byte just in case. This is better than returning no error message.
384        if let Some(idx) = buf.iter().position(|&b| b == 0) {
385            buf.truncate(idx);
386        }
387
388        // This should not be able to happen thanks to the above check, but let's put it here just in case
389        let Ok(str) = CString::new(buf) else {
390            return c"Error format failure: NulError".into();
391        };
392
393        str.into()
394    }
395}
396
397impl Display for HostWrapperError {
398    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
399        use HostWrapperError::*;
400        match self {
401            NullHostInstance => f.write_str("Host instance pointer is NULL"),
402            NullHostData => f.write_str("Host data pointer is NULL"),
403            InvalidParameter(s) => write!(f, "Invalid parameter: {s}"),
404            Panic => f.write_str("Host callback panicked"),
405            DeactivatedPlugin => {
406                f.write_str("Tried to call an audio-thread method while the plugin was deactivated")
407            }
408            Message(s) => f.write_str(s),
409            Error(e) => Display::fmt(e, f),
410            ErrorFormatError(e) => {
411                write!(f, "Error while formatting host error: '{e}'")
412            }
413        }
414    }
415}
416
417impl From<HostError> for HostWrapperError {
418    #[inline]
419    fn from(value: HostError) -> Self {
420        match value {
421            HostError::Error(e) => Self::Error(e),
422            HostError::Message(e) => Self::Message(e),
423        }
424    }
425}
426
427/// # Safety
428/// The user MUST ensure the Shared ref lives long enough
429unsafe fn extend_shared_ref<'a, H: SharedHandler<'a>>(shared: &H) -> &'a H {
430    &*(shared as *const _)
431}
432
433/// # Safety
434/// The user MUST prevent this reference to be written anywhere
435unsafe fn shrink_shared_ref<'a, 'instance, H: HostHandlers>(
436    shared: &'a H::Shared<'instance>,
437) -> &'a H::Shared<'a> {
438    let original_ptr = shared as *const H::Shared<'instance>;
439    let transmuted_ptr: *const H::Shared<'a> = original_ptr.cast();
440
441    &*transmuted_ptr
442}