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}