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}