use rustc_hir as hir; use rustc_hir::{Expr, Stmt}; use rustc_middle::ty::{Mutability, TyKind}; use rustc_session::lint::FutureIncompatibilityReason; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::edition::Edition; use rustc_span::{BytePos, Span}; use crate::lints::{MutRefSugg, RefOfMutStatic}; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { /// The `static_mut_refs` lint checks for shared or mutable references /// of mutable static inside `unsafe` blocks and `unsafe` functions. /// /// ### Example /// /// ```rust,edition2021 /// fn main() { /// static mut X: i32 = 23; /// static mut Y: i32 = 24; /// /// unsafe { /// let y = &X; /// let ref x = X; /// let (x, y) = (&X, &Y); /// foo(&X); /// } /// } /// /// unsafe fn _foo() { /// static mut X: i32 = 23; /// static mut Y: i32 = 24; /// /// let y = &X; /// let ref x = X; /// let (x, y) = (&X, &Y); /// foo(&X); /// } /// /// fn foo<'a>(_x: &'a i32) {} /// ``` /// /// {{produces}} /// /// ### Explanation /// /// Shared or mutable references of mutable static are almost always a mistake and /// can lead to undefined behavior and various other problems in your code. /// /// This lint is "warn" by default on editions up to 2021, in 2024 is "deny". pub STATIC_MUT_REFS, Warn, "shared references or mutable references of mutable static is discouraged", @future_incompatible = FutureIncompatibleInfo { reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024), reference: "", explain_reason: false, }; @edition Edition2024 => Deny; } declare_lint_pass!(StaticMutRefs => [STATIC_MUT_REFS]); impl<'tcx> LateLintPass<'tcx> for StaticMutRefs { #[allow(rustc::usage_of_ty_tykind)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { let err_span = expr.span; match expr.kind { hir::ExprKind::AddrOf(borrow_kind, m, ex) if matches!(borrow_kind, hir::BorrowKind::Ref) && let Some(err_span) = path_is_static_mut(ex, err_span) => { let source_map = cx.sess().source_map(); let snippet = source_map.span_to_snippet(err_span); let sugg_span = if let Ok(snippet) = snippet { // ( ( &IDENT ) ) // ~~~~ exclude these from the suggestion span to avoid unmatching parens let exclude_n_bytes: u32 = snippet .chars() .take_while(|ch| ch.is_whitespace() || *ch == '(') .map(|ch| ch.len_utf8() as u32) .sum(); err_span.with_lo(err_span.lo() + BytePos(exclude_n_bytes)).with_hi(ex.span.lo()) } else { err_span.with_hi(ex.span.lo()) }; emit_static_mut_refs(cx, err_span, sugg_span, m, !expr.span.from_expansion()); } hir::ExprKind::MethodCall(_, e, _, _) if let Some(err_span) = path_is_static_mut(e, expr.span) && let typeck = cx.typeck_results() && let Some(method_def_id) = typeck.type_dependent_def_id(expr.hir_id) && let inputs = cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder() && let Some(receiver) = inputs.get(0) && let TyKind::Ref(_, _, m) = receiver.kind() => { emit_static_mut_refs(cx, err_span, err_span.shrink_to_lo(), *m, false); } _ => {} } } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) { if let hir::StmtKind::Let(loc) = stmt.kind && let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind && let hir::ByRef::Yes(m) = ba.0 && let Some(init) = loc.init && let Some(err_span) = path_is_static_mut(init, init.span) { emit_static_mut_refs(cx, err_span, err_span.shrink_to_lo(), m, false); } } } fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option { if err_span.from_expansion() { err_span = expr.span; } while let hir::ExprKind::Field(e, _) = expr.kind { expr = e; } if let hir::ExprKind::Path(qpath) = expr.kind && let hir::QPath::Resolved(_, path) = qpath && let hir::def::Res::Def(def_kind, _) = path.res && let hir::def::DefKind::Static { safety: _, mutability: Mutability::Mut, nested: false } = def_kind { return Some(err_span); } None } fn emit_static_mut_refs( cx: &LateContext<'_>, span: Span, sugg_span: Span, mutable: Mutability, suggest_addr_of: bool, ) { let (shared_label, shared_note, mut_note, sugg) = match mutable { Mutability::Mut => { let sugg = if suggest_addr_of { Some(MutRefSugg::Mut { span: sugg_span }) } else { None }; ("mutable ", false, true, sugg) } Mutability::Not => { let sugg = if suggest_addr_of { Some(MutRefSugg::Shared { span: sugg_span }) } else { None }; ("shared ", true, false, sugg) } }; cx.emit_span_lint( STATIC_MUT_REFS, span, RefOfMutStatic { span, sugg, shared_label, shared_note, mut_note }, ); }