1use proc_macro2::TokenStream;
21use quote::ToTokens;
22use quote::quote;
23use syn::Item;
24use syn::Path;
25use syn::ReturnType;
26use syn::Type;
27use syn::parse_quote;
28use syn::spanned::Spanned;
29
30#[proc_macro_attribute]
31pub fn stacksafe(
32 args: proc_macro::TokenStream,
33 item: proc_macro::TokenStream,
34) -> proc_macro::TokenStream {
35 let args = TokenStream::from(args);
36 let item = TokenStream::from(item);
37 match stacksafe_impl(args, item) {
38 Ok(tokens) => tokens.into(),
39 Err(err) => err.into_compile_error().into(),
40 }
41}
42
43fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
44 let mut crate_path: Option<Path> = None;
45 let arg_parser = syn::meta::parser(|meta| {
46 if meta.path.is_ident("crate") {
47 if crate_path.is_some() {
48 return Err(meta.error("duplicate attribute parameter `crate`"));
49 }
50 crate_path = Some(meta.value()?.parse()?);
51 Ok(())
52 } else {
53 Err(meta.error(format!(
54 "unknown attribute parameter `{}`",
55 meta.path.to_token_stream()
56 )))
57 }
58 });
59 syn::parse::Parser::parse2(arg_parser, args)?;
60
61 let mut item_fn = match syn::parse2::<Item>(item)? {
62 Item::Fn(item_fn) => item_fn,
63 item => {
64 return Err(syn::Error::new_spanned(
65 item,
66 "#[stacksafe] can only be applied to functions",
67 ));
68 }
69 };
70
71 if item_fn.sig.asyncness.is_some() {
72 return Err(syn::Error::new(
73 item_fn.sig.asyncness.span(),
74 "#[stacksafe] does not support async functions",
75 ));
76 }
77
78 if item_fn.sig.constness.is_some() {
79 return Err(syn::Error::new(
80 item_fn.sig.constness.span(),
81 "#[stacksafe] does not support const functions",
82 ));
83 }
84
85 let ret = match &item_fn.sig.output {
86 ReturnType::Type(_, ty) if matches!(**ty, Type::ImplTrait(_)) => None,
89 ret => Some(ret),
90 };
91
92 let stacksafe_crate = crate_path.unwrap_or_else(|| parse_quote!(::stacksafe));
93 let block = &item_fn.block;
94 let wrapped_block = quote! {
95 {
96 #stacksafe_crate::internal::stacker::maybe_grow(
97 #stacksafe_crate::get_minimum_stack_size(),
98 #stacksafe_crate::get_stack_allocation_size(),
99 #stacksafe_crate::internal::with_protected(move || #ret { #block })
100 )
101 }
102 };
103
104 *item_fn.block = syn::parse2(wrapped_block)?;
105 Ok(item_fn.into_token_stream())
106}