alass_ffi_macros/
lib.rs

1#![warn(clippy::all)]
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6
7use quote::quote;
8use syn::{parse_macro_input, Expr, ItemFn};
9
10///
11/// Wraps a function body with `std::panic::catch_unwind` to ensure that panics won't unwind
12/// accross the ffi boundary. The macro argument should be the return value of the function
13/// in case of panic, or no argument if it returns void.
14/// 
15/// Also logs `error!` message upon panic so `log` crate must
16/// be in scope.
17/// 
18/// ### Example function with no return value:
19/// ```
20///     # use alass_ffi_macros::catch_panic;
21/// 
22///     #[catch_panic]
23///     pub extern "C" fn foo() {
24///         // may panic here...
25///     }
26/// ```
27///
28/// ### Example function that returns `0` on success and `-1` on panic:
29/// ```
30///     # use alass_ffi_macros::catch_panic;
31///     # use std::os::raw::c_int;
32/// 
33///     #[catch_panic(-1)]
34///     pub extern "C" fn bar() -> c_int {
35///         // may panic here...
36///         0
37///     }
38/// ```
39/// 
40#[proc_macro_attribute]
41pub fn catch_panic(args: TokenStream, item: TokenStream) -> TokenStream {
42
43    let ItemFn { attrs, vis, sig, block } = parse_macro_input!(item as ItemFn);
44
45    // If args aren't specified, assume function returns void
46    let args = if args.is_empty() { quote!(()).into() } else { args };
47
48    let ret_expr = parse_macro_input!(args as Expr);
49
50    let wrapped = quote! {
51        #(#attrs)*
52        #vis #sig {
53            use std::panic::AssertUnwindSafe;
54            match std::panic::catch_unwind(AssertUnwindSafe(|| { #block })) {
55                Ok(v) => v,
56                Err(e) => {
57                    match e.downcast_ref::<&'static str>() {
58                        Some(s) => log::error!("{}", s),
59                        None => log::error!("Unknown panic!"),
60                    };
61                    #ret_expr
62                }
63            }
64        }
65    };
66 
67    TokenStream::from(wrapped)
68}