1use proc_macro::TokenStream;
21use proc_macro_error2::abort;
22use proc_macro_error2::abort_call_site;
23use proc_macro_error2::proc_macro_error;
24use quote::ToTokens;
25use quote::quote;
26use syn::ItemFn;
27use syn::Path;
28use syn::ReturnType;
29use syn::Type;
30use syn::parse_macro_input;
31use syn::parse_quote;
32
33#[proc_macro_attribute]
34#[proc_macro_error]
35pub fn stacksafe(args: TokenStream, item: TokenStream) -> TokenStream {
36 let mut crate_path: Option<Path> = None;
37
38 let arg_parser = syn::meta::parser(|meta| {
39 if meta.path.is_ident("crate") {
40 crate_path = Some(meta.value()?.parse()?);
41 Ok(())
42 } else {
43 Err(meta.error(format!(
44 "unknown attribute parameter `{}`",
45 meta.path
46 .get_ident()
47 .map_or("unknown".to_string(), |i| i.to_string())
48 )))
49 }
50 });
51 parse_macro_input!(args with arg_parser);
52
53 let item_fn: ItemFn = match syn::parse(item.clone()) {
54 Ok(item) => item,
55 Err(_) => abort_call_site!("#[stacksafe] can only be applied to functions"),
56 };
57
58 if item_fn.sig.asyncness.is_some() {
59 abort!(
60 item_fn.sig.asyncness,
61 "#[stacksafe] does not support async functions"
62 );
63 }
64
65 let mut item_fn = item_fn;
66 let ret = match &item_fn.sig.output {
67 ReturnType::Type(_, ty) if matches!(**ty, Type::ImplTrait(_)) => ReturnType::Default,
70 _ => item_fn.sig.output.clone(),
71 };
72
73 let stacksafe_crate = crate_path.unwrap_or_else(|| parse_quote!(::stacksafe));
74 let block = &item_fn.block;
75 let wrapped_block = quote! {
76 {
77 #stacksafe_crate::internal::stacker::maybe_grow(
78 #stacksafe_crate::get_minimum_stack_size(),
79 #stacksafe_crate::get_stack_allocation_size(),
80 #stacksafe_crate::internal::with_protected(move || #ret { #block })
81 )
82 }
83 };
84
85 *item_fn.block = syn::parse(wrapped_block.into()).unwrap();
86 item_fn.into_token_stream().into()
87}