Skip to main content

rustc_lint/
static_mut_refs.rs

1use rustc_hir as hir;
2use rustc_hir::def_id::DefId;
3use rustc_hir::{Expr, Stmt};
4use rustc_lint_defs::{declare_lint, declare_lint_pass, fcw};
5use rustc_middle::ty::{Mutability, TyKind};
6use rustc_span::{BytePos, Span};
7
8use crate::diagnostics::{MutRefSugg, RefOfMutStatic, StaticMutRefsInteriorMutabilitySugg};
9use crate::{LateContext, LateLintPass, LintContext};
10
11#[doc =
r" The `static_mut_refs` lint checks for shared or mutable references"]
#[doc = r" of mutable static inside `unsafe` blocks and `unsafe` functions."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,edition2021"]
#[doc = r" fn main() {"]
#[doc = r"     static mut X: i32 = 23;"]
#[doc = r"     static mut Y: i32 = 24;"]
#[doc = r""]
#[doc = r"     unsafe {"]
#[doc = r"         let y = &X;"]
#[doc = r"         let ref x = X;"]
#[doc = r"         let (x, y) = (&X, &Y);"]
#[doc = r"         foo(&X);"]
#[doc = r"     }"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" unsafe fn _foo() {"]
#[doc = r"     static mut X: i32 = 23;"]
#[doc = r"     static mut Y: i32 = 24;"]
#[doc = r""]
#[doc = r"     let y = &X;"]
#[doc = r"     let ref x = X;"]
#[doc = r"     let (x, y) = (&X, &Y);"]
#[doc = r"     foo(&X);"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn foo<'a>(_x: &'a i32) {}"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Shared or mutable references of mutable static are almost always a mistake and"]
#[doc =
r" can lead to undefined behavior and various other problems in your code."]
#[doc = r""]
#[doc =
r#" This lint is "warn" by default on editions up to 2021, in 2024 is "deny"."#]
pub static STATIC_MUT_REFS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "STATIC_MUT_REFS",
            default_level: ::rustc_lint_defs::Warn,
            desc: "creating a shared reference to mutable static",
            is_externally_loaded: false,
            future_incompatible: Some(::rustc_lint_defs::FutureIncompatibleInfo {
                    reason: ::rustc_lint_defs::FutureIncompatibilityReason::EditionError(::rustc_lint_defs::EditionFcw {
                            edition: rustc_span::edition::Edition::Edition2024,
                            page_slug: "static-mut-references",
                        }),
                    explain_reason: false,
                    ..::rustc_lint_defs::FutureIncompatibleInfo::default_fields_for_macro()
                }),
            edition_lint_opts: Some((::rustc_span::edition::Edition::Edition2024,
                    ::rustc_lint_defs::Deny)),
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
12    /// The `static_mut_refs` lint checks for shared or mutable references
13    /// of mutable static inside `unsafe` blocks and `unsafe` functions.
14    ///
15    /// ### Example
16    ///
17    /// ```rust,edition2021
18    /// fn main() {
19    ///     static mut X: i32 = 23;
20    ///     static mut Y: i32 = 24;
21    ///
22    ///     unsafe {
23    ///         let y = &X;
24    ///         let ref x = X;
25    ///         let (x, y) = (&X, &Y);
26    ///         foo(&X);
27    ///     }
28    /// }
29    ///
30    /// unsafe fn _foo() {
31    ///     static mut X: i32 = 23;
32    ///     static mut Y: i32 = 24;
33    ///
34    ///     let y = &X;
35    ///     let ref x = X;
36    ///     let (x, y) = (&X, &Y);
37    ///     foo(&X);
38    /// }
39    ///
40    /// fn foo<'a>(_x: &'a i32) {}
41    /// ```
42    ///
43    /// {{produces}}
44    ///
45    /// ### Explanation
46    ///
47    /// Shared or mutable references of mutable static are almost always a mistake and
48    /// can lead to undefined behavior and various other problems in your code.
49    ///
50    /// This lint is "warn" by default on editions up to 2021, in 2024 is "deny".
51    pub STATIC_MUT_REFS,
52    Warn,
53    "creating a shared reference to mutable static",
54    @future_incompatible = FutureIncompatibleInfo {
55        reason: fcw!(EditionError 2024 "static-mut-references"),
56        explain_reason: false,
57    };
58    @edition Edition2024 => Deny;
59}
60
61pub struct StaticMutRefs;
#[automatically_derived]
impl ::core::marker::Copy for StaticMutRefs { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for StaticMutRefs { }
#[automatically_derived]
impl ::core::clone::Clone for StaticMutRefs {
    #[inline]
    fn clone(&self) -> StaticMutRefs { *self }
}
impl ::rustc_lint_defs::LintPass for StaticMutRefs {
    fn name(&self) -> &'static str { "StaticMutRefs" }
    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(),
                [STATIC_MUT_REFS]))
    }
}
impl StaticMutRefs {
    #[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(),
                [STATIC_MUT_REFS]))
    }
}declare_lint_pass!(StaticMutRefs => [STATIC_MUT_REFS]);
62
63impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
64    #[allow(rustc::usage_of_ty_tykind)]
65    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
66        let err_span = expr.span;
67        match expr.kind {
68            hir::ExprKind::AddrOf(borrow_kind, m, ex)
69                if #[allow(non_exhaustive_omitted_patterns)] match borrow_kind {
    hir::BorrowKind::Ref => true,
    _ => false,
}matches!(borrow_kind, hir::BorrowKind::Ref)
70                    && let Some(static_mut) = path_is_static_mut(ex, err_span) =>
71            {
72                let source_map = cx.sess().source_map();
73                let snippet = source_map.span_to_snippet(err_span);
74
75                let sugg_span = if let Ok(snippet) = snippet {
76                    // ( ( &IDENT ) )
77                    // ~~~~ exclude these from the suggestion span to avoid unmatching parens
78                    let exclude_n_bytes: u32 = snippet
79                        .chars()
80                        .take_while(|ch| ch.is_whitespace() || *ch == '(')
81                        .map(|ch| ch.len_utf8() as u32)
82                        .sum();
83
84                    err_span.with_lo(err_span.lo() + BytePos(exclude_n_bytes)).with_hi(ex.span.lo())
85                } else {
86                    err_span.with_hi(ex.span.lo())
87                };
88
89                emit_static_mut_refs(
90                    cx,
91                    static_mut.err_span,
92                    sugg_span,
93                    m,
94                    !expr.span.from_expansion(),
95                    static_mut.def_id,
96                );
97            }
98            hir::ExprKind::MethodCall(_, e, _, _)
99                if let Some(static_mut) = path_is_static_mut(e, expr.span)
100                    && let typeck = cx.typeck_results()
101                    && let Some(method_def_id) = typeck.type_dependent_def_id(expr.hir_id)
102                    && let inputs =
103                        cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()
104                    && let Some(receiver) = inputs.get(0)
105                    && let TyKind::Ref(_, _, m) = receiver.kind() =>
106            {
107                emit_static_mut_refs(
108                    cx,
109                    static_mut.err_span,
110                    static_mut.err_span.shrink_to_lo(),
111                    *m,
112                    false,
113                    static_mut.def_id,
114                );
115            }
116            _ => {}
117        }
118    }
119
120    fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
121        if let hir::StmtKind::Let(loc) = stmt.kind
122            && let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind
123            && let hir::ByRef::Yes(_, m) = ba.0
124            && let Some(init) = loc.init
125            && let Some(static_mut) = path_is_static_mut(init, init.span)
126        {
127            emit_static_mut_refs(
128                cx,
129                static_mut.err_span,
130                static_mut.err_span.shrink_to_lo(),
131                m,
132                false,
133                static_mut.def_id,
134            );
135        }
136    }
137}
138
139struct StaticMutInfo {
140    err_span: Span,
141    def_id: DefId,
142}
143
144fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option<StaticMutInfo> {
145    if err_span.from_expansion() {
146        err_span = expr.span;
147    }
148
149    while let hir::ExprKind::Field(e, _) = expr.kind {
150        expr = e;
151    }
152
153    if let hir::ExprKind::Path(qpath) = expr.kind
154        && let hir::QPath::Resolved(_, path) = qpath
155        && let hir::def::Res::Def(def_kind, def_id) = path.res
156        && let hir::def::DefKind::Static { safety: _, mutability: Mutability::Mut, nested: false } =
157            def_kind
158    {
159        return Some(StaticMutInfo { err_span, def_id });
160    }
161    None
162}
163
164fn emit_static_mut_refs(
165    cx: &LateContext<'_>,
166    span: Span,
167    sugg_span: Span,
168    mutable: Mutability,
169    suggest_addr_of: bool,
170    def_id: DefId,
171) {
172    let (shared_label, shared_note, mut_note, sugg) = match mutable {
173        Mutability::Mut => {
174            let sugg =
175                if suggest_addr_of { Some(MutRefSugg::Mut { span: sugg_span }) } else { None };
176            ("mutable ", false, true, sugg)
177        }
178        Mutability::Not => {
179            let sugg =
180                if suggest_addr_of { Some(MutRefSugg::Shared { span: sugg_span }) } else { None };
181            ("shared ", true, false, sugg)
182        }
183    };
184
185    let (interior_mutability_help, interior_mutability_sugg) =
186        interior_mutability_suggestion(cx, def_id, mut_note, suggest_addr_of);
187
188    cx.emit_span_lint(
189        STATIC_MUT_REFS,
190        span,
191        RefOfMutStatic {
192            span,
193            sugg,
194            shared_label,
195            shared_note,
196            mut_note,
197            interior_mutability_help,
198            interior_mutability_sugg,
199        },
200    );
201}
202
203// FIXME: This builds suggestion spans by handcrafting from source text.
204// Replace this with HIR-based handling once we can identify the `mut` token
205// in the static declaration that way.
206// Context: https://github.com/rust-lang/rust/pull/151362/changes#r3210767018
207fn interior_mutability_suggestion(
208    cx: &LateContext<'_>,
209    def_id: DefId,
210    mut_ref: bool,
211    suggest_addr_of: bool,
212) -> (bool, Option<StaticMutRefsInteriorMutabilitySugg>) {
213    let static_ty = cx.tcx.type_of(def_id).skip_binder();
214    let has_interior_mutability = !static_ty.is_freeze(cx.tcx, cx.typing_env());
215
216    if !has_interior_mutability {
217        return (!suggest_addr_of, None);
218    }
219
220    if mut_ref {
221        return (false, None);
222    }
223
224    let sugg =
225        static_mutability_span(cx, def_id).map(|span| StaticMutRefsInteriorMutabilitySugg { span });
226    (false, sugg)
227}
228
229fn static_mutability_span(cx: &LateContext<'_>, def_id: DefId) -> Option<Span> {
230    let hir_id = cx.tcx.hir_get_if_local(def_id)?;
231    let hir::Node::Item(item) = hir_id else { return None };
232    let (mutability, ident) = match item.kind {
233        hir::ItemKind::Static(mutability, ident, _, _) => (mutability, ident),
234        _ => return None,
235    };
236    if mutability != hir::Mutability::Mut {
237        return None;
238    }
239
240    let vis_span = item.vis_span.find_ancestor_inside(item.span)?;
241    if !item.span.can_be_used_for_suggestions() || !vis_span.can_be_used_for_suggestions() {
242        return None;
243    }
244
245    let header_span = vis_span.between(ident.span);
246    if !header_span.can_be_used_for_suggestions() {
247        return None;
248    }
249
250    let source_map = cx.sess().source_map();
251    let snippet = source_map.span_to_snippet(header_span).ok()?;
252
253    let (_static_start, static_end) = find_word(&snippet, "static", 0)?;
254    let (mut_start, mut_end) = find_word(&snippet, "mut", static_end)?;
255    let mut_end = extend_trailing_space(&snippet, mut_end);
256
257    Some(
258        header_span
259            .with_lo(header_span.lo() + BytePos(mut_start as u32))
260            .with_hi(header_span.lo() + BytePos(mut_end as u32)),
261    )
262}
263
264fn find_word(snippet: &str, word: &str, start: usize) -> Option<(usize, usize)> {
265    let bytes = snippet.as_bytes();
266    let word_bytes = word.as_bytes();
267    let mut search = start;
268    while search <= snippet.len() {
269        let found = snippet[search..].find(word)?;
270        let idx = search + found;
271        let end = idx + word_bytes.len();
272        let before_ok = idx == 0 || !is_ident_char(bytes[idx - 1]);
273        let after_ok = end >= bytes.len() || !is_ident_char(bytes[end]);
274        if before_ok && after_ok {
275            return Some((idx, end));
276        }
277        search = end;
278    }
279    None
280}
281
282fn is_ident_char(byte: u8) -> bool {
283    byte.is_ascii_alphanumeric() || byte == b'_'
284}
285
286fn extend_trailing_space(snippet: &str, mut end: usize) -> usize {
287    if let Some(ch) = snippet[end..].chars().next()
288        && (ch == ' ' || ch == '\t')
289    {
290        end += ch.len_utf8();
291    }
292    end
293}