1use rustc_hir as hir;
2use rustc_hir::def_id::DefId;
3use rustc_hir::{Expr, Stmt};
4use rustc_middle::ty::{Mutability, TyKind};
5use rustc_session::lint::fcw;
6use rustc_session::{declare_lint, declare_lint_pass};
7use rustc_span::{BytePos, Span};
8
9use crate::lints::{MutRefSugg, RefOfMutStatic, StaticMutRefsInteriorMutabilitySugg};
10use crate::{LateContext, LateLintPass, LintContext};
11
12#[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_lint_defs::Edition::Edition2024,
::rustc_lint_defs::Deny)),
..::rustc_lint_defs::Lint::default_fields_for_macro()
};declare_lint! {
13 pub STATIC_MUT_REFS,
53 Warn,
54 "creating a shared reference to mutable static",
55 @future_incompatible = FutureIncompatibleInfo {
56 reason: fcw!(EditionError 2024 "static-mut-references"),
57 explain_reason: false,
58 };
59 @edition Edition2024 => Deny;
60}
61
62pub 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]);
63
64impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
65 #[allow(rustc::usage_of_ty_tykind)]
66 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
67 let err_span = expr.span;
68 match expr.kind {
69 hir::ExprKind::AddrOf(borrow_kind, m, ex)
70 if #[allow(non_exhaustive_omitted_patterns)] match borrow_kind {
hir::BorrowKind::Ref => true,
_ => false,
}matches!(borrow_kind, hir::BorrowKind::Ref)
71 && let Some(static_mut) = path_is_static_mut(ex, err_span) =>
72 {
73 let source_map = cx.sess().source_map();
74 let snippet = source_map.span_to_snippet(err_span);
75
76 let sugg_span = if let Ok(snippet) = snippet {
77 let exclude_n_bytes: u32 = snippet
80 .chars()
81 .take_while(|ch| ch.is_whitespace() || *ch == '(')
82 .map(|ch| ch.len_utf8() as u32)
83 .sum();
84
85 err_span.with_lo(err_span.lo() + BytePos(exclude_n_bytes)).with_hi(ex.span.lo())
86 } else {
87 err_span.with_hi(ex.span.lo())
88 };
89
90 emit_static_mut_refs(
91 cx,
92 static_mut.err_span,
93 sugg_span,
94 m,
95 !expr.span.from_expansion(),
96 static_mut.def_id,
97 );
98 }
99 hir::ExprKind::MethodCall(_, e, _, _)
100 if let Some(static_mut) = path_is_static_mut(e, expr.span)
101 && let typeck = cx.typeck_results()
102 && let Some(method_def_id) = typeck.type_dependent_def_id(expr.hir_id)
103 && let inputs =
104 cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()
105 && let Some(receiver) = inputs.get(0)
106 && let TyKind::Ref(_, _, m) = receiver.kind() =>
107 {
108 emit_static_mut_refs(
109 cx,
110 static_mut.err_span,
111 static_mut.err_span.shrink_to_lo(),
112 *m,
113 false,
114 static_mut.def_id,
115 );
116 }
117 _ => {}
118 }
119 }
120
121 fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
122 if let hir::StmtKind::Let(loc) = stmt.kind
123 && let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind
124 && let hir::ByRef::Yes(_, m) = ba.0
125 && let Some(init) = loc.init
126 && let Some(static_mut) = path_is_static_mut(init, init.span)
127 {
128 emit_static_mut_refs(
129 cx,
130 static_mut.err_span,
131 static_mut.err_span.shrink_to_lo(),
132 m,
133 false,
134 static_mut.def_id,
135 );
136 }
137 }
138}
139
140struct StaticMutInfo {
141 err_span: Span,
142 def_id: DefId,
143}
144
145fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option<StaticMutInfo> {
146 if err_span.from_expansion() {
147 err_span = expr.span;
148 }
149
150 while let hir::ExprKind::Field(e, _) = expr.kind {
151 expr = e;
152 }
153
154 if let hir::ExprKind::Path(qpath) = expr.kind
155 && let hir::QPath::Resolved(_, path) = qpath
156 && let hir::def::Res::Def(def_kind, def_id) = path.res
157 && let hir::def::DefKind::Static { safety: _, mutability: Mutability::Mut, nested: false } =
158 def_kind
159 {
160 return Some(StaticMutInfo { err_span, def_id });
161 }
162 None
163}
164
165fn emit_static_mut_refs(
166 cx: &LateContext<'_>,
167 span: Span,
168 sugg_span: Span,
169 mutable: Mutability,
170 suggest_addr_of: bool,
171 def_id: DefId,
172) {
173 let (shared_label, shared_note, mut_note, sugg) = match mutable {
174 Mutability::Mut => {
175 let sugg =
176 if suggest_addr_of { Some(MutRefSugg::Mut { span: sugg_span }) } else { None };
177 ("mutable ", false, true, sugg)
178 }
179 Mutability::Not => {
180 let sugg =
181 if suggest_addr_of { Some(MutRefSugg::Shared { span: sugg_span }) } else { None };
182 ("shared ", true, false, sugg)
183 }
184 };
185
186 let (interior_mutability_help, interior_mutability_sugg) =
187 interior_mutability_suggestion(cx, def_id);
188
189 cx.emit_span_lint(
190 STATIC_MUT_REFS,
191 span,
192 RefOfMutStatic {
193 span,
194 sugg,
195 shared_label,
196 shared_note,
197 mut_note,
198 interior_mutability_help,
199 interior_mutability_sugg,
200 },
201 );
202}
203
204fn interior_mutability_suggestion(
209 cx: &LateContext<'_>,
210 def_id: DefId,
211) -> (bool, Option<StaticMutRefsInteriorMutabilitySugg>) {
212 let static_ty = cx.tcx.type_of(def_id).skip_binder();
213 let has_interior_mutability = !static_ty.is_freeze(cx.tcx, cx.typing_env());
214
215 if !has_interior_mutability {
216 return (false, None);
217 }
218
219 let sugg =
220 static_mutability_span(cx, def_id).map(|span| StaticMutRefsInteriorMutabilitySugg { span });
221 (sugg.is_none(), sugg)
222}
223
224fn static_mutability_span(cx: &LateContext<'_>, def_id: DefId) -> Option<Span> {
225 let hir_id = cx.tcx.hir_get_if_local(def_id)?;
226 let hir::Node::Item(item) = hir_id else { return None };
227 let (mutability, ident) = match item.kind {
228 hir::ItemKind::Static(mutability, ident, _, _) => (mutability, ident),
229 _ => return None,
230 };
231 if mutability != hir::Mutability::Mut {
232 return None;
233 }
234
235 let vis_span = item.vis_span.find_ancestor_inside(item.span)?;
236 if !item.span.can_be_used_for_suggestions() || !vis_span.can_be_used_for_suggestions() {
237 return None;
238 }
239
240 let header_span = vis_span.between(ident.span);
241 if !header_span.can_be_used_for_suggestions() {
242 return None;
243 }
244
245 let source_map = cx.sess().source_map();
246 let snippet = source_map.span_to_snippet(header_span).ok()?;
247
248 let (_static_start, static_end) = find_word(&snippet, "static", 0)?;
249 let (mut_start, mut_end) = find_word(&snippet, "mut", static_end)?;
250 let mut_end = extend_trailing_space(&snippet, mut_end);
251
252 Some(
253 header_span
254 .with_lo(header_span.lo() + BytePos(mut_start as u32))
255 .with_hi(header_span.lo() + BytePos(mut_end as u32)),
256 )
257}
258
259fn find_word(snippet: &str, word: &str, start: usize) -> Option<(usize, usize)> {
260 let bytes = snippet.as_bytes();
261 let word_bytes = word.as_bytes();
262 let mut search = start;
263 while search <= snippet.len() {
264 let found = snippet[search..].find(word)?;
265 let idx = search + found;
266 let end = idx + word_bytes.len();
267 let before_ok = idx == 0 || !is_ident_char(bytes[idx - 1]);
268 let after_ok = end >= bytes.len() || !is_ident_char(bytes[end]);
269 if before_ok && after_ok {
270 return Some((idx, end));
271 }
272 search = end;
273 }
274 None
275}
276
277fn is_ident_char(byte: u8) -> bool {
278 byte.is_ascii_alphanumeric() || byte == b'_'
279}
280
281fn extend_trailing_space(snippet: &str, mut end: usize) -> usize {
282 if let Some(ch) = snippet[end..].chars().next()
283 && (ch == ' ' || ch == '\t')
284 {
285 end += ch.len_utf8();
286 }
287 end
288}