• 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&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
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 //! *Compiler support: requires rustc 1.31+*
70 //!
71 //! <br>
72 //!
73 //! ## Caveats
74 //!
75 //! - Functions that require some amount of optimization to prove that they do
76 //!   not panic may no longer compile in debug mode after being marked
77 //!   `#[no_panic]`.
78 //!
79 //! - Panic detection happens at link time across the entire dependency graph,
80 //!   so any Cargo commands that do not invoke a linker will not trigger panic
81 //!   detection. This includes `cargo build` of library crates and `cargo check`
82 //!   of binary and library crates.
83 //!
84 //! - The attribute is useless in code built with `panic = "abort"`.
85 //!
86 //! If you find that code requires optimization to pass `#[no_panic]`, either
87 //! make no-panic an optional dependency that you only enable in release builds,
88 //! or add a section like the following to Cargo.toml to enable very basic
89 //! optimization in debug builds.
90 //!
91 //! ```toml
92 //! [profile.dev]
93 //! opt-level = 1
94 //! ```
95 //!
96 //! If the code that you need to prove isn't panicking makes function calls to
97 //! non-generic non-inline functions from a different crate, you may need thin
98 //! LTO enabled for the linker to deduce those do not panic.
99 //!
100 //! ```toml
101 //! [profile.release]
102 //! lto = "thin"
103 //! ```
104 //!
105 //! If you want no_panic to just assume that some function you call doesn't
106 //! panic, and get Undefined Behavior if it does at runtime, see
107 //! [dtolnay/no-panic#16]; try wrapping that call in an `unsafe extern "C"`
108 //! wrapper.
109 //!
110 //! [dtolnay/no-panic#16]: https://github.com/dtolnay/no-panic/issues/16
111 //!
112 //! <br>
113 //!
114 //! ## Acknowledgments
115 //!
116 //! The linker error technique is based on [Kixunil]'s crate [`dont_panic`].
117 //! Check out that crate for other convenient ways to require absence of panics.
118 //!
119 //! [Kixunil]: https://github.com/Kixunil
120 //! [`dont_panic`]: https://github.com/Kixunil/dont_panic
121 
122 extern crate proc_macro;
123 
124 use proc_macro::TokenStream;
125 use proc_macro2::Span;
126 use quote::quote;
127 use syn::{parse_macro_input, parse_quote, Attribute, FnArg, Ident, ItemFn, PatType, ReturnType};
128 
129 #[proc_macro_attribute]
no_panic(args: TokenStream, function: TokenStream) -> TokenStream130 pub fn no_panic(args: TokenStream, function: TokenStream) -> TokenStream {
131     assert!(args.is_empty());
132 
133     let mut function = parse_macro_input!(function as ItemFn);
134 
135     let mut move_self = None;
136     let mut arg_pat = Vec::new();
137     let mut arg_val = Vec::new();
138     for (i, input) in function.sig.inputs.iter_mut().enumerate() {
139         let numbered = Ident::new(&format!("__arg{}", i), Span::call_site());
140         match input {
141             FnArg::Typed(PatType { pat, .. }) => {
142                 arg_pat.push(quote!(#pat));
143                 arg_val.push(quote!(#numbered));
144                 *pat = parse_quote!(mut #numbered);
145             }
146             FnArg::Receiver(_) => {
147                 move_self = Some(quote! {
148                     if false {
149                         loop {}
150                         #[allow(unreachable_code)]
151                         {
152                             let __self = self;
153                         }
154                     }
155                 });
156             }
157         }
158     }
159 
160     let has_inline = function
161         .attrs
162         .iter()
163         .flat_map(Attribute::parse_meta)
164         .any(|meta| meta.path().is_ident("inline"));
165     if !has_inline {
166         function.attrs.push(parse_quote!(#[inline]));
167     }
168 
169     let ret = match &function.sig.output {
170         ReturnType::Default => quote!(-> ()),
171         output @ ReturnType::Type(..) => quote!(#output),
172     };
173     let stmts = function.block.stmts;
174     let message = format!(
175         "\n\nERROR[no-panic]: detected panic in function `{}`\n",
176         function.sig.ident,
177     );
178     function.block = Box::new(parse_quote!({
179         struct __NoPanic;
180         extern "C" {
181             #[link_name = #message]
182             fn trigger() -> !;
183         }
184         impl core::ops::Drop for __NoPanic {
185             fn drop(&mut self) {
186                 unsafe {
187                     trigger();
188                 }
189             }
190         }
191         let __guard = __NoPanic;
192         let __result = (move || #ret {
193             #move_self
194             #(
195                 let #arg_pat = #arg_val;
196             )*
197             #(#stmts)*
198         })();
199         core::mem::forget(__guard);
200         __result
201     }));
202 
203     TokenStream::from(quote!(#function))
204 }
205