Skip to main content

rustc_lint/
reference_casting.rs

1use rustc_ast::Mutability;
2use rustc_hir::{Expr, ExprKind, UnOp};
3use rustc_middle::ty::layout::{LayoutOf as _, TyAndLayout};
4use rustc_middle::ty::{self, Ty};
5use rustc_session::{declare_lint, declare_lint_pass};
6use rustc_span::sym;
7
8use crate::lints::InvalidReferenceCastingDiag;
9use crate::utils::peel_casts;
10use crate::{LateContext, LateLintPass, LintContext};
11
12#[doc =
r" The `invalid_reference_casting` lint checks for casts of `&T` to `&mut T`"]
#[doc = r" without using interior mutability."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,compile_fail"]
#[doc = r" fn x(r: &i32) {"]
#[doc = r"     unsafe {"]
#[doc = r"         *(r as *const i32 as *mut i32) += 1;"]
#[doc = r"     }"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Casting `&T` to `&mut T` without using interior mutability is undefined behavior,"]
#[doc = r" as it's a violation of Rust reference aliasing requirements."]
#[doc = r""]
#[doc =
r" `UnsafeCell` is the only way to obtain aliasable data that is considered"]
#[doc = r" mutable."]
static INVALID_REFERENCE_CASTING: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "INVALID_REFERENCE_CASTING",
            default_level: ::rustc_lint_defs::Deny,
            desc: "casts of `&T` to `&mut T` without interior mutability",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
