Skip to content

Commit d7b1da2

Browse files
committed
changelog: new lint: [rwlock_atomic]
changelog: new lint: [`rwlock_integer`]
1 parent c48592e commit d7b1da2

File tree

8 files changed

+776
-403
lines changed

8 files changed

+776
-403
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
534534
crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO,
535535
crate::mutex_atomic::MUTEX_ATOMIC_INFO,
536536
crate::mutex_atomic::MUTEX_INTEGER_INFO,
537+
crate::rwlock_atomic::RWLOCK_ATOMIC_INFO,
538+
crate::rwlock_atomic::RWLOCK_INTEGER_INFO,
537539
crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO,
538540
crate::needless_bool::NEEDLESS_BOOL_INFO,
539541
crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO,

clippy_lints/src/lib.rs

Lines changed: 365 additions & 403 deletions
Large diffs are not rendered by default.

clippy_lints/src/rwlock_atomic.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::res::MaybeDef;
3+
use clippy_utils::source::{IntoSpan, SpanRangeExt};
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::ty_from_hir_ty;
6+
use rustc_errors::{Applicability, Diag};
7+
use rustc_hir::{self as hir, Expr, ExprKind, Item, ItemKind, LetStmt, QPath};
8+
use rustc_lint::{LateContext, LateLintPass, LintContext};
9+
use rustc_middle::mir::Mutability;
10+
use rustc_middle::ty::{self, IntTy, Ty, UintTy};
11+
use rustc_session::declare_lint_pass;
12+
use rustc_span::sym;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Checks for usage of `RwLock<X>` where an atomic will do.
17+
///
18+
/// ### Why restrict this?
19+
/// Using a RwLock just to make access to a plain bool or
20+
/// reference sequential is shooting flies with cannons.
21+
/// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and
22+
/// faster.
23+
///
24+
/// On the other hand, `RwLock`s are, in general, easier to
25+
/// verify correctness. An atomic does not behave the same as
26+
/// an equivalent RwLock.
27+
///
28+
/// ### Example
29+
/// ```no_run
30+
/// # let y = true;
31+
/// # use std::sync::RwLock;
32+
/// let x = RwLock::new(&y);
33+
/// ```
34+
///
35+
/// Use instead:
36+
/// ```no_run
37+
/// # let y = true;
38+
/// # use std::sync::atomic::AtomicBool;
39+
/// let x = AtomicBool::new(y);
40+
/// ```
41+
#[clippy::version = "1.90.0"]
42+
pub RWLOCK_ATOMIC,
43+
restriction,
44+
"using a RwLock where an atomic value could be used instead."
45+
}
46+
47+
declare_clippy_lint! {
48+
/// ### What it does
49+
/// Checks for usage of `RwLock<X>` where `X` is an integral
50+
/// type.
51+
///
52+
/// ### Why restrict this?
53+
/// Using a RwLock just to make access to a plain integer
54+
/// sequential is
55+
/// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster.
56+
///
57+
/// On the other hand, `RwLock`s are, in general, easier to
58+
/// verify correctness. An atomic does not behave the same as
59+
/// an equivalent RwLock.
60+
///
61+
/// ### Example
62+
/// ```no_run
63+
/// # use std::sync::RwLock;
64+
/// let x = RwLock::new(0usize);
65+
/// ```
66+
///
67+
/// Use instead:
68+
/// ```no_run
69+
/// # use std::sync::atomic::AtomicUsize;
70+
/// let x = AtomicUsize::new(0usize);
71+
/// ```
72+
#[clippy::version = "1.90.0"]
73+
pub RWLOCK_INTEGER,
74+
restriction,
75+
"using a RwLock for an integer type"
76+
}
77+
78+
declare_lint_pass!(RwLock => [RWLOCK_ATOMIC, RWLOCK_INTEGER]);
79+
80+
// NOTE: we don't use `check_expr` because that would make us lint every _use_ of such RwLocks, not
81+
// just their definitions
82+
impl<'tcx> LateLintPass<'tcx> for RwLock {
83+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
84+
if !item.span.from_expansion()
85+
&& let ItemKind::Static(_, _, ty, body_id) = item.kind
86+
{
87+
let body = cx.tcx.hir_body(body_id);
88+
let mid_ty = ty_from_hir_ty(cx, ty);
89+
check_expr(cx, body.value.peel_blocks(), &TypeAscriptionKind::Required(ty), mid_ty);
90+
}
91+
}
92+
fn check_local(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx LetStmt<'_>) {
93+
if !stmt.span.from_expansion()
94+
&& let Some(init) = stmt.init
95+
{
96+
let mid_ty = cx.typeck_results().expr_ty(init);
97+
check_expr(cx, init.peel_blocks(), &TypeAscriptionKind::Optional(stmt.ty), mid_ty);
98+
}
99+
}
100+
}
101+
102+
/// Whether the type ascription `: RwLock<X>` (which we'll suggest replacing with `AtomicX`) is
103+
/// required
104+
enum TypeAscriptionKind<'tcx> {
105+
/// Yes; for us, this is the case for statics
106+
Required(&'tcx hir::Ty<'tcx>),
107+
/// No; the ascription might've been necessary in an expression like:
108+
/// ```ignore
109+
/// let rwlock: RwLock<u64> = RwLock::new(0);
110+
/// ```
111+
/// to specify the type of `0`, but since `AtomicX` already refers to a concrete type, we won't
112+
/// need this ascription anymore.
113+
Optional(Option<&'tcx hir::Ty<'tcx>>),
114+
}
115+
116+
fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty_ascription: &TypeAscriptionKind<'tcx>, ty: Ty<'tcx>) {
117+
if let ty::Adt(_, subst) = ty.kind()
118+
&& ty.is_diag_item(cx, sym::RwLock)
119+
&& let rwlock_param = subst.type_at(0)
120+
&& let Some(atomic_name) = get_atomic_name(rwlock_param)
121+
{
122+
let msg = "using a `RwLock` where an atomic would do";
123+
let diag = |diag: &mut Diag<'_, _>| {
124+
// if `expr = RwLock::new(arg)`, we can try emitting a suggestion
125+
if let ExprKind::Call(qpath, [arg]) = expr.kind
126+
&& let ExprKind::Path(QPath::TypeRelative(_rwlock, new)) = qpath.kind
127+
&& new.ident.name == sym::new
128+
{
129+
let mut applicability = Applicability::MaybeIncorrect;
130+
let arg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability);
131+
let mut suggs = vec![(expr.span, format!("std::sync::atomic::{atomic_name}::new({arg})"))];
132+
match ty_ascription {
133+
TypeAscriptionKind::Required(ty_ascription) => {
134+
suggs.push((ty_ascription.span, format!("std::sync::atomic::{atomic_name}")));
135+
},
136+
TypeAscriptionKind::Optional(Some(ty_ascription)) => {
137+
// See https://github.com/rust-lang/rust-clippy/pull/15386 for why this is
138+
// required
139+
let colon_ascription = (cx.sess().source_map())
140+
.span_extend_to_prev_char_before(ty_ascription.span, ':', true)
141+
.with_leading_whitespace(cx)
142+
.into_span();
143+
suggs.push((colon_ascription, String::new()));
144+
},
145+
TypeAscriptionKind::Optional(None) => {}, // nothing to remove/replace
146+
}
147+
diag.multipart_suggestion("try", suggs, applicability);
148+
} else {
149+
diag.help(format!("consider using an `{atomic_name}` instead"));
150+
}
151+
diag.help("if you just want the locking behavior and not the internal type, consider using `RwLock<()>`");
152+
};
153+
match *rwlock_param.kind() {
154+
ty::Uint(t) if t != UintTy::Usize => span_lint_and_then(cx, RWLOCK_INTEGER, expr.span, msg, diag),
155+
ty::Int(t) if t != IntTy::Isize => span_lint_and_then(cx, RWLOCK_INTEGER, expr.span, msg, diag),
156+
_ => span_lint_and_then(cx, RWLOCK_ATOMIC, expr.span, msg, diag),
157+
}
158+
}
159+
}
160+
161+
fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
162+
match ty.kind() {
163+
ty::Bool => Some("AtomicBool"),
164+
ty::Uint(uint_ty) => {
165+
match uint_ty {
166+
UintTy::U8 => Some("AtomicU8"),
167+
UintTy::U16 => Some("AtomicU16"),
168+
UintTy::U32 => Some("AtomicU32"),
169+
UintTy::U64 => Some("AtomicU64"),
170+
UintTy::Usize => Some("AtomicUsize"),
171+
// `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069
172+
UintTy::U128 => None,
173+
}
174+
},
175+
ty::Int(int_ty) => {
176+
match int_ty {
177+
IntTy::I8 => Some("AtomicI8"),
178+
IntTy::I16 => Some("AtomicI16"),
179+
IntTy::I32 => Some("AtomicI32"),
180+
IntTy::I64 => Some("AtomicI64"),
181+
IntTy::Isize => Some("AtomicIsize"),
182+
// `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069
183+
IntTy::I128 => None,
184+
}
185+
},
186+
// `AtomicPtr` only accepts `*mut T`
187+
ty::RawPtr(_, Mutability::Mut) => Some("AtomicPtr"),
188+
_ => None,
189+
}
190+
}

