diff options
Diffstat (limited to 'compiler/rustc_lint/src/reference_casting.rs')
-rw-r--r-- | compiler/rustc_lint/src/reference_casting.rs | 144 |
1 files changed, 120 insertions, 24 deletions
diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs index db8d7302a..2577cabb3 100644 --- a/compiler/rustc_lint/src/reference_casting.rs +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -1,7 +1,8 @@ use rustc_ast::Mutability; -use rustc_hir::{Expr, ExprKind, MutTy, TyKind, UnOp}; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, QPath, StmtKind, UnOp}; +use rustc_middle::ty::{self, TypeAndMut}; +use rustc_span::{sym, Span}; use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext}; @@ -12,7 +13,6 @@ declare_lint! { /// ### Example /// /// ```rust,compile_fail - /// # #![deny(invalid_reference_casting)] /// fn x(r: &i32) { /// unsafe { /// *(r as *const i32 as *mut i32) += 1; @@ -30,44 +30,140 @@ declare_lint! { /// `UnsafeCell` is the only way to obtain aliasable data that is considered /// mutable. INVALID_REFERENCE_CASTING, - Allow, + Deny, "casts of `&T` to `&mut T` without interior mutability" } -declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); +#[derive(Default)] +pub struct InvalidReferenceCasting { + casted: FxHashMap<HirId, Span>, +} + +impl_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx rustc_hir::Stmt<'tcx>) { + let StmtKind::Local(local) = stmt.kind else { + return; + }; + let Local { init: Some(init), els: None, .. } = local else { + return; + }; + + if is_cast_from_const_to_mut(cx, init) { + self.casted.insert(local.pat.hir_id, init.span); + } + } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - let ExprKind::Unary(UnOp::Deref, e) = &expr.kind else { return; }; + // &mut <expr> + let inner = if let ExprKind::AddrOf(_, Mutability::Mut, expr) = expr.kind { + expr + // <expr> = ... + } else if let ExprKind::Assign(expr, _, _) = expr.kind { + expr + // <expr> += ... + } else if let ExprKind::AssignOp(_, expr, _) = expr.kind { + expr + } else { + return; + }; + + let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else { + return; + }; - let e = e.peel_blocks(); - let e = if let ExprKind::Cast(e, t) = e.kind - && let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind { + let orig_cast = if is_cast_from_const_to_mut(cx, e) { + None + } else if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind + && let Res::Local(hir_id) = &path.res + && let Some(orig_cast) = self.casted.get(hir_id) { + Some(*orig_cast) + } else { + return; + }; + + cx.emit_spanned_lint( + INVALID_REFERENCE_CASTING, + expr.span, + if matches!(expr.kind, ExprKind::AddrOf(..)) { + InvalidReferenceCastingDiag::BorrowAsMut { orig_cast } + } else { + InvalidReferenceCastingDiag::AssignToRef { orig_cast } + }, + ); + } +} + +fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + let e = e.peel_blocks(); + + fn from_casts<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // <expr> as *mut ... + let mut e = if let ExprKind::Cast(e, t) = e.kind + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() { e + // <expr>.cast_mut() } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) { expr } else { - return; + return None; }; - let e = e.peel_blocks(); - let e = if let ExprKind::Cast(e, t) = e.kind - && let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind { - e - } else if let ExprKind::Call(path, [arg]) = e.kind + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + // <expr> as *mut/const ... or <expr> as <uint> + e = if let ExprKind::Cast(expr, t) = e.kind + && matches!(cx.typeck_results().node_type(t.hir_id).kind(), ty::RawPtr(_) | ty::Uint(_)) { + had_at_least_one_cast = true; + expr + // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) + ) + { + had_at_least_one_cast = true; + expr + // ptr::from_ref(<expr>) + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) { + return Some(arg); + } else if had_at_least_one_cast { + return Some(e); + } else { + return None; + }; + } + } + + fn from_transmute<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + ) -> Option<&'tcx Expr<'tcx>> { + // mem::transmute::<_, *mut _>(<expr>) + if let ExprKind::Call(path, [arg]) = e.kind && let ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() - && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) { - arg + && cx.tcx.is_diagnostic_item(sym::transmute, def_id) + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(e.hir_id).kind() { + Some(arg) } else { - return; - }; - - let e = e.peel_blocks(); - if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind() { - cx.emit_spanned_lint(INVALID_REFERENCE_CASTING, expr.span, InvalidReferenceCastingDiag); + None } } + + let Some(e) = from_casts(cx, e).or_else(|| from_transmute(cx, e)) else { + return false; + }; + + let e = e.peel_blocks(); + matches!(cx.typeck_results().node_type(e.hir_id).kind(), ty::Ref(_, _, Mutability::Not)) } |