wowlab_tidy/languages/rust/rules/correctness/
transmute_in_safe_fn.rs1use 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});