tests/ui/rwlock_atomic.fixed

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#![warn(clippy::rwlock_integer)]
2+
#![warn(clippy::rwlock_atomic)]
3+
#![allow(clippy::borrow_as_ptr)]
4+
5+
use std::sync::RwLock;
6+
7+
fn main() {
8+
let _ = std::sync::atomic::AtomicBool::new(true);
9+
//~^ rwlock_atomic
10+
11+
let _ = std::sync::atomic::AtomicUsize::new(5usize);
12+
//~^ rwlock_atomic
13+
14+
let _ = std::sync::atomic::AtomicIsize::new(9isize);
15+
//~^ rwlock_atomic
16+
17+
let mut x = 4u32;
18+
// `AtomicPtr` only accepts `*mut T`, so this should not lint
19+
let _ = RwLock::new(&x as *const u32);
20+
21+
let _ = std::sync::atomic::AtomicPtr::new(&mut x as *mut u32);
22+
//~^ rwlock_atomic
23+
24+
let _ = std::sync::atomic::AtomicU32::new(0u32);
25+
//~^ rwlock_integer
26+
27+
let _ = std::sync::atomic::AtomicI32::new(0i32);
28+
//~^ rwlock_integer
29+
30+
let _ = RwLock::new(0f32); // there are no float atomics, so this should not lint
31+
let _ = std::sync::atomic::AtomicU8::new(0u8);
32+
//~^ rwlock_integer
33+
34+
let _ = std::sync::atomic::AtomicI16::new(0i16);
35+
//~^ rwlock_integer
36+
37+
let _x = std::sync::atomic::AtomicI8::new(0);
38+
//~^ rwlock_integer
39+
40+
const X: i64 = 0;
41+
let _ = std::sync::atomic::AtomicI64::new(X);
42+
//~^ rwlock_integer
43+
44+
// there are no 128 atomics, so these two should not lint
45+
{
46+
let _ = RwLock::new(0u128);
47+
let _x: RwLock<i128> = RwLock::new(0);
48+
}
49+
}

