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 {}