Skip to content

Commit b9914a8

Browse files
committed
Add interior-mutability suggestion to static_mut_refs
1 parent 3d087e6 commit b9914a8

7 files changed

Lines changed: 203 additions & 12 deletions

File tree

compiler/rustc_lint/messages.ftl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,13 +842,17 @@ lint_single_use_lifetime = lifetime parameter `{$ident}` only used once
842842
843843
lint_span_use_eq_ctxt = use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
844844
845+
lint_static_mut_refs_interior_mutability_help =
846+
use a type that relies on "interior mutability" instead; to read more on this, visit <https://doc.rust-lang.org/reference/interior-mutability.html>
847+
lint_static_mut_refs_interior_mutability_suggestion =
848+
this type already provides "interior mutability", so its binding doesn't need to be declared as mutable
849+
845850
lint_static_mut_refs_lint = creating a {$shared_label}reference to mutable static
846851
.label = {$shared_label}reference to mutable static
847852
.suggestion = use `&raw const` instead to create a raw pointer
848853
.suggestion_mut = use `&raw mut` instead to create a raw pointer
849854
.shared_note = shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
850855
.mut_note = mutable references to mutable statics are dangerous; it's undefined behavior if any other pointer to the static is used or if any other reference is created for the static while the mutable reference lives
851-
852856
lint_supertrait_as_deref_target = this `Deref` implementation is covered by an implicit supertrait coercion
853857
.label = `{$self_ty}` implements `Deref<Target = dyn {$target_principal}>` which conflicts with supertrait `{$supertrait_principal}`
854858
.label2 = target type is a supertrait of `{$self_ty}`

compiler/rustc_lint/src/lints.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,6 +2917,10 @@ pub(crate) struct RefOfMutStatic<'a> {
29172917
pub shared_note: bool,
29182918
#[note(lint_mut_note)]
29192919
pub mut_note: bool,
2920+
#[help(lint_static_mut_refs_interior_mutability_help)]
2921+
pub interior_mutability_help: bool,
2922+
#[subdiagnostic]
2923+
pub interior_mutability_sugg: Option<StaticMutRefsInteriorMutabilitySugg>,
29202924
}
29212925

29222926
#[derive(Subdiagnostic)]
@@ -2937,6 +2941,18 @@ pub(crate) enum MutRefSugg {
29372941
},
29382942
}
29392943

2944+
#[derive(Subdiagnostic)]
2945+
#[suggestion(
2946+
lint_static_mut_refs_interior_mutability_suggestion,
2947+
style = "verbose",
2948+
applicability = "maybe-incorrect",
2949+
code = ""
2950+
)]
2951+
pub(crate) struct StaticMutRefsInteriorMutabilitySugg {
2952+
#[primary_span]
2953+
pub span: Span,
2954+
}
2955+
29402956
#[derive(LintDiagnostic)]
29412957
#[diag(lint_unqualified_local_imports)]
29422958
pub(crate) struct UnqualifiedLocalImportsDiag {}

compiler/rustc_lint/src/static_mut_refs.rs

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use rustc_hir as hir;
2+
use rustc_hir::def_id::DefId;
23
use rustc_hir::{Expr, Stmt};
34
use rustc_middle::ty::{Mutability, TyKind};
45
use rustc_session::lint::fcw;
56
use rustc_session::{declare_lint, declare_lint_pass};
67
use rustc_span::{BytePos, Span};
78

8-
use crate::lints::{MutRefSugg, RefOfMutStatic};
9+
use crate::lints::{MutRefSugg, RefOfMutStatic, StaticMutRefsInteriorMutabilitySugg};
910
use crate::{LateContext, LateLintPass, LintContext};
1011

