Skip to main content

wowlab_tidy/languages/rust/rules/safety/
unsafe_impl_send.rs

1use crate::{
2    Example, FileCtx, Violation,
3    infra::{helpers, parse, scanner},
4    violation,
5};
6
7#[rustfmt::skip]
8const EXAMPLES: &[Example] = &[
9    Example {
10        label: "unsafe impl Send without safety comment",
11        code: "struct Foo(*mut u8);\nunsafe impl Send for Foo {}",
12        pass: false,
13    },
14    Example {
15        label: "unsafe impl Sync without safety comment",
16        code: "struct Foo(*mut u8);\nunsafe impl Sync for Foo {}",
17        pass: false,
18    },
19    Example {
20        label: "unsafe impl Send with safety comment",
21        code: "struct Foo(*mut u8);\n// SAFETY: the pointer is owned and never shared across threads\nunsafe impl Send for Foo {}",
22        pass: true,
23    },
24    Example {
25        label: "generic blanket impl flagged even with safety comment",
26        code: "struct Wrapper<T>(T);\n// SAFETY: trust me\nunsafe impl<T> Send for Wrapper<T> {}",
27        pass: false,
28    },
29    Example {
30        label: "generic blanket Sync with bounds",
31        code: "struct Wrapper<T>(T);\nunsafe impl<T: Copy> Sync for Wrapper<T> {}",
32        pass: false,
33    },
34    Example {
35        label: "other unsafe trait impl",
36        code: "struct Chunk([u8; 8]);\n// SAFETY: all-zero bit pattern is valid\nunsafe impl Zeroable for Chunk {}",
37        pass: true,
38    },
39    Example {
40        label: "mention in string literal",
41        code: r#"let s = "unsafe impl Send for Foo";"#,
42        pass: true,
43    },
44    Example {
45        label: "mention in comment",
46        code: "// unsafe impl Send for Foo",
47        pass: true,
48    },
49];
50
51crate::line_rule!(
52    unsafe_impl_send,
53    "Flag `unsafe impl Send`/`Sync` without a `// SAFETY:` comment, and any generic (blanket) form.",
54    "Bypassing Send/Sync bounds is the canonical unsoundness footgun, and a blanket impl over all T cannot be proven safe (M-UNSAFE, M-UNSOUND).",
55    High,
56);
57
58const GENERIC_MSG: &str = "blanket `unsafe impl<..> Send/Sync` over a generic type is almost \
59                           always unsound — implement for concrete types instead (M-UNSOUND)";
60const MISSING_COMMENT_MSG: &str =
61    "`unsafe impl Send/Sync` without a `// SAFETY:` comment on a preceding line (M-UNSAFE)";
62
63fn check_unsafe_impl_send(ctx: &FileCtx<'_>) -> Vec<Violation> {
64    let mut out = Vec::new();
65
66    for (index, line) in ctx.lines.iter().enumerate() {
67        if parse::is_comment(line.trim()) {
68            continue;
69        }
70
71        let code = scanner::code_only(line);
72        let Some(generic) = find_unsafe_send_impl(&code) else {
73            continue;
74        };
75        let lineno = index + 1;
76
77        if generic {
78            out.push(violation(ctx.rel, lineno, GENERIC_MSG));
79        } else if !helpers::has_preceding_comment(ctx.lines, lineno, &["SAFETY:"]) {
80            out.push(violation(ctx.rel, lineno, MISSING_COMMENT_MSG));
81        }
82    }
83
84    out
85}
86
87fn find_unsafe_send_impl(code: &str) -> Option<bool> {
88    let (_, after) = code.split_once("unsafe impl")?;
89    let mut rest = after.trim_start();
90    let generic = rest.starts_with('<');
91
92    if generic {
93        rest = skip_generics(rest)?;
94    }
95
96    let trait_name = ["Send", "Sync"].into_iter().find(|t| rest.starts_with(t))?;
97    let tail = rest
98        .strip_prefix(trait_name)?
99        .trim_start()
100        .strip_prefix("for")?;
101
102    (tail.is_empty() || tail.starts_with(char::is_whitespace)).then_some(generic)
103}
104
105fn skip_generics(s: &str) -> Option<&str> {
106    let mut depth: usize = 0;
107
108    for (i, ch) in s.char_indices() {
109        match ch {
110            '<' => depth += 1,
111            '>' => {
112                depth = depth.checked_sub(1)?;
113
114                if depth == 0 {
115                    return s.get(i + 1..).map(str::trim_start);
116                }
117            }
118            _ => {}
119        }
120    }
121
122    None
123}
124
125crate::tidy_test!(check_unsafe_impl_send, {
126    crate::example_tests!(EXAMPLES, check_unsafe_impl_send);
127});