1#![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, proc_macro_error};
12use quote::quote;
13use syn::{parse_macro_input, FnArg, Lit, ReturnType, Type};
14
15use crate::hasher::PluginSignatureHasher;
16
17mod def;
18mod hasher;
19mod implementation;
20
21#[proc_macro]
35#[proc_macro_error]
36pub fn plugin_interface(tokens: TokenStream) -> TokenStream {
37 let plugin_def = parse_macro_input!(tokens as PluginDefinition);
38 let plugin_ident = &plugin_def.name;
39
40 let mut hasher = PluginSignatureHasher::default();
41 plugin_def.hash(&mut hasher);
42 let hash = hasher.finish();
43
44 let hash_debug: Option<TokenStream2> = {
45 #[cfg(feature = "debug-hashes")]
46 {
47 let hash_debug = format!("{hasher:?}");
48 Some(quote! {
49 #[no_mangle]
50 pub fn _dynamic_plugin_signature_unhashed() -> &'static str {
51 #hash_debug
52 }
53 })
54 }
55 #[cfg(not(feature = "debug-hashes"))]
56 {
57 None
58 }
59 };
60
61 let host_impl = if cfg!(feature = "host") {
62 let funcs = plugin_def.functions.iter().map(|pf| {
63 let attributes = &pf.attributes;
64 let name = &pf.name;
65 let name_as_str = format!(r#"b"{name}""#).parse::<TokenStream2>().unwrap();
66 let args = &pf.arguments;
67 let mut arg_types = vec![];
68 let mut arg_names = vec![];
69 for arg in args {
70 if let FnArg::Typed(typed) = arg {
71 arg_types.push(typed.ty.clone());
72 arg_names.push(typed.pat.clone());
73 }
74 }
75 let ret = if let Some(typ) = &pf.return_type { quote! { #typ } } else { quote! { () } };
76 let sig = quote! { unsafe extern fn(#(#arg_types),*) -> #ret };
77 quote! {
78 #(#attributes)*
79 pub extern "C" fn #name(&self, #(#args),*) -> ::dynamic_plugin::Result<#ret> {
80 unsafe {
81 let func: ::dynamic_plugin::PluginLibrarySymbol<#sig> = self.library.get(#name_as_str)?;
82 Ok(func(#(#arg_names),*))
83 }
84 }
85 }
86 });
87
88 let fn_checks = plugin_def.functions.iter().map(|f| {
89 let name_bytes = f.name.to_string();
90 quote! {
91 let _: ::dynamic_plugin::PluginLibrarySymbol<unsafe extern fn()> =
92 library.get(#name_bytes.as_bytes()).map_err(|_| ::dynamic_plugin::Error::NotAPlugin)?;
93 }
94 });
95
96 Some(quote! {
97 impl #plugin_ident {
98 #hash_debug
99
100 pub fn find_plugins<P>(path: P) -> ::std::vec::Vec<Self>
102 where
103 P: ::std::convert::AsRef<::std::path::Path>,
104 {
105 let mut plugins = vec![];
106
107 if let Ok(paths) = ::std::fs::read_dir(path) {
109 for path in paths {
110 if let Ok(path) = path {
111 if let Ok(plugin) = Self::load_plugin_and_check(path.path()) {
113 plugins.push(plugin);
114 }
115 }
116 }
117 }
118
119 plugins
120 }
121
122 pub fn load_plugin_and_check<P>(path: P) -> ::dynamic_plugin::Result<Self>
129 where
130 P: ::std::convert::AsRef<::std::ffi::OsStr>,
131 {
132 Self::load_plugin(path, true)
133 }
134
135 pub fn load_plugin<P>(path: P, check_signature: bool) -> ::dynamic_plugin::Result<Self>
142 where
143 P: ::std::convert::AsRef<::std::ffi::OsStr>,
144 {
145 unsafe {
146 let library = ::dynamic_plugin::PluginDynamicLibrary::new(path)?;
148
149 let func: ::dynamic_plugin::PluginLibrarySymbol<unsafe extern fn() -> u64> =
151 library.get(b"_dynamic_plugin_signature").map_err(|_| ::dynamic_plugin::Error::NotAPlugin)?;
152 if check_signature {
153 let hash = func();
155
156 if hash != #hash {
157 return ::dynamic_plugin::Result::Err(::dynamic_plugin::Error::InvalidPluginSignature);
158 }
159 }
160
161 Ok(Self {
162 library,
163 })
164 }
165 }
166
167 pub fn load_plugin_and_check_compat<P>(path: P) -> ::dynamic_plugin::Result<Self>
183 where
184 P: ::std::convert::AsRef<::std::ffi::OsStr>,
185 {
186 unsafe {
187 let library = ::dynamic_plugin::PluginDynamicLibrary::new(path)?;
189
190 #(#fn_checks)*
192
193 Ok(Self {
194 library,
195 })
196 }
197 }
198
199 #(#funcs)*
200 }
201 })
202 } else {
203 None
204 };
205
206 let definition =
207 {
208 let mut s = String::new();
209 for def::PluginFunction {
210 attributes,
211 name,
212 arguments,
213 return_type,
214 ..
215 } in &plugin_def.functions
216 {
217 for attr in attributes {
218 if attr.path().is_ident("doc") {
219 match &attr.meta {
220 syn::Meta::NameValue(inner) => {
221 if inner.path.is_ident("doc") {
222 if let syn::Expr::Lit(expr) = &inner.value {
223 if let Lit::Str(doc) = &expr.lit {
224 s.push_str(&format!("/// {}\n", doc.value().trim()));
225 }
226 }
227 }
228 }
229 _ => (),
230 }
231 }
232 }
233 s.push_str("fn ");
234 s.push_str(&name.to_string());
235 s.push('(');
236 for (idx, arg) in arguments.iter().enumerate() {
237 match arg {
238 FnArg::Receiver(..) => s.push_str("self"),
239 FnArg::Typed(ty) => {
240 s.push_str("_: ");
241 s.push_str(&crate::type_to_string(*ty.ty.clone()).expect(
242 "this should have failed earlier! please open a bug report!",
243 ));
244 }
245 };
246 if idx < arguments.len() - 1 {
247 s.push_str(", ");
248 }
249 }
250 s.push(')');
251 if let ::std::option::Option::Some(ret) = return_type {
252 s.push_str(" -> ");
253 s.push_str(
254 &crate::type_to_string(ret.clone())
255 .expect("this should have failed earlier! please open a bug report!"),
256 );
257 }
258 s.push_str(r#" { todo!("not yet implemented") }"#);
259 s.push('\n');
260 }
261 s
262 };
263 let func_sigs = plugin_def.functions.iter().map(|f| {
264 let func_name = f.name.to_string();
265 let args = f.arguments.iter().map(|a| match a {
266 FnArg::Receiver(..) => "self".to_string(),
267 FnArg::Typed(ty) => crate::type_to_string(*ty.ty.clone())
268 .expect("this should have failed earlier! please open a bug report!"),
269 });
270 let return_typ = if let Some(ty) = f
271 .return_type
272 .as_ref()
273 .map(|ty| crate::type_to_string(ty.clone()))
274 {
275 quote!(::std::option::Option::Some(#ty))
276 } else {
277 quote!(::std::option::Option::None)
278 };
279 quote! {
280 (#func_name, &[#(#args),*], #return_typ)
281 }
282 });
283
284 quote! {
285 pub struct #plugin_ident {
286 library: ::dynamic_plugin::PluginDynamicLibrary,
287 }
288
289 impl #plugin_ident {
290 pub const PLUGIN_SIGNATURE: u64 = #hash;
295 pub const PLUGIN_DEFINITION: &str = #definition;
299 pub const PLUGIN_FUNCTIONS: &[(&'static str, &[&'static str], ::std::option::Option<&'static str>)] = &[
302 #(#func_sigs),*
303 ];
304 }
305
306 #host_impl
307 }
308 .into()
309}
310
311#[proc_macro]
343#[cfg(feature = "client")]
344pub fn plugin_impl(tokens: TokenStream) -> TokenStream {
345 use implementation::PluginImplementation;
346
347 let plugin = parse_macro_input!(tokens as PluginImplementation);
348 let target_plugin = &plugin.target_plugin;
349 let functions = plugin.functions.iter().map(|maybe_unsafe_func| {
350 let unsafe_ = maybe_unsafe_func._unsafe;
351 let func = &maybe_unsafe_func.func;
352 quote! {
353 #[no_mangle]
354 pub #unsafe_ extern "C" #func
355 }
356 });
357 let mut hasher = PluginSignatureHasher::default();
358 plugin.hash(&mut hasher);
359 let hash = hasher.finish();
360
361 let hash_debug: Option<TokenStream2> = {
362 #[cfg(feature = "debug-hashes")]
363 {
364 let hash_debug = format!("{hasher:?}");
365 Some(quote! {
366 #[no_mangle]
367 pub fn _dynamic_plugin_signature_unhashed() -> &'static str {
368 #hash_debug
369 }
370 })
371 }
372 #[cfg(not(feature = "debug-hashes"))]
373 {
374 None
375 }
376 };
377
378 quote! {
379 ::dynamic_plugin::static_assert!(
380 #target_plugin::PLUGIN_SIGNATURE == #hash,
381 ::dynamic_plugin::const_concat!(
382 "\nThe implementation does not match the definition:\n\n",
383 #target_plugin::PLUGIN_DEFINITION
384 )
385 );
386
387 #[no_mangle]
388 pub extern "C" fn _dynamic_plugin_signature() -> u64 {
389 #hash
390 }
391
392 #hash_debug
393
394 #(#functions)*
395 }
396 .into()
397}
398
399fn type_to_string(ty: Type) -> Option<String> {
402 match ty {
403 Type::Array(inner) => Some(format!("[{}]", type_to_string(*inner.elem)?)),
404 Type::BareFn(inner) => {
405 let mut s = String::new();
406 s.push_str(r#"unsafe extern "C" fn("#);
407 let has_inputs = !inner.inputs.is_empty();
408 for inp in inner.inputs {
409 s.push_str(&type_to_string(inp.ty)?);
410 s.push_str(", ");
411 }
412 if has_inputs {
413 s.pop();
415 s.pop();
416 }
417 s.push(')');
418 if inner.variadic.is_some() {
419 return None;
420 }
421 match inner.output {
422 ReturnType::Default => (),
423 ReturnType::Type(_, ty) => s.push_str(&format!("-> {}", type_to_string(*ty)?)),
424 }
425 Some(s)
426 }
427 Type::Group(inner) => type_to_string(*inner.elem),
428 Type::Paren(inner) => type_to_string(*inner.elem),
429 Type::Ptr(inner) => type_to_string(*inner.elem),
430 Type::Never(_) => Some("!".to_string()),
431 Type::Path(inner) => {
432 if inner.qself.is_some() {
433 return None;
434 }
435 let last_segment = inner.path.segments.last().unwrap();
437 if !last_segment.arguments.is_none() {
438 return None;
439 }
440 Some(last_segment.ident.to_string())
441 }
442 Type::ImplTrait(_)
443 | Type::Infer(_)
444 | Type::Macro(_)
445 | Type::Reference(_)
446 | Type::Slice(_)
447 | Type::TraitObject(_)
448 | Type::Tuple(_)
449 | Type::Verbatim(_) => None,
450 _ => todo!("This type is not yet supported by dynamic-plugin"),
451 }
452}
453
454fn hash_type<H: Hasher>(hasher: &mut H, ty: Type) {
455 match ty {
456 Type::Array(inner) => {
457 "arr".hash(hasher);
458 hash_type(hasher, *inner.elem);
459 }
460 Type::BareFn(inner) => {
461 "fn".hash(hasher);
462 for inp in inner.inputs {
463 hash_type(hasher, inp.ty);
464 }
465 if inner.variadic.is_some() {
466 abort!(
467 inner.variadic,
468 "Bare functions with variadics are not supported in plugin interfaces"
469 );
470 }
471 "->".hash(hasher);
472 match inner.output {
473 ReturnType::Default => "()".hash(hasher),
474 ReturnType::Type(_, ty) => hash_type(hasher, *ty),
475 }
476 ";".hash(hasher);
477 }
478 Type::Group(inner) => hash_type(hasher, *inner.elem),
479 Type::ImplTrait(inner) => abort!(inner, "Traits are supported in plugin interfaces"),
480 Type::Infer(inner) => abort!(
481 inner,
482 "Compiler inference is supported in plugin interfaces"
483 ),
484 Type::Macro(inner) => abort!(inner, "Macros are not supported in plugin interfaces"),
485 Type::Never(_) => "never".hash(hasher),
486 Type::Paren(inner) => hash_type(hasher, *inner.elem),
487 Type::Path(inner) => {
488 if inner.qself.is_some() {
489 abort!(
490 inner,
491 "Qualified types are not supported in plugin interfaces"
492 );
493 }
494 let last_segment = inner.path.segments.last().unwrap();
496 if !last_segment.arguments.is_none() {
497 abort!(
498 last_segment.arguments,
499 "Types cannot be generic or require lifetimes in plugin interfaces"
500 );
501 }
502 last_segment.ident.hash(hasher);
503 }
504 Type::Ptr(inner) => hash_type(hasher, *inner.elem),
505 Type::Reference(inner) => abort!(
506 inner,
507 "References are not supported in plugin interfaces (use raw pointers instead)"
508 ),
509 Type::Slice(inner) => abort!(
510 inner,
511 "Slices are not supported in plugin interfaces (use raw pointers instead)"
512 ),
513 Type::TraitObject(inner) => {
514 abort!(inner, "Trait objects not supported in plugin interfaces")
515 }
516 Type::Tuple(inner) => abort!(inner, "Tuples not supported in plugin interfaces"),
517 Type::Verbatim(inner) => abort!(inner, "This type is not supported in plugin interfaces"),
518 _ => todo!("This type is not yet supported by dynamic-plugin"),
519 }
520}