dynamic_plugin_macros/
lib.rs

1#![deny(missing_docs)]
2#![warn(clippy::pedantic)]
3
4//! # Macros for the [`dynamic-plugin`](https://docs.rs/dynamic-plugin/latest/dynamic_plugin/) crate.
5
6use std::hash::{Hash, Hasher};
7
8use def::PluginDefinition;
9use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use proc_macro_error2::abort;
12use quote::quote;
13use syn::{parse_macro_input, FnArg, ReturnType, Type};
14
15use crate::hasher::PluginSignatureHasher;
16
17mod def;
18mod hasher;
19mod implementation;
20
21/// Define an interface for a plugin. See the `dynamic_plugin` crate documentation for more.
22///
23/// ## Example
24/// ```ignore
25/// plugin_interface! {
26///     extern trait ExamplePlugin {
27///         /// Ask the plugin to do a thing
28///         fn do_a_thing();
29///         /// Say hello to a person
30///         fn say_hello(to: *const c_char) -> bool;
31///     }
32/// }
33/// ```
34#[proc_macro]
35pub fn plugin_interface(tokens: TokenStream) -> TokenStream {
36    let plugin_def = parse_macro_input!(tokens as PluginDefinition);
37    let plugin_ident = &plugin_def.name;
38
39    let mut hasher = PluginSignatureHasher::default();
40    plugin_def.hash(&mut hasher);
41    let hash = hasher.finish();
42    
43    let hash_debug: Option<TokenStream2> = {
44        #[cfg(feature = "debug-hashes")]
45        {
46            let hash_debug = format!("{hasher:?}");
47            Some(quote! {
48                #[no_mangle]
49                pub fn _dynamic_plugin_signature_unhashed() -> &'static str {
50                    #hash_debug
51                }
52            })
53        }
54        #[cfg(not(feature = "debug-hashes"))]
55        {
56            None
57        }
58    };
59
60    let host_impl = if cfg!(feature = "host") {
61        let funcs = plugin_def.functions.iter().map(|pf| {
62            let attributes = &pf.attributes;
63            let name = &pf.name;
64            let name_as_str = format!(r#"b"{name}""#).parse::<TokenStream2>().unwrap();
65            let args = &pf.arguments;
66            let mut arg_types = vec![];
67            let mut arg_names = vec![];
68            for arg in args {
69                if let FnArg::Typed(typed) = arg {
70                    arg_types.push(typed.ty.clone());
71                    arg_names.push(typed.pat.clone());
72                }
73            }
74            let ret = if let Some(typ) = &pf.return_type { quote! { #typ } } else { quote! { () } };
75            let sig = quote! { unsafe extern fn(#(#arg_types),*) -> #ret };
76            quote! {
77                #(#attributes)*
78                pub extern "C" fn #name(&self, #(#args),*) -> ::dynamic_plugin::Result<#ret> {
79                    unsafe {
80                        let func: ::dynamic_plugin::PluginLibrarySymbol<#sig> = self.library.get(#name_as_str)?;
81                        Ok(func(#(#arg_names),*))
82                    }
83                }
84            }
85        });
86
87        Some(quote! {
88            impl #plugin_ident {
89                #hash_debug
90
91                /// Search `path` to find compatible plugins.
92                pub fn find_plugins<P>(path: P) -> ::std::vec::Vec<Self>
93                where
94                    P: ::std::convert::AsRef<::std::path::Path>,
95                {
96                    let mut plugins = vec![];
97
98                    // Iterate through directory entries
99                    if let Ok(paths) = ::std::fs::read_dir(path) {
100                        for path in paths {
101                            if let Ok(path) = path {
102                                // Try to load each potential plugin. Catch errors and ignore.
103                                if let Ok(plugin) = Self::load_plugin_and_check(path.path()) {
104                                    plugins.push(plugin);
105                                }
106                            }
107                        }
108                    }
109
110                    plugins
111                }
112
113                /// Load the plugin at `path`
114                /// 
115                /// # Errors
116                /// 
117                /// - [`::dynamic_plugin::Error::NotAPlugin`] if the file provided is determined not to be a compatible (dynamic_plugin style) plugin.
118                /// - [`::dynamic_plugin::Error::InvalidPluginSignature`] if the signature does not match this loader.
119                pub fn load_plugin_and_check<P>(path: P) -> ::dynamic_plugin::Result<Self>
120                where
121                    P: ::std::convert::AsRef<::std::ffi::OsStr>,
122                {
123                    Self::load_plugin(path, true)
124                }
125
126                /// Load the plugin at `path`
127                /// 
128                /// # Errors
129                /// 
130                /// - [`::dynamic_plugin::Error::NotAPlugin`] if the file provided is determined not to be a compatible (dynamic_plugin style) plugin.
131                /// - [`::dynamic_plugin::Error::InvalidPluginSignature`] if `check_signature` is true and the signature does not match this loader.
132                pub fn load_plugin<P>(path: P, check_signature: bool) -> ::dynamic_plugin::Result<Self>
133                where
134                    P: ::std::convert::AsRef<::std::ffi::OsStr>,
135                {
136                    unsafe {
137                        // Attempt to load library
138                        let library = ::dynamic_plugin::PluginDynamicLibrary::new(path)?;
139
140                        // Check that signature function exists
141                        let func: ::dynamic_plugin::PluginLibrarySymbol<unsafe extern fn() -> u64> =
142                            library.get(b"_dynamic_plugin_signature").map_err(|_| ::dynamic_plugin::Error::NotAPlugin)?;
143                        if check_signature {
144                            // Check plugin library signature
145                            let hash = func();
146                            
147                            if hash != #hash {
148                                return ::dynamic_plugin::Result::Err(::dynamic_plugin::Error::InvalidPluginSignature);
149                            }
150                        }
151
152                        Ok(Self {
153                            library,
154                        })
155                    }
156                }
157
158                #(#funcs)*
159            }
160        })
161    } else {
162        None
163    };
164
165    quote! {
166        pub struct #plugin_ident {
167            library: ::dynamic_plugin::PluginDynamicLibrary,
168        }
169
170        impl #plugin_ident {
171            pub const PLUGIN_SIGNATURE: u64 = #hash;
172        }
173
174        #host_impl
175    }
176    .into()
177}
178
179/// Write an implementation for a plugin. See the `dynamic_plugin` crate documentation for more.
180///
181/// ## `attempt to compute '0_usize - 1_usize', which would overflow`
182///
183/// If you come across this compile-time error, this indicates that the implementation you are writing does not match the expected implementation for the plugin definition. Please check that you:
184///
185/// - Are using the correct definition.
186/// - Have all the functions you need to meet the definition.
187/// - That all the functions are named correctly (identically to the definition).
188/// - That all the function arguments are the same order and types as the definition.
189/// - That all the function return types are the same as the definition.
190///
191/// ## Example
192///
193/// ```ignore
194/// plugin_impl! {
195///     ExamplePlugin,
196///
197///     fn do_a_thing() {
198///         println!("A thing has been done!");
199///     }
200///
201///     fn say_hello(name: *const c_char) -> bool {
202///         unsafe {
203///             let name = CStr::from_ptr(name);
204///             println!("Hello, {}!", name.to_string_lossy());
205///         }
206///         true
207///     }
208/// }
209/// ```
210#[proc_macro]
211#[cfg(feature = "client")]
212pub fn plugin_impl(tokens: TokenStream) -> TokenStream {
213    use implementation::PluginImplementation;
214
215    let plugin = parse_macro_input!(tokens as PluginImplementation);
216    let target_plugin = &plugin.target_plugin;
217    let functions = plugin.functions.iter().map(|maybe_unsafe_func| {
218        let unsafe_ = maybe_unsafe_func._unsafe;
219        let func = &maybe_unsafe_func.func;
220        quote! {
221            #[no_mangle]
222            pub #unsafe_ extern "C" #func
223        }
224    });
225    let mut hasher = PluginSignatureHasher::default();
226    plugin.hash(&mut hasher);
227    let hash = hasher.finish();
228
229    let hash_debug: Option<TokenStream2> = {
230        #[cfg(feature = "debug-hashes")]
231        {
232            let hash_debug = format!("{hasher:?}");
233            Some(quote! {
234                #[no_mangle]
235                pub fn _dynamic_plugin_signature_unhashed() -> &'static str {
236                    #hash_debug
237                }
238            })
239        }
240        #[cfg(not(feature = "debug-hashes"))]
241        {
242            None
243        }
244    };
245
246    quote! {
247        ::dynamic_plugin::static_assert!(#target_plugin::PLUGIN_SIGNATURE == #hash, "The implementation signature does not match the definition. Check that all functions are implemented with the correct types.");
248
249        #[no_mangle]
250        pub extern "C" fn _dynamic_plugin_signature() -> u64 {
251            #hash
252        }
253
254        #hash_debug
255
256        #(#functions)*
257    }
258    .into()
259}
260
261fn hash_type<H: Hasher>(hasher: &mut H, ty: Type) {
262    match ty {
263        Type::Array(inner) => {
264            "arr".hash(hasher);
265            hash_type(hasher, *inner.elem);
266        }
267        Type::BareFn(inner) => {
268            "fn".hash(hasher);
269            for inp in inner.inputs {
270                hash_type(hasher, inp.ty);
271            }
272            if inner.variadic.is_some() {
273                abort!(
274                    inner.variadic,
275                    "Bare functions with variadics are not supported in plugin interfaces"
276                );
277            }
278            "->".hash(hasher);
279            match inner.output {
280                ReturnType::Default => "()".hash(hasher),
281                ReturnType::Type(_, ty) => hash_type(hasher, *ty),
282            }
283            ";".hash(hasher);
284        }
285        Type::Group(inner) => hash_type(hasher, *inner.elem),
286        Type::ImplTrait(inner) => abort!(inner, "Traits are supported in plugin interfaces"),
287        Type::Infer(inner) => abort!(
288            inner,
289            "Compiler inference is supported in plugin interfaces"
290        ),
291        Type::Macro(inner) => abort!(inner, "Macros are not supported in plugin interfaces"),
292        Type::Never(_) => "never".hash(hasher),
293        Type::Paren(inner) => hash_type(hasher, *inner.elem),
294        Type::Path(inner) => {
295            if inner.qself.is_some() {
296                abort!(
297                    inner,
298                    "Qualified types are not supported in plugin interfaces"
299                );
300            }
301            // Hash only last segment
302            let last_segment = inner.path.segments.last().unwrap();
303            if !last_segment.arguments.is_none() {
304                abort!(
305                    last_segment.arguments,
306                    "Types cannot be generic or require lifetimes in plugin interfaces"
307                );
308            }
309            last_segment.ident.hash(hasher);
310        }
311        Type::Ptr(inner) => hash_type(hasher, *inner.elem),
312        Type::Reference(inner) => abort!(
313            inner,
314            "References are not supported in plugin interfaces (use raw pointers instead)"
315        ),
316        Type::Slice(inner) => abort!(
317            inner,
318            "Slices are not supported in plugin interfaces (use raw pointers instead)"
319        ),
320        Type::TraitObject(inner) => {
321            abort!(inner, "Trait objects not supported in plugin interfaces")
322        }
323        Type::Tuple(inner) => abort!(inner, "Tuples not supported in plugin interfaces"),
324        Type::Verbatim(inner) => abort!(inner, "This type is not supported in plugin interfaces"),
325        _ => todo!("This type is not yet supported by dynamic-plugin"),
326    }
327}