• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! [![github]](https://github.com/dtolnay/no-panic) [![crates-io]](https://crates.io/crates/no-panic) [![docs-rs]](https://docs.rs/no-panic)
2 //!
3 //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6 //!
7 //! <br>
8 //!
9 //! A Rust attribute macro to require that the compiler prove a function can't
10 //! ever panic.
11 //!
12 //! ```toml
13 //! [dependencies]
14 //! no-panic = "0.1"
15 //! ```
16 //!
17 //! ```
18 //! use no_panic::no_panic;
19 //!
20 //! #[no_panic]
21 //! fn demo(s: &str) -> &str {
22 //!     &s[1..]
23 //! }
24 //!
25 //! fn main() {
26 //!     # fn demo(s: &str) -> &str {
27 //!     #     &s[1..]
28 //!     # }
29 //!     #
30 //!     println!("{}", demo("input string"));
31 //! }
32 //! ```
33 //!
34 //! If the function does panic (or the compiler fails to prove that the function
35 //! cannot panic), the program fails to compile with a linker error that
36 //! identifies the function name. Let's trigger that by passing a string that
37 //! cannot be sliced at the first byte:
38 //!
39 //! ```should_panic
40 //! # fn demo(s: &str) -> &str {
41 //! #     &s[1..]
42 //! # }
43 //! #
44 //! fn main() {
45 //!     println!("{}", demo("\u{1f980}input string"));
46 //! }
47 //! ```
48 //!
49 //! ```console
50 //!    Compiling no-panic-demo v0.0.1
51 //! error: linking with `cc` failed: exit code: 1
52 //!   |
53 //!   = note: /no-panic-demo/target/release/deps/no_panic_demo-7170785b672ae322.no_p
54 //! anic_demo1-cba7f4b666ccdbcbbf02b7348e5df1b2.rs.rcgu.o: In function `_$LT$no_pani
55 //! c_demo..demo..__NoPanic$u20$as$u20$core..ops..drop..Drop$GT$::drop::h72f8f423002
56 //! b8d9f':
57 //!           no_panic_demo1-cba7f4b666ccdbcbbf02b7348e5df1b2.rs:(.text._ZN72_$LT$no
58 //! _panic_demo..demo..__NoPanic$u20$as$u20$core..ops..drop..Drop$GT$4drop17h72f8f42
59 //! 3002b8d9fE+0x2): undefined reference to `
60 //!
61 //!           ERROR[no-panic]: detected panic in function `demo`
62 //!           '
63 //!           collect2: error: ld returned 1 exit status
64 //! ```
65 //!
66 //! The error is not stellar but notice the ERROR\[no-panic\] part at the end
67 //! that provides the name of the offending function.
68 //!
69 //! <br>
70 //!
71 //! ## Caveats
72 //!
73 //! - Functions that require some amount of optimization to prove that they do
74 //!   not panic may no longer compile in debug mode after being marked
75 //!   `#[no_panic]`.
76 //!
77 //! - Panic detection happens at link time across the entire dependency graph,
78 //!   so any Cargo commands that do not invoke a linker will not trigger panic
79 //!   detection. This includes `cargo build` of library crates and `cargo check`
80 //!   of binary and library crates.
81 //!
82 //! - The attribute is useless in code built with `panic = "abort"`. Code must
83 //!   be built with `panic = "unwind"` (the default) in order for any panics to
84 //!   be detected. After confirming absence of panics, you can of course still
85 //!   ship your software as a `panic = "abort"` build.
86 //!
87 //! - Const functions are not supported. The attribute will fail to compile if
88 //!   placed on a `const fn`.
89 //!
90 //! If you find that code requires optimization to pass `#[no_panic]`, either
91 //! make no-panic an optional dependency that you only enable in release builds,
92 //! or add a section like the following to your Cargo.toml or .cargo/config.toml
93 //! to enable very basic optimization in debug builds.
94 //!
95 //! ```toml
96 //! [profile.dev]
97 //! opt-level = 1
98 //! ```
99 //!
100 //! If the code that you need to prove isn't panicking makes function calls to
101 //! non-generic non-inline functions from a different crate, you may need thin
102 //! LTO enabled for the linker to deduce those do not panic.
103 //!
104 //! ```toml
105 //! [profile.release]
106 //! lto = "thin"
107 //! ```
108 //!
109 //! If thin LTO isn't cutting it, the next thing to try would be fat LTO with a
110 //! single codegen unit:
111 //!
112 //! ```toml
113 //! [profile.release]
114 //! lto = "fat"
115 //! codegen-units = 1
116 //! ```
117 //!
118 //! If you want no_panic to just assume that some function you call doesn't
119 //! panic, and get Undefined Behavior if it does at runtime, see
120 //! [dtolnay/no-panic#16]; try wrapping that call in an `unsafe extern "C"`
121 //! wrapper.
122 //!
123 //! [dtolnay/no-panic#16]: https://github.com/dtolnay/no-panic/issues/16
124 //!
125 //! <br>
126 //!
127 //! ## Acknowledgments
128 //!
129 //! The linker error technique is based on [Kixunil]'s crate [`dont_panic`].
130 //! Check out that crate for other convenient ways to require absence of panics.
131 //!
132 //! [Kixunil]: https://github.com/Kixunil
133 //! [`dont_panic`]: https://github.com/Kixunil/dont_panic
134 
135 #![doc(html_root_url = "https://docs.rs/no-panic/0.1.35")]
136 #![allow(
137     clippy::doc_markdown,
138     clippy::match_same_arms,
139     clippy::missing_panics_doc
140 )]
141 #![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))]
142 
143 extern crate proc_macro;
144 
145 use proc_macro::TokenStream;
146 use proc_macro2::{Span, TokenStream as TokenStream2};
147 use quote::quote;
148 use syn::parse::{Error, Nothing, Result};
149 use syn::{
150     parse_quote, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments,
151     ReturnType, Token, Type, TypeInfer, TypeParamBound,
152 };
153 
154 #[proc_macro_attribute]
no_panic(args: TokenStream, input: TokenStream) -> TokenStream155 pub fn no_panic(args: TokenStream, input: TokenStream) -> TokenStream {
156     let args = TokenStream2::from(args);
157     let input = TokenStream2::from(input);
158     TokenStream::from(match parse(args, input.clone()) {
159         Ok(function) => {
160             let expanded = expand_no_panic(function);
161             quote! {
162                 #[cfg(not(doc))]
163                 #expanded
164                 // Keep generated parameter names out of doc builds.
165                 #[cfg(doc)]
166                 #input
167             }
168         }
169         Err(parse_error) => {
170             let compile_error = parse_error.to_compile_error();
171             quote! {
172                 #compile_error
173                 #input
174             }
175         }
176     })
177 }
178 
parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn>179 fn parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn> {
180     let function: ItemFn = syn::parse2(input)?;
181     let _: Nothing = syn::parse2::<Nothing>(args)?;
182     if function.sig.constness.is_some() {
183         return Err(Error::new(
184             Span::call_site(),
185             "no_panic attribute on const fn is not supported",
186         ));
187     }
188     if function.sig.asyncness.is_some() {
189         return Err(Error::new(
190             Span::call_site(),
191             "no_panic attribute on async fn is not supported",
192         ));
193     }
194     Ok(function)
195 }
196 
197 // Convert `Path<impl Trait>` to `Path<_>`
make_impl_trait_wild(ret: &mut Type)198 fn make_impl_trait_wild(ret: &mut Type) {
199     match ret {
200         #![cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
201         Type::ImplTrait(impl_trait) => {
202             *ret = Type::Infer(TypeInfer {
203                 underscore_token: Token![_](impl_trait.impl_token.span),
204             });
205         }
206         Type::Array(ret) => make_impl_trait_wild(&mut ret.elem),
207         Type::Group(ret) => make_impl_trait_wild(&mut ret.elem),
208         Type::Paren(ret) => make_impl_trait_wild(&mut ret.elem),
209         Type::Path(ret) => make_impl_trait_wild_in_path(&mut ret.path),
210         Type::Ptr(ret) => make_impl_trait_wild(&mut ret.elem),
211         Type::Reference(ret) => make_impl_trait_wild(&mut ret.elem),
212         Type::Slice(ret) => make_impl_trait_wild(&mut ret.elem),
213         Type::TraitObject(ret) => {
214             for bound in &mut ret.bounds {
215                 if let TypeParamBound::Trait(bound) = bound {
216                     make_impl_trait_wild_in_path(&mut bound.path);
217                 }
218             }
219         }
220         Type::Tuple(ret) => ret.elems.iter_mut().for_each(make_impl_trait_wild),
221         Type::BareFn(_) | Type::Infer(_) | Type::Macro(_) | Type::Never(_) | Type::Verbatim(_) => {}
222         _ => {}
223     }
224 }
225 
make_impl_trait_wild_in_path(path: &mut Path)226 fn make_impl_trait_wild_in_path(path: &mut Path) {
227     for segment in &mut path.segments {
228         if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments {
229             for arg in &mut bracketed.args {
230                 if let GenericArgument::Type(arg) = arg {
231                     make_impl_trait_wild(arg);
232                 }
233             }
234         }
235     }
236 }
237 
expand_no_panic(mut function: ItemFn) -> TokenStream2238 fn expand_no_panic(mut function: ItemFn) -> TokenStream2 {
239     let mut move_self = None;
240     let mut arg_pat = Vec::new();
241     let mut arg_val = Vec::new();
242     for (i, input) in function.sig.inputs.iter_mut().enumerate() {
243         match input {
244             FnArg::Typed(PatType { pat, .. })
245                 if match pat.as_ref() {
246                     Pat::Ident(pat) => pat.ident != "self",
247                     _ => true,
248                 } =>
249             {
250                 let arg_name = if let Pat::Ident(original_name) = &**pat {
251                     original_name.ident.clone()
252                 } else {
253                     Ident::new(&format!("__arg{}", i), Span::call_site())
254                 };
255                 arg_pat.push(quote!(#pat));
256                 arg_val.push(quote!(#arg_name));
257                 *pat = parse_quote!(mut #arg_name);
258             }
259             FnArg::Typed(_) | FnArg::Receiver(_) => {
260                 move_self = Some(quote! {
261                     if false {
262                         loop {}
263                         #[allow(unreachable_code)]
264                         {
265                             let __self = self;
266                         }
267                     }
268                 });
269             }
270         }
271     }
272 
273     let has_inline = function
274         .attrs
275         .iter()
276         .any(|attr| attr.path().is_ident("inline"));
277     if !has_inline {
278         function.attrs.push(parse_quote!(#[inline]));
279     }
280 
281     let ret = match &function.sig.output {
282         ReturnType::Default => quote!(-> ()),
283         ReturnType::Type(arrow, output) => {
284             let mut output = output.clone();
285             make_impl_trait_wild(&mut output);
286             quote!(#arrow #output)
287         }
288     };
289     let stmts = function.block.stmts;
290     let message = format!(
291         "\n\nERROR[no-panic]: detected panic in function `{}`\n",
292         function.sig.ident,
293     );
294     let unsafe_extern = if cfg!(no_unsafe_extern_blocks) {
295         None
296     } else {
297         Some(Token![unsafe](Span::call_site()))
298     };
299     function.block = Box::new(parse_quote!({
300         struct __NoPanic;
301         #unsafe_extern extern "C" {
302             #[link_name = #message]
303             fn trigger() -> !;
304         }
305         impl ::core::ops::Drop for __NoPanic {
306             fn drop(&mut self) {
307                 unsafe {
308                     trigger();
309                 }
310             }
311         }
312         let __guard = __NoPanic;
313         let __result = (move || #ret {
314             #move_self
315             #(
316                 let #arg_pat = #arg_val;
317             )*
318             #(#stmts)*
319         })();
320         ::core::mem::forget(__guard);
321         __result
322     }));
323 
324     quote!(#function)
325 }
326