1112
declare_lint! {
@@ -67,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
6768
match expr.kind {
6869
hir::ExprKind::AddrOf(borrow_kind, m, ex)
6970
if matches!(borrow_kind, hir::BorrowKind::Ref)
70-
&& let Some(err_span) = path_is_static_mut(ex, err_span) =>
71+
&& let Some(static_mut) = path_is_static_mut(ex, err_span) =>
7172
{
7273
let source_map = cx.sess().source_map();
7374
let snippet = source_map.span_to_snippet(err_span);
@@ -86,18 +87,32 @@ impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
8687
err_span.with_hi(ex.span.lo())
8788
};
8889

89-
emit_static_mut_refs(cx, err_span, sugg_span, m, !expr.span.from_expansion());
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+
);
9098
}
9199
hir::ExprKind::MethodCall(_, e, _, _)
92-
if let Some(err_span) = path_is_static_mut(e, expr.span)
100+
if let Some(static_mut) = path_is_static_mut(e, expr.span)
93101
&& let typeck = cx.typeck_results()
94102
&& let Some(method_def_id) = typeck.type_dependent_def_id(expr.hir_id)
95103
&& let inputs =
96104
cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()
97105
&& let Some(receiver) = inputs.get(0)
98106
&& let TyKind::Ref(_, _, m) = receiver.kind() =>
99107
{
100-
emit_static_mut_refs(cx, err_span, err_span.shrink_to_lo(), *m, false);
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+
);
101116
}
102117
_ => {}
103118
}
@@ -108,14 +123,26 @@ impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
108123
&& let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind
109124
&& let hir::ByRef::Yes(_, m) = ba.0
110125
&& let Some(init) = loc.init
111-
&& let Some(err_span) = path_is_static_mut(init, init.span)
126+
&& let Some(static_mut) = path_is_static_mut(init, init.span)
112127
{
113-
emit_static_mut_refs(cx, err_span, err_span.shrink_to_lo(), m, false);
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+
);
114136
}
115137
}
116138
}
117139

