Skip to main content

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

1use ra_ap_syntax::{
2    AstNode,
3    ast::{self, HasName, HasVisibility, VisibilityKind},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10    Example {
11        label: "transmute in safe pub fn",
12        code: "pub fn f(x: u32) -> f32 { unsafe { std::mem::transmute(x) } }",
13        pass: false,
14    },
15    Example {
16        label: "transmute in safe pub method",
17        code: "struct S;\nimpl S {\n    pub fn f(&self, x: u32) -> f32 { unsafe { std::mem::transmute(x) } }\n}",
18        pass: false,
19    },
20    Example {
21        label: "transmute in pub unsafe fn",
22        code: "pub unsafe fn f(x: u32) -> f32 { unsafe { std::mem::transmute(x) } }",
23        pass: true,
24    },
25    Example {
26        label: "transmute in private fn",
27        code: "fn f(x: u32) -> f32 { unsafe { std::mem::transmute(x) } }",
28        pass: true,
29    },
30    Example {
31        label: "transmute in pub(crate) fn",
32        code: "pub(crate) fn f(x: u32) -> f32 { unsafe { std::mem::transmute(x) } }",
33        pass: true,
34    },
35    Example {
36        label: "transmute in test module",
37        code: "#[cfg(test)]\nmod tests {\n    pub fn t(x: u32) -> f32 { unsafe { std::mem::transmute(x) } }\n}",
38        pass: true,
39    },
40];
41
42crate::ast_rule!(
43    transmute_in_safe_fn,
44    "Flag `transmute` inside a safe `pub` fn.",
45    "A safe public signature promises soundness its transmuting body cannot guarantee — the prime unsoundness suspect.",
46    High,
47);
48
49fn check_transmute_in_safe_fn(ctx: &AstCtx<'_>) -> Vec<Violation> {
50    ctx.nodes::<ast::CallExpr>()
51        .filter(|call| !ctx.is_in_test(call))
52        .filter_map(|call| {
53            let ast::Expr::PathExpr(path_expr) = call.expr()? else {
54                return None;
55            };
56            let path = path_expr.path()?;
57
58            if path
59                .segment()
60                .and_then(|segment| segment.name_ref()).is_none_or(|name| name.text() != "transmute")
61            {
62                return None;
63            }
64
65            let function = call.syntax().ancestors().find_map(ast::Fn::cast)?;
66            let name = safe_pub_fn_name(&function)?;
67
68            Some(ctx.violation(
69                &path,
70                format!(
71                    "transmute inside safe `pub fn {name}` — mark it `unsafe fn` or encapsulate the invariant"
72                ),
73            ))
74        })
75        .collect()
76}
77
78fn safe_pub_fn_name(function: &ast::Fn) -> Option<String> {
79    (function
80        .visibility()
81        .is_some_and(|visibility| matches!(visibility.kind(), VisibilityKind::Pub))
82        && function.unsafe_token().is_none())
83    .then(|| function.name().map(|name| name.text().to_string()))
84    .flatten()
85}
86
87crate::tidy_ast_test!(check_transmute_in_safe_fn, {
88    crate::example_tests!(EXAMPLES, check_transmute_in_safe_fn);
89});