dynamic_plugin_macros/
lib.rs1#![deny(missing_docs)]
2#![warn(clippy::pedantic)]
3
4use 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#[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 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 if let Ok(paths) = ::std::fs::read_dir(path) {
100 for path in paths {
101 if let Ok(path) = path {
102 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 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 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 let library = ::dynamic_plugin::PluginDynamicLibrary::new(path)?;
139
140 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 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#[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 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}