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 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 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 let (inner, pat) = if let ExprKind::AddrOf(_, mutbl, expr) = expr.kind {
103 (expr, PatternKind::Borrow { mutbl })
104 } else if let ExprKind::Assign(expr, _, _) = expr.kind {
106 (expr, PatternKind::Assign)
107 } else if let ExprKind::AssignOp(_, expr, _) = expr.kind {
109 (expr, PatternKind::Assign)
110 } else {
111 return None;
112 };
113
114 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 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 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 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 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 alloc_ty.is_any_ptr() {
214 return None;
215 }
216
217 let from_layout = cx.layout_of(*inner_start_ty).ok()?;
218
219 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
237fn 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 if depth >= cx.tcx.recursion_limit().0 {
248 return false;
249 }
250
251 if ty.is_unsafe_cell() || ty.is_phantom_data() {
253 return true;
254 }
255
256 if let Ok(layout) = cx.layout_of(ty) {
258 if layout.is_zst() {
259 return true;
260 }
261 }
262
263 match ty.kind() {
265 ty::Adt(def, args) => {
266 if def.is_enum() {
268 return false;
269 }
270
271 let variant = def.non_enum_variant();
272
273 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 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 ty::UnsafeBinder(_) => false,
319 }
320 }
321
322 inner(cx, ty, 0)
323}