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"`.
83 //!
84 //! If you find that code requires optimization to pass `#[no_panic]`, either
85 //! make no-panic an optional dependency that you only enable in release builds,
86 //! or add a section like the following to Cargo.toml to enable very basic
87 //! optimization in debug builds.
88 //!
89 //! ```toml
90 //! [profile.dev]
91 //! opt-level = 1
92 //! ```
93 //!
94 //! If the code that you need to prove isn't panicking makes function calls to
95 //! non-generic non-inline functions from a different crate, you may need thin
96 //! LTO enabled for the linker to deduce those do not panic.
97 //!
98 //! ```toml
99 //! [profile.release]
100 //! lto = "thin"
101 //! ```
102 //!
103 //! If you want no_panic to just assume that some function you call doesn't
104 //! panic, and get Undefined Behavior if it does at runtime, see
105 //! [dtolnay/no-panic#16]; try wrapping that call in an `unsafe extern "C"`
106 //! wrapper.
107 //!
108 //! [dtolnay/no-panic#16]: https://github.com/dtolnay/no-panic/issues/16
109 //!
110 //! <br>
111 //!
112 //! ## Acknowledgments
113 //!
114 //! The linker error technique is based on [Kixunil]'s crate [`dont_panic`].
115 //! Check out that crate for other convenient ways to require absence of panics.
116 //!
117 //! [Kixunil]: https://github.com/Kixunil
118 //! [`dont_panic`]: https://github.com/Kixunil/dont_panic
119
120 #![doc(html_root_url = "https://docs.rs/no-panic/0.1.26")]
121 #![allow(
122 clippy::doc_markdown,
123 clippy::match_same_arms,
124 clippy::missing_panics_doc
125 )]
126 #![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))]
127
128 extern crate proc_macro;
129
130 use proc_macro::TokenStream;
131 use proc_macro2::{Span, TokenStream as TokenStream2};
132 use quote::quote;
133 use syn::parse::{Error, Nothing, Result};
134 use syn::{
135 parse_quote, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments,
136 ReturnType, Token, Type, TypeInfer, TypeParamBound,
137 };
138
139 #[proc_macro_attribute]
no_panic(args: TokenStream, input: TokenStream) -> TokenStream140 pub fn no_panic(args: TokenStream, input: TokenStream) -> TokenStream {
141 let args = TokenStream2::from(args);
142 let input = TokenStream2::from(input);
143 let expanded = match parse(args, input.clone()) {
144 Ok(function) => expand_no_panic(function),
145 Err(parse_error) => {
146 let compile_error = parse_error.to_compile_error();
147 quote!(#compile_error #input)
148 }
149 };
150 TokenStream::from(expanded)
151 }
152
parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn>153 fn parse(args: TokenStream2, input: TokenStream2) -> Result<ItemFn> {
154 let function: ItemFn = syn::parse2(input)?;
155 let _: Nothing = syn::parse2::<Nothing>(args)?;
156 if function.sig.asyncness.is_some() {
157 return Err(Error::new(
158 Span::call_site(),
159 "no_panic attribute on async fn is not supported",
160 ));
161 }
162 Ok(function)
163 }
164
165 // Convert `Path<impl Trait>` to `Path<_>`
make_impl_trait_wild(ret: &mut Type)166 fn make_impl_trait_wild(ret: &mut Type) {
167 match ret {
168 Type::ImplTrait(impl_trait) => {
169 *ret = Type::Infer(TypeInfer {
170 underscore_token: Token,
171 });
172 }
173 Type::Array(ret) => make_impl_trait_wild(&mut ret.elem),
174 Type::Group(ret) => make_impl_trait_wild(&mut ret.elem),
175 Type::Paren(ret) => make_impl_trait_wild(&mut ret.elem),
176 Type::Path(ret) => make_impl_trait_wild_in_path(&mut ret.path),
177 Type::Ptr(ret) => make_impl_trait_wild(&mut ret.elem),
178 Type::Reference(ret) => make_impl_trait_wild(&mut ret.elem),
179 Type::Slice(ret) => make_impl_trait_wild(&mut ret.elem),
180 Type::TraitObject(ret) => {
181 for bound in &mut ret.bounds {
182 if let TypeParamBound::Trait(bound) = bound {
183 make_impl_trait_wild_in_path(&mut bound.path);
184 }
185 }
186 }
187 Type::Tuple(ret) => ret.elems.iter_mut().for_each(make_impl_trait_wild),
188 Type::BareFn(_) | Type::Infer(_) | Type::Macro(_) | Type::Never(_) | Type::Verbatim(_) => {}
189 #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
190 _ => {}
191 }
192 }
193
make_impl_trait_wild_in_path(path: &mut Path)194 fn make_impl_trait_wild_in_path(path: &mut Path) {
195 for segment in &mut path.segments {
196 if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments {
197 for arg in &mut bracketed.args {
198 if let GenericArgument::Type(arg) = arg {
199 make_impl_trait_wild(arg);
200 }
201 }
202 }
203 }
204 }
205
expand_no_panic(mut function: ItemFn) -> TokenStream2206 fn expand_no_panic(mut function: ItemFn) -> TokenStream2 {
207 let mut move_self = None;
208 let mut arg_pat = Vec::new();
209 let mut arg_val = Vec::new();
210 for (i, input) in function.sig.inputs.iter_mut().enumerate() {
211 let numbered = Ident::new(&format!("__arg{}", i), Span::call_site());
212 match input {
213 FnArg::Typed(PatType { pat, .. })
214 if match pat.as_ref() {
215 Pat::Ident(pat) => pat.ident != "self",
216 _ => true,
217 } =>
218 {
219 arg_pat.push(quote!(#pat));
220 arg_val.push(quote!(#numbered));
221 *pat = parse_quote!(mut #numbered);
222 }
223 FnArg::Typed(_) | FnArg::Receiver(_) => {
224 move_self = Some(quote! {
225 if false {
226 loop {}
227 #[allow(unreachable_code)]
228 {
229 let __self = self;
230 }
231 }
232 });
233 }
234 }
235 }
236
237 let has_inline = function
238 .attrs
239 .iter()
240 .any(|attr| attr.path().is_ident("inline"));
241 if !has_inline {
242 function.attrs.push(parse_quote!(#[inline]));
243 }
244
245 let ret = match &function.sig.output {
246 ReturnType::Default => quote!(-> ()),
247 ReturnType::Type(arrow, output) => {
248 let mut output = output.clone();
249 make_impl_trait_wild(&mut output);
250 quote!(#arrow #output)
251 }
252 };
253 let stmts = function.block.stmts;
254 let message = format!(
255 "\n\nERROR[no-panic]: detected panic in function `{}`\n",
256 function.sig.ident,
257 );
258 function.block = Box::new(parse_quote!({
259 struct __NoPanic;
260 extern "C" {
261 #[link_name = #message]
262 fn trigger() -> !;
263 }
264 impl core::ops::Drop for __NoPanic {
265 fn drop(&mut self) {
266 unsafe {
267 trigger();
268 }
269 }
270 }
271 let __guard = __NoPanic;
272 let __result = (move || #ret {
273 #move_self
274 #(
275 let #arg_pat = #arg_val;
276 )*
277 #(#stmts)*
278 })();
279 core::mem::forget(__guard);
280 __result
281 }));
282
283 quote!(#function)
284 }
285