wowlab_tidy/languages/rust/rules/correctness/
catch_unwind.rs1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "full path catch_unwind",
9 code: "fn f() { let _ = std::panic::catch_unwind(|| {}); }",
10 pass: false,
11 },
12 Example {
13 label: "short path catch_unwind",
14 code: "fn f() { let _ = panic::catch_unwind(|| {}); }",
15 pass: false,
16 },
17 Example {
18 label: "method catch_unwind",
19 code: "fn f<F: Future>(fut: F) { let _ = fut.catch_unwind(); }",
20 pass: false,
21 },
22 Example {
23 label: "catch_unwind with boundary comment",
24 code: "fn f() {\n // PANIC-BOUNDARY: isolates one request; the worker restarts after any unwind\n let _ = std::panic::catch_unwind(|| {});\n}",
25 pass: true,
26 },
27 Example {
28 label: "catch_unwind in test module",
29 code: "#[cfg(test)]\nmod tests {\n fn t() { let _ = std::panic::catch_unwind(|| {}); }\n}",
30 pass: true,
31 },
32 Example {
33 label: "no catch_unwind",
34 code: "fn f() -> Result<(), String> { Ok(()) }",
35 pass: true,
36 },
37];
38
39crate::ast_rule!(
40 catch_unwind,
41 "Require `// PANIC-BOUNDARY:` comment on `catch_unwind` calls.",
42 "Catching a panic and continuing risks observing broken state. The comment must state the controlled-restart story.",
43 High,
44);
45
46fn check_catch_unwind(ctx: &AstCtx<'_>) -> Vec<Violation> {
47 let calls = ctx
48 .nodes::<ast::CallExpr>()
49 .filter(|call| !ctx.is_in_test(call))
50 .filter_map(|call| {
51 let ast::Expr::PathExpr(path) = call.expr()? else {
52 return None;
53 };
54 let path = path.path()?;
55
56 path.segment()
57 .and_then(|segment| segment.name_ref())
58 .filter(|name| name.text() == "catch_unwind")
59 });
60 let methods = ctx
61 .nodes::<ast::MethodCallExpr>()
62 .filter(|call| !ctx.is_in_test(call))
63 .filter_map(|call| call.name_ref().filter(|name| name.text() == "catch_unwind"));
64
65 calls
66 .chain(methods)
67 .filter_map(|name| missing_boundary(ctx, &name))
68 .collect()
69}
70
71fn missing_boundary(ctx: &AstCtx<'_>, name: &ast::NameRef) -> Option<Violation> {
72 let line = ctx.line_of(name);
73
74 if !crate::infra::helpers::has_preceding_comment(ctx.file.lines, line, &["PANIC-BOUNDARY:"]) {
75 return Some(ctx.violation(
76 name,
77 "catch_unwind without // PANIC-BOUNDARY: comment stating the controlled-restart story",
78 ));
79 }
80
81 None
82}
83
84crate::tidy_ast_test!(check_catch_unwind, {
85 crate::example_tests!(EXAMPLES, check_catch_unwind);
86});