Skip to content

Commit a2f65c3

Browse files
committed
Add interior_mutable_const_item_mutations lint
1 parent 20f59e0 commit a2f65c3

12 files changed

+2603
-0
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,14 @@ lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a point
403403
.suggestion_with_exposed_provenance = use `std::ptr::with_exposed_provenance{$suffix}` instead to use a previously exposed provenance
404404
.suggestion_without_provenance_mut = if you truly mean to create a pointer without provenance, use `std::ptr::without_provenance_mut`
405405
406+
lint_interior_mutable_const_item_mutations =
407+
mutation of an interior mutable `const` item with call to `{$method_name}`
408+
.label = `{$const_name}` is a interior mutable `const` item of type `{$const_ty}`
409+
.temporary = each usage of a `const` item creates a new temporary
410+
.never_original = only the temporaries and never the original `const {$const_name}` will be modified
411+
.suggestion_static = for a shared instance of `{$const_name}`, consider making it a `static` item instead
412+
.help = for more details on interior mutability see <https://doc.rust-lang.org/reference/interior-mutability.html>
413+
406414
lint_invalid_asm_label_binary = avoid using labels containing only the digits `0` and `1` in inline assembly
407415
.label = use a different label that doesn't start with `0` or `1`
408416
.help = start numbering with `2` instead
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use rustc_hir::attrs::AttributeKind;
2+
use rustc_hir::def::{DefKind, Res};
3+
use rustc_hir::{Expr, ExprKind, ItemKind, Node, find_attr};
4+
use rustc_session::{declare_lint, declare_lint_pass};
5+
6+
use crate::lints::{
7+
InteriorMutableConstItemMutationsDiag, InteriorMutableConstItemMutationsSuggestionStatic,
8+
};
9+
use crate::{LateContext, LateLintPass, LintContext};
10+
11+
declare_lint! {
12+
/// The `interior_mutable_const_item_mutations` lint checks for calls which
13+
/// mutates an interior mutable const-item.
14+
///
15+
/// ### Example
16+
///
17+
/// ```rust
18+
/// use std::sync::Once;
19+
///
20+
/// const INIT: Once = Once::new(); // using `INIT` will always create a temporary and
21+
/// // never modify it-self on use, should be a `static`
22+
/// // instead for shared use
23+
///
24+
/// fn init() {
25+
/// INIT.call_once(|| {
26+
/// println!("Once::call_once first call");
27+
/// });
28+
/// INIT.call_once(|| { // this second will also print
29+
/// println!("Once::call_once second call"); // as each call to `INIT` creates
30+
/// }); // new temporary
31+
/// }
32+
/// ```
33+
///
34+
/// {{produces}}
35+
///
36+
/// ### Explanation
37+
///
38+
/// Calling a method which mutates an interior mutable type has no effect as const-item
39+
/// are essentially inlined wherever they are used, meaning that they are copied
40+
/// directly into the relevant context when used rendering modification through
41+
/// interior mutability ineffective across usage of that const-item.
42+
///
43+
/// The current implementation of this lint only warns on significant `std` and
44+
/// `core` interior mutable types, like `Once`, `AtomicI32`, ... this is done out
45+
/// of prudence to avoid false-positive and may be extended in the future.
46+
pub INTERIOR_MUTABLE_CONST_ITEM_MUTATIONS,
47+
Warn,
48+
"checks for calls which mutates a interior mutable const-item"
49+
}
50+
51+
declare_lint_pass!(InteriorMutableConsts => [INTERIOR_MUTABLE_CONST_ITEM_MUTATIONS]);
52+
53+
impl<'tcx> LateLintPass<'tcx> for InteriorMutableConsts {
54+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
55+
let typeck = cx.typeck_results();
56+
57+
let (method_did, receiver) = match expr.kind {
58+
// matching on `<receiver>.method(..)`
59+
ExprKind::MethodCall(_, receiver, _, _) => {
60+
(typeck.type_dependent_def_id(expr.hir_id), receiver)
61+
}
62+
// matching on `function(&<receiver>, ...)`
63+
ExprKind::Call(path, [receiver, ..]) => match receiver.kind {
64+
ExprKind::AddrOf(_, _, receiver) => match path.kind {
65+
ExprKind::Path(ref qpath) => {
66+
(cx.qpath_res(qpath, path.hir_id).opt_def_id(), receiver)
67+
}
68+
_ => return,
69+
},
70+
_ => return,
71+
},
72+
_ => return,
73+
};
74+
75+
let Some(method_did) = method_did else {
76+
return;
77+
};
78+
79+
if let ExprKind::Path(qpath) = &receiver.kind
80+
&& let Res::Def(DefKind::Const | DefKind::AssocConst, const_did) =
81+
typeck.qpath_res(qpath, receiver.hir_id)
82+
// Let's do the attribute check after the other checks for perf reasons
83+
&& find_attr!(
84+
cx.tcx.get_all_attrs(method_did),
85+
AttributeKind::RustcMustNotCallOnInteriorMutableConsts(_)
86+
)
87+
&& let Some(method_name) = cx.tcx.opt_item_ident(method_did)
88+
&& let Some(const_name) = cx.tcx.opt_item_ident(const_did)
89+
&& let Some(const_ty) = typeck.node_type_opt(receiver.hir_id)
90+
{
91+
// Find the local `const`-item and create the suggestion to use `static` instead
92+
let sugg_static = if let Some(Node::Item(const_item)) =
93+
cx.tcx.hir_get_if_local(const_did)
94+
&& let ItemKind::Const(ident, _generics, _ty, _body_id) = const_item.kind
95+
{
96+
if let Some(vis_span) = const_item.vis_span.find_ancestor_inside(const_item.span)
97+
&& const_item.span.can_be_used_for_suggestions()
98+
&& vis_span.can_be_used_for_suggestions()
99+
{
100+
Some(InteriorMutableConstItemMutationsSuggestionStatic::Spanful {
101+
const_: const_item.vis_span.between(ident.span),
102+
before: if !vis_span.is_empty() { " " } else { "" },
103+
})
104+
} else {
105+
Some(InteriorMutableConstItemMutationsSuggestionStatic::Spanless)
106+
}
107+
} else {
108+
None
109+
};
110+
111+
cx.emit_span_lint(
112+
INTERIOR_MUTABLE_CONST_ITEM_MUTATIONS,
113+
expr.span,
114+
InteriorMutableConstItemMutationsDiag {
115+
method_name,
116+
const_name,
117+
const_ty,
118+
receiver_span: receiver.span,
119+
sugg_static,
120+
},
121+
);
122+
}
123+
}
124+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod for_loops_over_fallibles;
5050
mod foreign_modules;
5151
mod if_let_rescope;
5252
mod impl_trait_overcaptures;
53+
mod interior_mutable_consts;
5354
mod internal;
5455
mod invalid_from_utf8;
5556
mod late;
@@ -94,6 +95,7 @@ use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
9495
use for_loops_over_fallibles::*;
9596
use if_let_rescope::IfLetRescope;
9697
use impl_trait_overcaptures::ImplTraitOvercaptures;
98+
use interior_mutable_consts::*;
9799
use internal::*;
98100
use invalid_from_utf8::*;
99101
use let_underscore::*;
@@ -240,6 +242,7 @@ late_lint_methods!(
240242
AsyncClosureUsage: AsyncClosureUsage,
241243
AsyncFnInTrait: AsyncFnInTrait,
242244
NonLocalDefinitions: NonLocalDefinitions::default(),
245+
InteriorMutableConsts: InteriorMutableConsts,
243246
ImplTraitOvercaptures: ImplTraitOvercaptures,
244247
IfLetRescope: IfLetRescope::default(),
245248
StaticMutRefs: StaticMutRefs,

compiler/rustc_lint/src/lints.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,39 @@ pub(crate) enum InvalidFromUtf8Diag {
784784
},
785785
}
786786

