wowlab_tidy/languages/rust/rules/safety/
unsafe_comment.rs1use ra_ap_syntax::ast::{self};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "unsafe without safety comment",
9 code: "fn f() { unsafe { std::ptr::null::<u8>().read() }; }",
10 pass: false,
11 },
12 Example {
13 label: "unsafe with safety comment",
14 code: "fn f() {\n // SAFETY: pointer is valid and aligned\n unsafe { std::ptr::null::<u8>().read() };\n}",
15 pass: true,
16 },
17 Example {
18 label: "unsafe in test module",
19 code: "#[cfg(test)]\nmod tests {\n fn t() { unsafe { std::ptr::null::<u8>().read() }; }\n}",
20 pass: true,
21 },
22 Example {
23 label: "safety comment with blank line",
24 code: "fn f() {\n // SAFETY: guaranteed valid\n\n unsafe { std::ptr::null::<u8>().read() };\n}",
25 pass: true,
26 },
27];
28
29crate::ast_rule!(
30 unsafe_comment,
31 "Require `// SAFETY:` comment on `unsafe` blocks.",
32 "Every unsafe block must document why it is sound. Without a SAFETY comment, reviewers cannot verify correctness.",
33 High,
34);
35
36fn check_unsafe_comment(ctx: &AstCtx<'_>) -> Vec<Violation> {
37 ctx.nodes::<ast::BlockExpr>()
38 .filter(|block| block.unsafe_token().is_some() && !ctx.is_in_test(block))
39 .filter_map(|block| {
40 let unsafe_token = block.unsafe_token()?;
41 let line = ctx
42 .line_index
43 .line_col(unsafe_token.text_range().start())
44 .line as usize
45 + 1;
46
47 (!crate::infra::helpers::has_preceding_comment(ctx.file.lines, line, &["SAFETY:"]))
48 .then(|| {
49 ctx.violation(
50 &block,
51 "unsafe block without // SAFETY: comment on a preceding line",
52 )
53 })
54 })
55 .collect()
56}
57
58crate::tidy_ast_test!(check_unsafe_comment, {
59 crate::example_tests!(EXAMPLES, check_unsafe_comment);
60});