rulewright 0.1.2

Executable engineering standards for Rust workspaces
Documentation
use crate::{
    Example, FileCtx, Violation,
    infra::{helpers, parse, scanner},
    violation,
};

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "unsafe impl Send without safety comment",
        code: "struct Foo(*mut u8);\nunsafe impl Send for Foo {}",
        pass: false,
    },
    Example {
        label: "unsafe impl Sync without safety comment",
        code: "struct Foo(*mut u8);\nunsafe impl Sync for Foo {}",
        pass: false,
    },
    Example {
        label: "unsafe impl Send with safety comment",
        code: "struct Foo(*mut u8);\n// SAFETY: the pointer is owned and never shared across threads\nunsafe impl Send for Foo {}",
        pass: true,
    },
    Example {
        label: "generic blanket impl flagged even with safety comment",
        code: "struct Wrapper<T>(T);\n// SAFETY: trust me\nunsafe impl<T> Send for Wrapper<T> {}",
        pass: false,
    },
    Example {
        label: "generic blanket Sync with bounds",
        code: "struct Wrapper<T>(T);\nunsafe impl<T: Copy> Sync for Wrapper<T> {}",
        pass: false,
    },
    Example {
        label: "other unsafe trait impl",
        code: "struct Chunk([u8; 8]);\n// SAFETY: all-zero bit pattern is valid\nunsafe impl Zeroable for Chunk {}",
        pass: true,
    },
    Example {
        label: "mention in string literal",
        code: r#"let s = "unsafe impl Send for Foo";"#,
        pass: true,
    },
    Example {
        label: "mention in comment",
        code: "// unsafe impl Send for Foo",
        pass: true,
    },
];

crate::line_rule!(
    unsafe_impl_send,
    "Flag `unsafe impl Send`/`Sync` without a `// SAFETY:` comment, and any generic (blanket) form.",
    "Bypassing Send/Sync bounds is the canonical unsoundness footgun, and a blanket impl over all T cannot be proven safe.",
    High,
);

const GENERIC_MSG: &str = "blanket `unsafe impl<..> Send/Sync` over a generic type is almost \
                           always unsound — implement for concrete types instead";
const MISSING_COMMENT_MSG: &str =
    "`unsafe impl Send/Sync` without a `// SAFETY:` comment on a preceding line";

fn check_unsafe_impl_send(ctx: &FileCtx<'_>) -> Vec<Violation> {
    let mut out = Vec::new();

    for (index, line) in ctx.lines.iter().enumerate() {
        if parse::is_comment(line.trim()) {
            continue;
        }

        let code = scanner::code_only(line);
        let Some(generic) = find_unsafe_send_impl(&code) else {
            continue;
        };
        let lineno = index + 1;

        if generic {
            out.push(violation(ctx.rel, lineno, GENERIC_MSG));
        } else if !helpers::has_preceding_comment(ctx.lines, lineno, &["SAFETY:"]) {
            out.push(violation(ctx.rel, lineno, MISSING_COMMENT_MSG));
        }
    }

    out
}

fn find_unsafe_send_impl(code: &str) -> Option<bool> {
    let (_, after) = code.split_once("unsafe impl")?;
    let mut rest = after.trim_start();
    let generic = rest.starts_with('<');

    if generic {
        rest = skip_generics(rest)?;
    }

    let trait_name = ["Send", "Sync"].into_iter().find(|t| rest.starts_with(t))?;
    let tail = rest
        .strip_prefix(trait_name)?
        .trim_start()
        .strip_prefix("for")?;

    (tail.is_empty() || tail.starts_with(char::is_whitespace)).then_some(generic)
}

fn skip_generics(s: &str) -> Option<&str> {
    let mut depth: usize = 0;

    for (i, ch) in s.char_indices() {
        match ch {
            '<' => depth += 1,

            '>' => {
                depth = depth.checked_sub(1)?;

                if depth == 0 {
                    return s.get(i + 1..).map(str::trim_start);
                }
            }

            _ => {}
        }
    }

    None
}

crate::rulewright_test!(check_unsafe_impl_send, {
    crate::example_tests!(EXAMPLES, check_unsafe_impl_send);
});