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