787+
// interior_mutable_consts.rs
788+
#[derive(LintDiagnostic)]
789+
#[diag(lint_interior_mutable_const_item_mutations)]
790+
#[note(lint_temporary)]
791+
#[note(lint_never_original)]
792+
#[help]
793+
pub(crate) struct InteriorMutableConstItemMutationsDiag<'tcx> {
794+
pub method_name: Ident,
795+
pub const_name: Ident,
796+
pub const_ty: Ty<'tcx>,
797+
#[label]
798+
pub receiver_span: Span,
799+
#[subdiagnostic]
800+
pub sugg_static: Option<InteriorMutableConstItemMutationsSuggestionStatic>,
801+
}
802+
803+
#[derive(Subdiagnostic)]
804+
pub(crate) enum InteriorMutableConstItemMutationsSuggestionStatic {
805+
#[suggestion(
806+
lint_suggestion_static,
807+
code = "{before}static ",
808+
style = "verbose",
809+
applicability = "maybe-incorrect"
810+
)]
811+
Spanful {
812+
#[primary_span]
813+
const_: Span,
814+
before: &'static str,
815+
},
816+
#[help(lint_suggestion_static)]
817+
Spanless,
818+
}
819+
787820
// reference_casting.rs
788821
#[derive(LintDiagnostic)]
789822
pub(crate) enum InvalidReferenceCastingDiag<'tcx> {
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//@ check-pass
2+
//@ run-rustfix
3+
4+
#![allow(deprecated)]
5+
#![allow(dead_code)]
6+
#![feature(atomic_try_update)]
7+
8+
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
9+
10+
fn atomic_bool() {
11+
static A: AtomicBool = AtomicBool::new(false);
12+
13+
let _a = A.load(Ordering::SeqCst);
14+
//~^ WARN mutation of an interior mutable `const` item with call to `load`
15+
16+
let _a = A.store(true, Ordering::SeqCst);
17+
//~^ WARN mutation of an interior mutable `const` item with call to `store`
18+
19+
let _a = A.swap(true, Ordering::SeqCst);
20+
//~^ WARN mutation of an interior mutable `const` item with call to `swap`
21+
22+
let _a = A.compare_and_swap(false, true, Ordering::SeqCst);
23+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_and_swap`
24+
25+
let _a = A.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed);
26+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange`
27+
28+
let _a = A.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed);
29+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange_weak`
30+
31+
let _a = A.fetch_and(true, Ordering::SeqCst);
32+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_and`
33+
34+
let _a = A.fetch_nand(true, Ordering::SeqCst);
35+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_nand`
36+
37+
let _a = A.fetch_or(true, Ordering::SeqCst);
38+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_or`
39+
40+
let _a = A.fetch_xor(true, Ordering::SeqCst);
41+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_xor`
42+
43+
let _a = A.fetch_not(Ordering::SeqCst);
44+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_not`
45+
46+
let _a = A.fetch_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(true));
47+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_update`
48+
49+
let _a = A.try_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(false));
50+
//~^ WARN mutation of an interior mutable `const` item with call to `try_update`
51+
52+
let _a = A.update(Ordering::SeqCst, Ordering::Relaxed, |_| true);
53+
//~^ WARN mutation of an interior mutable `const` item with call to `update`
54+
}
55+
56+
fn atomic_ptr() {
57+
static A: AtomicPtr<i32> = AtomicPtr::new(std::ptr::null_mut());
58+
59+
let _a = A.load(Ordering::SeqCst);
60+
//~^ WARN mutation of an interior mutable `const` item with call to `load`
61+
62+
let _a = A.store(std::ptr::null_mut(), Ordering::SeqCst);
63+
//~^ WARN mutation of an interior mutable `const` item with call to `store`
64+
65+
let _a = A.swap(std::ptr::null_mut(), Ordering::SeqCst);
66+
//~^ WARN mutation of an interior mutable `const` item with call to `swap`
67+
68+
let _a = A.compare_and_swap(std::ptr::null_mut(), std::ptr::null_mut(), Ordering::SeqCst);
69+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_and_swap`
70+
71+
let _a = A.compare_exchange(
72+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange`
73+
std::ptr::null_mut(),
74+
std::ptr::null_mut(),
75+
Ordering::SeqCst,
76+
Ordering::Relaxed,
77+
);
78+
79+
let _a = A.compare_exchange_weak(
80+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange_weak`
81+
std::ptr::null_mut(),
82+
std::ptr::null_mut(),
83+
Ordering::SeqCst,
84+
Ordering::Relaxed,
85+
);
86+
87+
let _a = A.fetch_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(std::ptr::null_mut()));
88+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_update`
89+
90+
let _a = A.try_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(std::ptr::null_mut()));
91+
//~^ WARN mutation of an interior mutable `const` item with call to `try_update`
92+
93+
let _a = A.update(Ordering::SeqCst, Ordering::Relaxed, |_| std::ptr::null_mut());
94+
//~^ WARN mutation of an interior mutable `const` item with call to `update`
95+
96+
let _a = A.fetch_ptr_add(1, Ordering::SeqCst);
97+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_ptr_add`
98+
99+
let _a = A.fetch_ptr_sub(1, Ordering::SeqCst);
100+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_ptr_sub`
101+
102+
let _a = A.fetch_byte_add(1, Ordering::SeqCst);
103+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_byte_add`
104+
105+
let _a = A.fetch_byte_sub(1, Ordering::SeqCst);
106+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_byte_sub`
107+
108+
let _a = A.fetch_and(1, Ordering::SeqCst);
109+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_and`
110+
111+
let _a = A.fetch_or(1, Ordering::SeqCst);
112+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_or`
113+
114+
let _a = A.fetch_xor(1, Ordering::SeqCst);
115+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_xor`
116+
}
117+
118+
fn atomic_u32() {
119+
static A: AtomicU32 = AtomicU32::new(0);
120+
121+
let _a = A.load(Ordering::SeqCst);
122+
//~^ WARN mutation of an interior mutable `const` item with call to `load`
123+
124+
let _a = A.store(1, Ordering::SeqCst);
125+
//~^ WARN mutation of an interior mutable `const` item with call to `store`
126+
127+
let _a = A.swap(2, Ordering::SeqCst);
128+
//~^ WARN mutation of an interior mutable `const` item with call to `swap`
129+
130+
let _a = A.compare_and_swap(2, 3, Ordering::SeqCst);
131+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_and_swap`
132+
133+
let _a = A.compare_exchange(3, 4, Ordering::SeqCst, Ordering::Relaxed);
134+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange`
135+
136+
let _a = A.compare_exchange_weak(4, 5, Ordering::SeqCst, Ordering::Relaxed);
137+
//~^ WARN mutation of an interior mutable `const` item with call to `compare_exchange_weak`
138+
139+
let _a = A.fetch_add(1, Ordering::SeqCst);
140+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_add`
141+
142+
let _a = A.fetch_sub(1, Ordering::SeqCst);
143+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_sub`
144+
145+
let _a = A.fetch_add(2, Ordering::SeqCst);
146+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_add`
147+
148+
let _a = A.fetch_nand(1, Ordering::SeqCst);
149+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_nand`
150+
151+
let _a = A.fetch_or(1, Ordering::SeqCst);
152+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_or`
153+
154+
let _a = A.fetch_xor(1, Ordering::SeqCst);
155+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_xor`
156+
157+
let _a = A.fetch_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(10));
158+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_update`
159+
160+
let _a = A.try_update(Ordering::SeqCst, Ordering::Relaxed, |_| Some(11));
161+
//~^ WARN mutation of an interior mutable `const` item with call to `try_update`
162+
163+
let _a = A.update(Ordering::SeqCst, Ordering::Relaxed, |_| 12);
164+
//~^ WARN mutation of an interior mutable `const` item with call to `update`
165+
166+
let _a = A.fetch_max(20, Ordering::SeqCst);
167+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_max`
168+
169+
let _a = A.fetch_min(5, Ordering::SeqCst);
170+
//~^ WARN mutation of an interior mutable `const` item with call to `fetch_min`
171+
}
172+
173+
fn main() {}

0 commit comments

Comments
 (0)