wowlab_tidy/languages/rust/rules/correctness/
drop_panic.rs1use ra_ap_syntax::{AstNode, ast};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "panic in Drop",
9 code: "struct Foo;\nimpl Drop for Foo {\n fn drop(&mut self) { panic!(\"oh no\"); }\n}",
10 pass: false,
11 },
12 Example {
13 label: "unwrap in Drop",
14 code: "struct Foo;\nimpl Drop for Foo {\n fn drop(&mut self) { Some(1).unwrap(); }\n}",
15 pass: false,
16 },
17 Example {
18 label: "expect in Drop",
19 code: "struct Foo;\nimpl Drop for Foo {\n fn drop(&mut self) { Some(1).expect(\"msg\"); }\n}",
20 pass: false,
21 },
22 Example {
23 label: "println in Drop",
24 code: "struct Foo;\nimpl Drop for Foo {\n fn drop(&mut self) { println!(\"dropping\"); }\n}",
25 pass: true,
26 },
27 Example {
28 label: "panic outside Drop",
29 code: "struct Foo;\nimpl Foo {\n fn bar(&self) { panic!(\"ok\"); }\n}",
30 pass: true,
31 },
32 Example {
33 label: "todo in Drop",
34 code: "struct Foo;\nimpl Drop for Foo {\n fn drop(&mut self) { todo!(); }\n}",
35 pass: false,
36 },
37 Example {
38 label: "panic in Drop in test module",
39 code: "#[cfg(test)]\nmod tests {\n struct Foo;\n impl Drop for Foo {\n fn drop(&mut self) { panic!(\"test\"); }\n }\n}",
40 pass: true,
41 },
42];
43
44crate::ast_rule!(
45 drop_panic,
46 "Ban `panic!`, `.unwrap()`, `.expect()` inside `impl Drop`.",
47 "Panicking in Drop causes a double-panic abort. Drop must be infallible to avoid crashing the entire process.",
48 High,
49);
50
51fn check_drop_panic(ctx: &AstCtx<'_>) -> Vec<Violation> {
52 let macros = ctx
53 .nodes::<ast::MacroCall>()
54 .filter(|call| !ctx.is_in_test(call) && inside_drop(call))
55 .filter_map(|call| {
56 let name = call.path()?.segment()?.name_ref()?.text().to_string();
57
58 matches!(name.as_str(), "panic" | "todo" | "unimplemented").then(|| {
59 ctx.violation(
60 &call,
61 "panic-family macro in Drop impl — this can cause a double-panic abort",
62 )
63 })
64 });
65 let methods = ctx
66 .nodes::<ast::MethodCallExpr>()
67 .filter(|call| !ctx.is_in_test(call) && inside_drop(call))
68 .filter_map(|call| {
69 let name = call.name_ref()?;
70
71 matches!(name.text().as_str(), "unwrap" | "expect").then(|| {
72 ctx.violation(
73 &name,
74 format!(
75 ".{}() in Drop impl — this can cause a double-panic abort",
76 name.text()
77 ),
78 )
79 })
80 });
81
82 macros.chain(methods).collect()
83}
84
85fn inside_drop<N>(node: &N) -> bool
86where
87 N: AstNode,
88{
89 node.syntax()
90 .ancestors()
91 .filter_map(ast::Impl::cast)
92 .filter_map(|item_impl| item_impl.trait_())
93 .any(|ty| {
94 ty.syntax()
95 .descendants()
96 .filter_map(ast::NameRef::cast)
97 .last()
98 .is_some_and(|name| name.text() == "Drop")
99 })
100}
101
102crate::tidy_ast_test!(check_drop_panic, {
103 crate::example_tests!(EXAMPLES, check_drop_panic);
104});