wowlab_tidy/languages/rust/rules/correctness/
transmute_usage.rs1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "bare transmute without safety comment",
9 code: "unsafe fn f() { let _x: u32 = std::mem::transmute(1.0f32); }",
10 pass: false,
11 },
12 Example {
13 label: "transmute with safety comment",
14 code: "unsafe fn f() {\n // SAFETY: f32 and u32 have the same size\n let _x: u32 = std::mem::transmute(1.0f32);\n}",
15 pass: true,
16 },
17 Example {
18 label: "transmute in test module",
19 code: "#[cfg(test)]\nmod tests {\n unsafe fn t() { let _x: u32 = std::mem::transmute(1.0f32); }\n}",
20 pass: true,
21 },
22];
23
24crate::ast_rule!(
25 transmute_usage,
26 "Require `SAFETY` comment on `std::mem::transmute` calls.",
27 "transmute reinterprets raw bytes and can cause UB if the types are incompatible. A SAFETY comment proves correctness.",
28 High,
29);
30
31fn check_transmute_usage(ctx: &AstCtx<'_>) -> Vec<Violation> {
32 ctx.nodes::<ast::CallExpr>()
33 .filter(|call| !ctx.is_in_test(call))
34 .filter_map(|call| {
35 let ast::Expr::PathExpr(path_expr) = call.expr()? else {
36 return None;
37 };
38 let path = path_expr.path()?;
39
40 if path
41 .segment()
42 .and_then(|segment| segment.name_ref())
43 .is_none_or(|name| name.text() != "transmute")
44 {
45 return None;
46 }
47
48 let line = ctx.line_of(&path);
49
50 (!crate::infra::helpers::has_preceding_comment(ctx.file.lines, line, &["SAFETY:"]))
51 .then(|| {
52 ctx.violation(
53 &path,
54 "std::mem::transmute without // SAFETY: comment on a preceding line",
55 )
56 })
57 })
58 .collect()
59}
60
61crate::tidy_ast_test!(check_transmute_usage, {
62 crate::example_tests!(EXAMPLES, check_transmute_usage);
63});