Skip to main content

wowlab_tidy/languages/rust/rules/correctness/
drop_panic.rs

1use 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});