tests/ui/rwlock_atomic.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#![warn(clippy::rwlock_integer)]
2+
#![warn(clippy::rwlock_atomic)]
3+
#![allow(clippy::borrow_as_ptr)]
4+
5+
use std::sync::RwLock;
6+
7+
fn main() {
8+
let _ = RwLock::new(true);
9+
//~^ rwlock_atomic
10+
11+
let _ = RwLock::new(5usize);
12+
//~^ rwlock_atomic
13+
14+
let _ = RwLock::new(9isize);
15+
//~^ rwlock_atomic
16+
17+
let mut x = 4u32;
18+
// `AtomicPtr` only accepts `*mut T`, so this should not lint
19+
let _ = RwLock::new(&x as *const u32);
20+
21+
let _ = RwLock::new(&mut x as *mut u32);
22+
//~^ rwlock_atomic
23+
24+
let _ = RwLock::new(0u32);
25+
//~^ rwlock_integer
26+
27+
let _ = RwLock::new(0i32);
28+
//~^ rwlock_integer
29+
30+
let _ = RwLock::new(0f32); // there are no float atomics, so this should not lint
31+
let _ = RwLock::new(0u8);
32+
//~^ rwlock_integer
33+
34+
let _ = RwLock::new(0i16);
35+
//~^ rwlock_integer
36+
37+
let _x: RwLock<i8> = RwLock::new(0);
38+
//~^ rwlock_integer
39+
40+
const X: i64 = 0;
41+
let _ = RwLock::new(X);
42+
//~^ rwlock_integer
43+
44+
// there are no 128 atomics, so these two should not lint
45+
{
46+
let _ = RwLock::new(0u128);
47+
let _x: RwLock<i128> = RwLock::new(0);
48+
}
49+
}

