#![allow(clippy::doc_markdown, clippy::missing_panics_doc)]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::parse::{Nothing, Result};
use syn::{parse_quote, Attribute, FnArg, Ident, ItemFn, PatType, ReturnType};
#[proc_macro_attribute]
pub fn no_panic(args: TokenStream, input: TokenStream) -> TokenStream {
let args = TokenStream2::from(args);
let input = TokenStream2::from(input);
let expanded = match parse(args, input.clone()) {
Ok(function) => expand_no_panic(function),
Err(parse_error) => {
let compile_error = parse_error.to_compile_error();
quote!(#compile_error #input)
}
};
TokenStream::from(expanded)
}
fn parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn> {
let function: ItemFn = syn::parse2(input)?;
let _: Nothing = syn::parse2::<Nothing>(args)?;
Ok(function)
}
fn expand_no_panic(mut function: ItemFn) -> TokenStream2 {
let mut move_self = None;
let mut arg_pat = Vec::new();
let mut arg_val = Vec::new();
for (i, input) in function.sig.inputs.iter_mut().enumerate() {
let numbered = Ident::new(&format!("__arg{}", i), Span::call_site());
match input {
FnArg::Typed(PatType { pat, .. }) => {
arg_pat.push(quote!(#pat));
arg_val.push(quote!(#numbered));
*pat = parse_quote!(mut #numbered);
}
FnArg::Receiver(_) => {
move_self = Some(quote! {
if false {
loop {}
#[allow(unreachable_code)]
{
let __self = self;
}
}
});
}
}
}
let has_inline = function
.attrs
.iter()
.flat_map(Attribute::parse_meta)
.any(|meta| meta.path().is_ident("inline"));
if !has_inline {
function.attrs.push(parse_quote!(#[inline]));
}
let ret = match &function.sig.output {
ReturnType::Default => quote!(-> ()),
output @ ReturnType::Type(..) => quote!(#output),
};
let stmts = function.block.stmts;
let message = format!(
"\n\nERROR[no-panic]: detected panic in function `{}`\n",
function.sig.ident,
);
function.block = Box::new(parse_quote!({
struct __NoPanic;
extern "C" {
#[link_name = #message]
fn trigger() -> !;
}
impl core::ops::Drop for __NoPanic {
fn drop(&mut self) {
unsafe {
trigger();
}
}
}
let __guard = __NoPanic;
let __result = (move || #ret {
#move_self
#(
let #arg_pat = #arg_val;
)*
#(#stmts)*
})();
core::mem::forget(__guard);
__result
}));
quote!(#function)
}