Skip to content

Commit d3c5b48

Browse files
committed
Auto merge of #11210 - y21:readonly_write_lock, r=giraffate
new lint: [`readonly_write_lock`] Closes #8555 A new lint that catches `RwLock::write` calls to acquire a write lock only to read from it and not actually do any writes (mutations). changelog: new lint: [`readonly_write_lock`]
2 parents 295bdc0 + 5e88003 commit d3c5b48

File tree

6 files changed

+148
-0
lines changed

6 files changed

+148
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5181,6 +5181,7 @@ Released 2018-09-13
51815181
[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
51825182
[`read_line_without_trim`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_line_without_trim
51835183
[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec
5184+
[`readonly_write_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#readonly_write_lock
51845185
[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
51855186
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
51865187
[`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
398398
crate::methods::OR_THEN_UNWRAP_INFO,
399399
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
400400
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
401+
crate::methods::READONLY_WRITE_LOCK_INFO,
401402
crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
402403
crate::methods::REPEAT_ONCE_INFO,
403404
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,

clippy_lints/src/methods/mod.rs

+36
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ mod or_then_unwrap;
7676
mod path_buf_push_overwrite;
7777
mod range_zip_with_len;
7878
mod read_line_without_trim;
79+
mod readonly_write_lock;
7980
mod repeat_once;
8081
mod search_is_some;
8182
mod seek_from_current;
@@ -3507,6 +3508,37 @@ declare_clippy_lint! {
35073508
"checks for usage of `bool::then` in `Iterator::filter_map`"
35083509
}
35093510

3511+
declare_clippy_lint! {
3512+
/// ### What it does
3513+
/// Looks for calls to `RwLock::write` where the lock is only used for reading.
3514+
///
3515+
/// ### Why is this bad?
3516+
/// The write portion of `RwLock` is exclusive, meaning that no other thread
3517+
/// can access the lock while this writer is active.
3518+
///
3519+
/// ### Example
3520+
/// ```rust
3521+
/// use std::sync::RwLock;
3522+
/// fn assert_is_zero(lock: &RwLock<i32>) {
3523+
/// let num = lock.write().unwrap();
3524+
/// assert_eq!(*num, 0);
3525+
/// }
3526+
/// ```
3527+
///
3528+
/// Use instead:
3529+
/// ```rust
3530+
/// use std::sync::RwLock;
3531+
/// fn assert_is_zero(lock: &RwLock<i32>) {
3532+
/// let num = lock.read().unwrap();
3533+
/// assert_eq!(*num, 0);
3534+
/// }
3535+
/// ```
3536+
#[clippy::version = "1.73.0"]
3537+
pub READONLY_WRITE_LOCK,
3538+
nursery,
3539+
"acquiring a write lock when a read lock would work"
3540+
}
3541+
35103542
pub struct Methods {
35113543
avoid_breaking_exported_api: bool,
35123544
msrv: Msrv,
@@ -3645,6 +3677,7 @@ impl_lint_pass!(Methods => [
36453677
STRING_LIT_CHARS_ANY,
36463678
ITER_SKIP_ZERO,
36473679
FILTER_MAP_BOOL_THEN,
3680+
READONLY_WRITE_LOCK
36483681
]);
36493682

36503683
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4188,6 +4221,9 @@ impl Methods {
41884221
range_zip_with_len::check(cx, expr, iter_recv, arg);
41894222
}
41904223
},
4224+
("write", []) => {
4225+
readonly_write_lock::check(cx, expr, recv);
4226+
}
41914227
_ => {},
41924228
}
41934229
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use super::READONLY_WRITE_LOCK;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::mir::{enclosing_mir, visit_local_usage};
4+
use clippy_utils::source::snippet;
5+
use clippy_utils::ty::is_type_diagnostic_item;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Expr, ExprKind, Node};
8+
use rustc_lint::LateContext;
9+
use rustc_middle::mir::{Location, START_BLOCK};
10+
use rustc_span::sym;
11+
12+
fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
13+
if let ExprKind::MethodCall(path, receiver, ..) = expr.kind
14+
&& path.ident.name == sym::unwrap
15+
{
16+
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result)
17+
} else {
18+
false
19+
}
20+
}
21+
22+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, receiver: &Expr<'_>) {
23+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::RwLock)
24+
&& let Node::Expr(unwrap_call_expr) = cx.tcx.hir().get_parent(expr.hir_id)
25+
&& is_unwrap_call(cx, unwrap_call_expr)
26+
&& let parent = cx.tcx.hir().get_parent(unwrap_call_expr.hir_id)
27+
&& let Node::Local(local) = parent
28+
&& let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id)
29+
&& let Some((local, _)) = mir.local_decls.iter_enumerated().find(|(_, decl)| {
30+
local.span.contains(decl.source_info.span)
31+
})
32+
&& let Some(usages) = visit_local_usage(&[local], mir, Location {
33+
block: START_BLOCK,
34+
statement_index: 0,
35+
})
36+
&& let [usage] = usages.as_slice()
37+
{
38+
let writer_never_mutated = usage.local_consume_or_mutate_locs.is_empty();
39+
40+
if writer_never_mutated {
41+
span_lint_and_sugg(
42+
cx,
43+
READONLY_WRITE_LOCK,
44+
expr.span,
45+
"this write lock is used only for reading",
46+
"consider using a read lock instead",
47+
format!("{}.read()", snippet(cx, receiver.span, "<receiver>")),
48+
Applicability::MaybeIncorrect // write lock might be intentional for enforcing exclusiveness
49+
);
50+
}
51+
}
52+
}

tests/ui/readonly_write_lock.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![warn(clippy::readonly_write_lock)]
2+
3+
use std::sync::RwLock;
4+
5+
fn mutate_i32(x: &mut i32) {
6+
*x += 1;
7+
}
8+
9+
fn accept_i32(_: i32) {}
10+
11+
fn main() {
12+
let lock = RwLock::new(42);
13+
let lock2 = RwLock::new(1234);
14+
15+
{
16+
let writer = lock.write().unwrap();
17+
dbg!(&writer);
18+
}
19+
20+
{
21+
let writer = lock.write().unwrap();
22+
accept_i32(*writer);
23+
}
24+
25+
{
26+
let mut writer = lock.write().unwrap();
27+
mutate_i32(&mut writer);
28+
dbg!(&writer);
29+
}
30+
31+
{
32+
let mut writer = lock.write().unwrap();
33+
*writer += 1;
34+
}
35+
36+
{
37+
let mut writer1 = lock.write().unwrap();
38+
let mut writer2 = lock2.write().unwrap();
39+
*writer2 += 1;
40+
*writer1 = *writer2;
41+
}
42+
}

tests/ui/readonly_write_lock.stderr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: this write lock is used only for reading
2+
--> $DIR/readonly_write_lock.rs:16:22
3+
|
4+
LL | let writer = lock.write().unwrap();
5+
| ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()`
6+
|
7+
= note: `-D clippy::readonly-write-lock` implied by `-D warnings`
8+
9+
error: this write lock is used only for reading
10+
--> $DIR/readonly_write_lock.rs:21:22
11+
|
12+
LL | let writer = lock.write().unwrap();
13+
| ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()`
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)