118-
fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option<Span> {
140+
struct StaticMutInfo {
141+
err_span: Span,
142+
def_id: DefId,
143+
}
144+
145+
fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option<StaticMutInfo> {
119146
if err_span.from_expansion() {
120147
err_span = expr.span;
121148
}
@@ -126,11 +153,11 @@ fn path_is_static_mut(mut expr: &hir::Expr<'_>, mut err_span: Span) -> Option<Sp
126153

127154
if let hir::ExprKind::Path(qpath) = expr.kind
128155
&& let hir::QPath::Resolved(_, path) = qpath
129-
&& let hir::def::Res::Def(def_kind, _) = path.res
156+
&& let hir::def::Res::Def(def_kind, def_id) = path.res
130157
&& let hir::def::DefKind::Static { safety: _, mutability: Mutability::Mut, nested: false } =
131158
def_kind
132159
{
133-
return Some(err_span);
160+
return Some(StaticMutInfo { err_span, def_id });
134161
}
135162
None
136163
}
@@ -141,6 +168,7 @@ fn emit_static_mut_refs(
141168
sugg_span: Span,
142169
mutable: Mutability,
143170
suggest_addr_of: bool,
171+
def_id: DefId,
144172
) {
145173
let (shared_label, shared_note, mut_note, sugg) = match mutable {
146174
Mutability::Mut => {
@@ -155,9 +183,102 @@ fn emit_static_mut_refs(
155183
}
156184
};
157185

186+
let (interior_mutability_help, interior_mutability_sugg) =
187+
interior_mutability_suggestion(cx, def_id);
188+
158189
cx.emit_span_lint(
159190
STATIC_MUT_REFS,
160191
span,
161-
RefOfMutStatic { span, sugg, shared_label, shared_note, mut_note },
192+
RefOfMutStatic {
193+
span,
194+
sugg,
195+
shared_label,
196+
shared_note,
197+
mut_note,
198+
interior_mutability_help,
199+
interior_mutability_sugg,
200+
},
162201
);
163202
}
203+
204+
fn interior_mutability_suggestion(
205+
cx: &LateContext<'_>,
206+
def_id: DefId,
207+
) -> (bool, Option<StaticMutRefsInteriorMutabilitySugg>) {
208+
let static_ty = cx.tcx.type_of(def_id).skip_binder();
209+
let has_interior_mutability = !static_ty.is_freeze(cx.tcx, cx.typing_env());
210+
211+
if !has_interior_mutability {
212+
return (false, None);
213+
}
214+
215+
let sugg =
216+
static_mutability_span(cx, def_id).map(|span| StaticMutRefsInteriorMutabilitySugg { span });
217+
(true, sugg)
218+
}
219+
220+
fn static_mutability_span(cx: &LateContext<'_>, def_id: DefId) -> Option<Span> {
221+
let hir_id = cx.tcx.hir_get_if_local(def_id)?;
222+
let hir::Node::Item(item) = hir_id else { return None };
223+
let (mutability, ident) = match item.kind {
224+
hir::ItemKind::Static(mutability, ident, _, _) => (mutability, ident),
225+
_ => return None,
226+
};
227+
if mutability != hir::Mutability::Mut {
228+
return None;
229+
}
230+
231+
let vis_span = item.vis_span.find_ancestor_inside(item.span)?;
232+
if !item.span.can_be_used_for_suggestions() || !vis_span.can_be_used_for_suggestions() {
233+
return None;
234+
}
235+
236+
let header_span = vis_span.between(ident.span);
237+
if !header_span.can_be_used_for_suggestions() {
238+
return None;
239+
}
240+
241+
let source_map = cx.sess().source_map();
242+
let snippet = source_map.span_to_snippet(header_span).ok()?;
243+
244+
let (_static_start, static_end) = find_word(&snippet, "static", 0)?;
245+
let (mut_start, mut_end) = find_word(&snippet, "mut", static_end)?;
246+
let mut_end = extend_trailing_space(&snippet, mut_end);
247+
248+
Some(
249+
header_span
250+
.with_lo(header_span.lo() + BytePos(mut_start as u32))
251+
.with_hi(header_span.lo() + BytePos(mut_end as u32)),
252+
)
253+
}
254+
255+
fn find_word(snippet: &str, word: &str, start: usize) -> Option<(usize, usize)> {
256+
let bytes = snippet.as_bytes();
257+
let word_bytes = word.as_bytes();
258+
let mut search = start;
259+
while search <= snippet.len() {
260+
let found = snippet[search..].find(word)?;
261+
let idx = search + found;
262+
let end = idx + word_bytes.len();
263+
let before_ok = idx == 0 || !is_ident_char(bytes[idx - 1]);
264+
let after_ok = end >= bytes.len() || !is_ident_char(bytes[end]);
265+
if before_ok && after_ok {
266+
return Some((idx, end));
267+
}
268+
search = end;
269+
}
270+
None
271+
}
272+
273+
fn is_ident_char(byte: u8) -> bool {
274+
byte.is_ascii_alphanumeric() || byte == b'_'
275+
}
276+
277+
fn extend_trailing_space(snippet: &str, mut end: usize) -> usize {
278+
if let Some(ch) = snippet[end..].chars().next()
279+
&& (ch == ' ' || ch == '\t')
280+
{
281+
end += ch.len_utf8();
282+
}
283+
end
284+
}

tests/ui/issues/issue-39367.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ LL | | });
1111
|
1212
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
1313
= note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
14+
= help: use a type that relies on "interior mutability" instead; to read more on this, visit <https://doc.rust-lang.org/reference/interior-mutability.html>
1415
= note: `#[warn(static_mut_refs)]` (part of `#[warn(rust_2024_compatibility)]`) on by default
16+
help: this type already provides "interior mutability", so its binding doesn't need to be declared as mutable
17+
|
18+
LL - static mut ONCE: Once = Once::new();
19+
LL + static ONCE: Once = Once::new();
20+
|
1521

1622
warning: 1 warning emitted
1723

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ edition:2024
2+
//@ run-rustfix
3+
4+
#![allow(unused_unsafe)]
5+
6+
use std::sync::Mutex;
7+
8+
static STDINOUT_MUTEX: Mutex<bool> = Mutex::new(false);
9+
10+
fn main() {
11+
let _lock = unsafe { STDINOUT_MUTEX.lock().unwrap() };
12+
//~^ ERROR creating a shared reference to mutable static [static_mut_refs]
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ edition:2024
2+
//@ run-rustfix
3+
4+
#![allow(unused_unsafe)]
5+
6+
use std::sync::Mutex;
7+
8+
static mut STDINOUT_MUTEX: Mutex<bool> = Mutex::new(false);
9+
10+
fn main() {
11+
let _lock = unsafe { STDINOUT_MUTEX.lock().unwrap() };
12+
//~^ ERROR creating a shared reference to mutable static [static_mut_refs]
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: creating a shared reference to mutable static
2+
--> $DIR/static-mut-refs-interior-mutability.rs:11:26
3+
|
4+
LL | let _lock = unsafe { STDINOUT_MUTEX.lock().unwrap() };
5+
| ^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static
6+
|
7+
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
8+
= note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
9+
= help: use a type that relies on "interior mutability" instead; to read more on this, visit <https://doc.rust-lang.org/reference/interior-mutability.html>
10+
= note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
11+
help: this type already provides "interior mutability", so its binding doesn't need to be declared as mutable
12+
|
13+
LL - static mut STDINOUT_MUTEX: Mutex<bool> = Mutex::new(false);
14+
LL + static STDINOUT_MUTEX: Mutex<bool> = Mutex::new(false);
15+
|
16+
17+
error: aborting due to 1 previous error
18+

0 commit comments

Comments
 (0)