tests/ui/rwlock_atomic.stderr

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
error: using a `RwLock` where an atomic would do
2+
--> tests/ui/rwlock_atomic.rs:8:13
3+
|
4+
LL | let _ = RwLock::new(true);
5+
| ^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicBool::new(true)`
6+
|
7+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
8+
= note: `-D clippy::rwlock-atomic` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::rwlock_atomic)]`
10+
11+
error: using a `RwLock` where an atomic would do
12+
--> tests/ui/rwlock_atomic.rs:11:13
13+
|
14+
LL | let _ = RwLock::new(5usize);
15+
| ^^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicUsize::new(5usize)`
16+
|
17+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
18+
19+
error: using a `RwLock` where an atomic would do
20+
--> tests/ui/rwlock_atomic.rs:14:13
21+
|
22+
LL | let _ = RwLock::new(9isize);
23+
| ^^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicIsize::new(9isize)`
24+
|
25+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
26+
27+
error: using a `RwLock` where an atomic would do
28+
--> tests/ui/rwlock_atomic.rs:21:13
29+
|
30+
LL | let _ = RwLock::new(&mut x as *mut u32);
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicPtr::new(&mut x as *mut u32)`
32+
|
33+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
34+
35+
error: using a `RwLock` where an atomic would do
36+
--> tests/ui/rwlock_atomic.rs:24:13
37+
|
38+
LL | let _ = RwLock::new(0u32);
39+
| ^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicU32::new(0u32)`
40+
|
41+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
42+
= note: `-D clippy::rwlock-integer` implied by `-D warnings`
43+
= help: to override `-D warnings` add `#[allow(clippy::rwlock_integer)]`
44+
45+
error: using a `RwLock` where an atomic would do
46+
--> tests/ui/rwlock_atomic.rs:27:13
47+
|
48+
LL | let _ = RwLock::new(0i32);
49+
| ^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI32::new(0i32)`
50+
|
51+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
52+
53+
error: using a `RwLock` where an atomic would do
54+
--> tests/ui/rwlock_atomic.rs:31:13
55+
|
56+
LL | let _ = RwLock::new(0u8);
57+
| ^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicU8::new(0u8)`
58+
|
59+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
60+
61+
error: using a `RwLock` where an atomic would do
62+
--> tests/ui/rwlock_atomic.rs:34:13
63+
|
64+
LL | let _ = RwLock::new(0i16);
65+
| ^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI16::new(0i16)`
66+
|
67+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
68+
69+
error: using a `RwLock` where an atomic would do
70+
--> tests/ui/rwlock_atomic.rs:37:26
71+
|
72+
LL | let _x: RwLock<i8> = RwLock::new(0);
73+
| ^^^^^^^^^^^^^^
74+
|
75+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
76+
help: try
77+
|
78+
LL - let _x: RwLock<i8> = RwLock::new(0);
79+
LL + let _x = std::sync::atomic::AtomicI8::new(0);
80+
|
81+
82+
error: using a `RwLock` where an atomic would do
83+
--> tests/ui/rwlock_atomic.rs:41:13
84+
|
85+
LL | let _ = RwLock::new(X);
86+
| ^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicI64::new(X)`
87+
|
88+
= help: if you just want the locking behavior and not the internal type, consider using `RwLock<()>`
89+
90+
error: aborting due to 10 previous errors
91+

0 commit comments

Comments
 (0)