Skip to main content

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

1use ra_ap_syntax::{AstNode, ast};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "bare forget without comment",
9        code: "fn f() { std::mem::forget(String::new()); }",
10        pass: false,
11    },
12    Example {
13        label: "forget with LEAK comment",
14        code: "fn f() {\n    // LEAK: intentionally leaked for static lifetime\n    std::mem::forget(String::new());\n}",
15        pass: true,
16    },
17    Example {
18        label: "forget with SAFETY comment",
19        code: "fn f() {\n    // SAFETY: ownership transferred to FFI\n    std::mem::forget(String::new());\n}",
20        pass: true,
21    },
22    Example {
23        label: "forget in test module",
24        code: "#[cfg(test)]\nmod tests {\n    fn t() { std::mem::forget(String::new()); }\n}",
25        pass: true,
26    },
27    Example {
28        label: "unrelated forget function",
29        code: "fn f() { cache::forget(key); }",
30        pass: true,
31    },
32];
33
34crate::ast_rule!(
35    mem_forget,
36    "Require `LEAK` or `SAFETY` comment on `std::mem::forget()` calls.",
37    "mem::forget permanently leaks memory. A justification comment proves the leak is intentional, not a bug.",
38    High,
39);
40
41fn check_mem_forget(ctx: &AstCtx<'_>) -> Vec<Violation> {
42    ctx.nodes::<ast::CallExpr>()
43        .filter(|call| !ctx.is_in_test(call))
44        .filter_map(|call| {
45            let ast::Expr::PathExpr(path_expr) = call.expr()? else {
46                return None;
47            };
48            let path = path_expr.path()?;
49            let source = path.syntax().text().to_string();
50            let is_forget = path
51                .segment()
52                .and_then(|segment| segment.name_ref())
53                .is_some_and(|name| name.text() == "forget")
54                && source.split("::").any(|segment| segment == "mem");
55
56            if !is_forget {
57                return None;
58            }
59
60            let line = ctx.line_of(&path);
61
62            (!crate::infra::helpers::has_preceding_comment(
63                ctx.file.lines,
64                line,
65                &["LEAK:", "SAFETY:"],
66            ))
67            .then(|| {
68                ctx.violation(
69                    &path,
70                    "std::mem::forget without // LEAK: or // SAFETY: comment on a preceding line",
71                )
72            })
73        })
74        .collect()
75}
76
77crate::tidy_ast_test!(check_mem_forget, {
78    crate::example_tests!(EXAMPLES, check_mem_forget);
79});