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