13    /// The `invalid_reference_casting` lint checks for casts of `&T` to `&mut T`
14    /// without using interior mutability.
15    ///
16    /// ### Example
17    ///
18    /// ```rust,compile_fail
19    /// fn x(r: &i32) {
20    ///     unsafe {
21    ///         *(r as *const i32 as *mut i32) += 1;
22    ///     }
23    /// }
24    /// ```
25    ///
26    /// {{produces}}
27    ///
28    /// ### Explanation
29    ///
30    /// Casting `&T` to `&mut T` without using interior mutability is undefined behavior,
31    /// as it's a violation of Rust reference aliasing requirements.
32    ///
33    /// `UnsafeCell` is the only way to obtain aliasable data that is considered
34    /// mutable.
35    INVALID_REFERENCE_CASTING,
36    Deny,
37    "casts of `&T` to `&mut T` without interior mutability"
38}
39
40pub struct InvalidReferenceCasting;
#[automatically_derived]
impl ::core::marker::Copy for InvalidReferenceCasting { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for InvalidReferenceCasting { }
#[automatically_derived]
impl ::core::clone::Clone for InvalidReferenceCasting {
    #[inline]
    fn clone(&self) -> InvalidReferenceCasting { *self }
}
impl ::rustc_lint_defs::LintPass for InvalidReferenceCasting {
    fn name(&self) -> &'static str { "InvalidReferenceCasting" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_REFERENCE_CASTING]))
    }
}
impl InvalidReferenceCasting {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_REFERENCE_CASTING]))
    }
}declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]);
41
42impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
43    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
44        if let Some((e, pat)) = borrow_or_assign(cx, expr) {
45            let init = cx.expr_or_init(e);
46            let orig_cast = if init.span != e.span { Some(init.span) } else { None };
47
48            // small cache to avoid recomputing needlessly computing peel_casts of init
49            let mut peel_casts = {
50                let mut peel_casts_cache = None;
51                move || *peel_casts_cache.get_or_insert_with(|| peel_casts(cx, init))
52            };
53
54            if #[allow(non_exhaustive_omitted_patterns)] match pat {
    PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign =>
        true,
    _ => false,
}matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign)
55                && is_invalid_cast_from_ref_to_mut_ptr(cx, init, &mut peel_casts)
56            {
57                cx.emit_span_lint(
58                    INVALID_REFERENCE_CASTING,
59                    expr.span,
60                    if pat == PatternKind::Assign {
61                        InvalidReferenceCastingDiag::AssignToRef { orig_cast }
62                    } else {
63                        InvalidReferenceCastingDiag::BorrowAsMut { orig_cast }
64                    },
65                );
66            }
67
68            if let Some((from_ty_layout, to_ty_layout, e_alloc)) =
69                is_cast_to_bigger_memory_layout(cx, init, &mut peel_casts)
70            {
71                cx.emit_span_lint(
72                    INVALID_REFERENCE_CASTING,
73                    expr.span,
74                    InvalidReferenceCastingDiag::BiggerLayout {
75                        orig_cast,
76                        alloc: e_alloc.span,
77                        from_ty: from_ty_layout.ty,
78                        from_size: from_ty_layout.layout.size().bytes(),
79                        to_ty: to_ty_layout.ty,
80                        to_size: to_ty_layout.layout.size().bytes(),
81                    },
82                );
83            }
84        }
85    }
86}
87
88#[derive(#[automatically_derived]
impl ::core::fmt::Debug for PatternKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            PatternKind::Borrow { mutbl: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "Borrow", "mutbl", &__self_0),
            PatternKind::Assign =>
                ::core::fmt::Formatter::write_str(f, "Assign"),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for PatternKind {
    #[inline]
    fn clone(&self) -> PatternKind {
        let _: ::core::clone::AssertParamIsClone<Mutability>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for PatternKind { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for PatternKind {
    #[inline]
    fn eq(&self, other: &PatternKind) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (PatternKind::Borrow { mutbl: __self_0 },
                    PatternKind::Borrow { mutbl: __arg1_0 }) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for PatternKind {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Mutability>;
    }
}Eq)]
89enum PatternKind {
90    Borrow { mutbl: Mutability },
91    Assign,
92}
93
94fn borrow_or_assign<'tcx>(
95    cx: &LateContext<'tcx>,
96    e: &'tcx Expr<'tcx>,
97) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
98    fn deref_assign_or_addr_of<'tcx>(
99        expr: &'tcx Expr<'tcx>,
100    ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
101        // &(mut) <expr>
102        let (inner, pat) = if let ExprKind::AddrOf(_, mutbl, expr) = expr.kind {
103            (expr, PatternKind::Borrow { mutbl })
104        // <expr> = ...
105        } else if let ExprKind::Assign(expr, _, _) = expr.kind {
106            (expr, PatternKind::Assign)
107        // <expr> += ...
108        } else if let ExprKind::AssignOp(_, expr, _) = expr.kind {
109            (expr, PatternKind::Assign)
110        } else {
111            return None;
112        };
113
114        // *<inner>
115        let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else {
116            return None;
117        };
118        Some((e, pat))
119    }
120
121    fn ptr_write<'tcx>(
122        cx: &LateContext<'tcx>,
123        e: &'tcx Expr<'tcx>,
124    ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
125        if let ExprKind::Call(path, [arg_ptr, _arg_val]) = e.kind
126            && let ExprKind::Path(ref qpath) = path.kind
127            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
128            && #[allow(non_exhaustive_omitted_patterns)] match cx.tcx.get_diagnostic_name(def_id)
    {
    Some(sym::ptr_write | sym::ptr_write_volatile | sym::ptr_write_unaligned)
        => true,
    _ => false,
}matches!(
129                cx.tcx.get_diagnostic_name(def_id),
130                Some(sym::ptr_write | sym::ptr_write_volatile | sym::ptr_write_unaligned)
131            )
132        {
133            Some((arg_ptr, PatternKind::Assign))
134        } else {
135            None
136        }
137    }
138
139    deref_assign_or_addr_of(e).or_else(|| ptr_write(cx, e))
140}
141
142fn is_invalid_cast_from_ref_to_mut_ptr<'tcx>(
143    cx: &LateContext<'tcx>,
144    orig_expr: &'tcx Expr<'tcx>,
145    mut peel_casts: impl FnMut() -> &'tcx Expr<'tcx>,
146) -> bool {
147    let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
148
149    // Bail out early if the end type is **not** a mutable pointer.
150    if !#[allow(non_exhaustive_omitted_patterns)] match end_ty.kind() {
    ty::RawPtr(_, Mutability::Mut) => true,
    _ => false,
}matches!(end_ty.kind(), ty::RawPtr(_, Mutability::Mut)) {
151        return false;
152    }
153
154    let e = peel_casts();
155    let start_ty = cx.typeck_results().node_type(e.hir_id);
156
157    if let ty::Ref(_, inner_ty, Mutability::Not) = start_ty.kind() {
158        // We need to additionally check the inner type for the presence of `UnsafeCell`
159        // as those would "punches a hole" in the immutability requirement of `&`.
160        //
161        // The `UnsafeCell`s must recursively covered all the fields (modulo ZSTs).
162        //
163        // Not checking it would lead us to incorrectly lint on valid casts
164        // (see https://github.com/rust-lang/unsafe-code-guidelines/issues/281).
165        //
166        // For optimization purpose we first check that the type has no generics
167        // (as it's impossible to make it safe).
168        let inner_ty_has_interior_mutability =
169            inner_ty.has_concrete_skeleton() && is_ty_fully_unsafe_celled(cx, *inner_ty);
170
171        !inner_ty_has_interior_mutability
172    } else {
173        false
174    }
175}
176
177fn is_cast_to_bigger_memory_layout<'tcx>(
178    cx: &LateContext<'tcx>,
179    orig_expr: &'tcx Expr<'tcx>,
180    mut peel_casts: impl FnMut() -> &'tcx Expr<'tcx>,
181) -> Option<(TyAndLayout<'tcx>, TyAndLayout<'tcx>, Expr<'tcx>)> {
182    let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
183
184    let ty::RawPtr(inner_end_ty, _) = end_ty.kind() else {
185        return None;
186    };
187
188    let e = peel_casts();
189    let start_ty = cx.typeck_results().node_type(e.hir_id);
190
191    let ty::Ref(_, inner_start_ty, _) = start_ty.kind() else {
192        return None;
193    };
194
195    // try to find the underlying allocation
196    let e_alloc = cx.expr_or_init(e);
197    let e_alloc =
198        if let ExprKind::AddrOf(_, _, inner_expr) = e_alloc.kind { inner_expr } else { e_alloc };
199
200    // if the current expr looks like this `&mut expr[index]` then just looking
201    // at `expr[index]` won't give us the underlying allocation, so we just skip it
202    // the same logic applies field access `&mut expr.field` and reborrows `&mut *expr`.
203    if let ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(UnOp::Deref, ..) =
204        e_alloc.kind
205    {
206        return None;
207    }
208
209    let alloc_ty = cx.typeck_results().node_type(e_alloc.hir_id);
210
211    // if we do not find it we bail out, as this may not be UB
212    // see https://github.com/rust-lang/unsafe-code-guidelines/issues/256
213    if alloc_ty.is_any_ptr() {
214        return None;
215    }
216
217    let from_layout = cx.layout_of(*inner_start_ty).ok()?;
218
219    // if the type isn't sized, we bail out, instead of potentially giving
220    // the user a meaningless warning.
221    if from_layout.is_unsized() {
222        return None;
223    }
224
225    let alloc_layout = cx.layout_of(alloc_ty).ok()?;
226    let to_layout = cx.layout_of(*inner_end_ty).ok()?;
227
228    if to_layout.layout.size() > from_layout.layout.size()
229        && to_layout.layout.size() > alloc_layout.layout.size()
230    {
231        Some((from_layout, to_layout, *e_alloc))
232    } else {
233        None
234    }
235}
236
237/// Checks that a `Ty` fully covered by one or more `UnsafeCell`.
238///
239/// This checks excludes ZST and `PhantomData`.
240///
241/// This check is an approximation and can return `false` for spurious reasons,
242/// but `true` is dependable.
243// Ideally we would check the layout for this information, but alas it doesn't have such information.
244fn is_ty_fully_unsafe_celled<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
245    fn inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool {
246        // Did we reach the recursion limit? Yes, bail-out.
247        if depth >= cx.tcx.recursion_limit().0 {
248            return false;
249        }
250
251        // Is this an `UnsafeCell` or `PhantomData`?
252        if ty.is_unsafe_cell() || ty.is_phantom_data() {
253            return true;
254        }
255
256        // Is this an ZSTs? Yes, consider them covered.
257        if let Ok(layout) = cx.layout_of(ty) {
258            if layout.is_zst() {
259                return true;
260            }
261        }
262
263        // Check that the inner fields/types are them-selves covered by an `UnsafeCell`.
264        match ty.kind() {
265            ty::Adt(def, args) => {
266                // Is this an enum? Yes, bail-out.
267                if def.is_enum() {
268                    return false;
269                }
270
271                let variant = def.non_enum_variant();
272
273                // Empty struct and union are trivially covered
274                if variant.fields.is_empty() {
275                    return true;
276                }
277
278                variant.fields.iter().all(|field| {
279                    let field_ty = field.ty(cx.tcx, args).skip_norm_wip();
280                    inner(cx, field_ty, depth + 1)
281                })
282            }
283
284            ty::Tuple(fields) => fields.iter().all(|field_ty| inner(cx, field_ty, depth + 1)),
285
286            ty::Array(elem_ty, _) | ty::Slice(elem_ty) | ty::Pat(elem_ty, _) => {
287                inner(cx, *elem_ty, depth + 1)
288            }
289
290            // Primitive, slices, references, pointers and closures are not
291            // covered by an `UnsafeCell`
292            ty::Char
293            | ty::Bool
294            | ty::Int(..)
295            | ty::Uint(..)
296            | ty::Float(..)
297            | ty::Str
298            | ty::Never
299            | ty::RawPtr(..)
300            | ty::Ref(..)
301            | ty::FnDef(..)
302            | ty::FnPtr(..)
303            | ty::Closure(..)
304            | ty::CoroutineClosure(..)
305            | ty::Coroutine(..)
306            | ty::CoroutineWitness(..) => false,
307
308            ty::Foreign(_)
309            | ty::Dynamic(..)
310            | ty::Alias(..)
311            | ty::Param(_)
312            | ty::Bound(..)
313            | ty::Placeholder(..)
314            | ty::Infer(_)
315            | ty::Error(_) => false,
316
317            // FIXME: How to handle unsafe binders?
318            ty::UnsafeBinder(_) => false,
319        }
320    }
321
322    inner(cx, ty, 0)
323}