Skip to content

Initial implementation of mut_refcell_borrow #9423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3908,6 +3908,7 @@ Released 2018-09-13
[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock
[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
[`mut_refcell_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_refcell_borrow
[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::MAP_FLATTEN),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::MUT_MUTEX_LOCK),
LintId::of(methods::MUT_REFCELL_BORROW),
LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
LintId::of(methods::NEEDLESS_OPTION_TAKE),
LintId::of(methods::NEEDLESS_SPLITN),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ store.register_lints(&[
methods::MAP_IDENTITY,
methods::MAP_UNWRAP_OR,
methods::MUT_MUTEX_LOCK,
methods::MUT_REFCELL_BORROW,
methods::NAIVE_BYTECOUNT,
methods::NEEDLESS_OPTION_AS_DEREF,
methods::NEEDLESS_OPTION_TAKE,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(methods::MAP_CLONE),
LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
LintId::of(methods::MUT_MUTEX_LOCK),
LintId::of(methods::MUT_REFCELL_BORROW),
LintId::of(methods::NEW_RET_NO_SELF),
LintId::of(methods::OBFUSCATED_IF_ELSE),
LintId::of(methods::OK_EXPECT),
Expand Down
56 changes: 55 additions & 1 deletion clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod mut_mutex_lock;
mod mut_refcell_borrow;
mod needless_option_as_deref;
mod needless_option_take;
mod no_effect_replace;
Expand Down Expand Up @@ -2765,6 +2766,53 @@ declare_clippy_lint! {
"`&mut Mutex::lock` does unnecessary locking"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for `&mut std::cell::RefCell` method calls which perform
/// runtime-checks that the compiler can statically guarantee
///
/// ### Why is this bad?
/// Methods on `RefCell` explicitly or implicitly perform a runtime-check
/// to guarantee the borrowing rules. If called on a `&mut RefCell` we
/// can statically guarantee that the borrowing rules are upheld.
///
/// ### Example
/// ```
/// use std::cell::RefCell;
///
/// fn foo(x: &mut RefCell<i32>) -> i32 {
/// // This implicitly panics if the value is already borrowed. But it
/// // can't be borrowed because we have an exclusive reference to it
/// x.replace(42)
/// }
///
/// fn bar(x: &mut RefCell<i32>) {
/// // This check can never fail
/// if let Ok(mut value) = x.try_borrow_mut() {
/// *value = 42;
/// }
/// }
/// ```
/// Use instead:
/// ```
/// use std::cell::RefCell;
///
/// fn foo(x: &mut RefCell<i32>) -> i32 {
/// // No need for an implicit check
/// std::mem::replace(x.get_mut(), 42)
/// }
///
/// fn bar(x: &mut RefCell<i32>) {
/// // No need for an error path
/// *x.get_mut() = 42;
/// }
/// ```
#[clippy::version = "1.64.0"]
pub MUT_REFCELL_BORROW,
style,
"method call to `&mut RefCell` performs unnecessary runtime-check"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for duplicate open options as well as combinations
Expand Down Expand Up @@ -3149,6 +3197,7 @@ impl_lint_pass!(Methods => [
MAP_CLONE,
MAP_ERR_IGNORE,
MUT_MUTEX_LOCK,
MUT_REFCELL_BORROW,
NONSENSICAL_OPEN_OPTIONS,
PATH_BUF_PUSH_OVERWRITE,
RANGE_ZIP_WITH_LEN,
Expand Down Expand Up @@ -3492,6 +3541,8 @@ impl Methods {
("lock", []) => {
mut_mutex_lock::check(cx, expr, recv, span);
},
(name @ ("replace" | "replace_with"), [arg]) => mut_refcell_borrow::check(cx, expr, recv, span, name, Some(arg)),
(name @ ("borrow" | "try_borrow" | "borrow_mut" | "try_borrow_mut"), []) => mut_refcell_borrow::check(cx, expr, recv, span, name, None),
(name @ ("map" | "map_err"), [m_arg]) => {
if name == "map" {
map_clone::check(cx, expr, recv, m_arg, self.msrv);
Expand Down Expand Up @@ -3597,7 +3648,10 @@ impl Methods {
}
}
},
("take", []) => needless_option_take::check(cx, expr, recv),
("take", []) => {
needless_option_take::check(cx, expr, recv);
mut_refcell_borrow::check(cx, expr, recv, span, "take", None);
},
("then", [arg]) => {
if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
return;
Expand Down
123 changes: 123 additions & 0 deletions clippy_lints/src/methods/mut_refcell_borrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::{expr_custom_deref_adjustment, match_def_path, paths};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;

use super::MUT_REFCELL_BORROW;

//TODO calls to RefCell's `Clone`-impl could be replaced by `RefCell::new(foo.get_mut().clone())`
//to circumvent the runtime-check

fn emit_replace(cx: &LateContext<'_>, name_span: Span) {
span_lint_and_help(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::replace()` unnecessarily performs a runtime-check that can never fail",
None,
"use `.get_mut()` to get a mutable reference to the value, and replace the value using `std::mem::replace()` or direct assignment",
);
}

fn emit_replace_with(cx: &LateContext<'_>, name_span: Span) {
span_lint_and_help(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::replace_with()` unnecessarily performs a runtime-check that can never fail",
None,
"use `.get_mut()` to get a mutable reference to the value, and replace the value using `std::mem::replace()` or direct assignment",
);
}

fn emit_borrow(cx: &LateContext<'_>, name_span: Span) {
// This is not MachineApplicable as `borrow` returns a `Ref` while `get_mut` returns a
// `&mut T`, and we don't check surrounding types
span_lint_and_sugg(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::borrow()` unnecessarily performs a runtime-check that can never fail",
"change this to",
"get_mut".to_owned(),
Applicability::MaybeIncorrect,
);
}

fn emit_try_borrow(cx: &LateContext<'_>, name_span: Span) {
span_lint_and_help(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::try_borrow()` unnecessarily performs a runtime-check that can never fail",
None,
"use `.get_mut()` instead of `.try_borrow()` to get a reference to the value; remove the error-handling",
);
}

fn emit_borrow_mut(cx: &LateContext<'_>, name_span: Span) {
// This is not MachineApplicable as `borrow_mut` returns a different type than `get_mut`, for
// which we don't check
span_lint_and_sugg(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::borrow_mut()` unnecessarily performs a runtime-check that can never fail",
"change this to",
"get_mut".to_owned(),
Applicability::MaybeIncorrect,
);
}

fn emit_try_borrow_mut(cx: &LateContext<'_>, name_span: Span) {
span_lint_and_help(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::try_borrow_mut()` unnecessarily performs a runtime-check that can never fail",
None,
"use `.get_mut()` instead of `.try_borrow_mut()` to get a mutable reference to the value; remove the error-handling",
);
}

fn emit_take(cx: &LateContext<'_>, name_span: Span) {
span_lint_and_help(
cx,
MUT_REFCELL_BORROW,
name_span,
"calling `&mut RefCell::take()` unnecessarily performs a runtime-check that can never fail",
None,
"use `.get_mut()` to get a mutable reference to the value, and `std::mem::take()` to get ownership via that reference",
);
}

pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
ex: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
name_span: Span,
name: &'tcx str,
arg: Option<&'tcx Expr<'tcx>>,
) {
if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut))
&& let ty::Ref(_, _, Mutability::Mut) = cx.typeck_results().expr_ty(recv).kind()
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(method_id)
&& match_def_path(cx, impl_id, &paths::REFCELL)
{
//TODO: Use `arg` to emit better suggestions
match (name, arg) {
("replace", Some(_arg)) => emit_replace(cx, name_span),
("replace_with", Some(_arg)) => emit_replace_with(cx, name_span),
("borrow", None) => emit_borrow(cx, name_span),
("try_borrow", None) => emit_try_borrow(cx, name_span),
("borrow_mut", None) => emit_borrow_mut(cx, name_span),
("try_borrow_mut", None) => emit_try_borrow_mut(cx, name_span),
("take", None) => emit_take(cx, name_span),
_ => unreachable!(),
};
}
}
1 change: 1 addition & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
pub const REFCELL: [&str; 3] = ["core", "cell", "RefCell"];
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
Expand Down
1 change: 1 addition & 0 deletions src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ docs! {
"mut_mut",
"mut_mutex_lock",
"mut_range_bound",
"mut_refcell_borrow",
"mutable_key_type",
"mutex_atomic",
"mutex_integer",
Expand Down
40 changes: 40 additions & 0 deletions src/docs/mut_refcell_borrow.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
### What it does
Checks for `&mut std::cell::RefCell` method calls which perform
runtime-checks that the compiler can statically guarantee

### Why is this bad?
Methods on `RefCell` explicitly or implicitly perform a runtime-check
to guarantee the borrowing rules. If called on a `&mut RefCell` we
can statically guarantee that the borrowing rules are upheld.

### Example
```
use std::cell::RefCell;

fn foo(x: &mut RefCell<i32>) -> i32 {
// This implicitly panics if the value is already borrowed. But it
// can't be borrowed because we have an exclusive reference to it
x.replace(42)
}

fn bar(x: &mut RefCell<i32>) {
// This check can never fail
if let Ok(mut value) = x.try_borrow_mut() {
*value = 42;
}
}
```
Use instead:
```
use std::cell::RefCell;

fn foo(x: &mut RefCell<i32>) -> i32 {
// No need for an implicit check
std::mem::replace(x.get_mut(), 42)
}

fn bar(x: &mut RefCell<i32>) {
// No need for an error path
*x.get_mut() = 42;
}
```
44 changes: 44 additions & 0 deletions tests/ui/mut_refcell_borrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![warn(clippy::mut_mutex_lock)]

use std::cell::RefCell;
use std::rc::Rc;

pub fn replace(x: &mut RefCell<i32>) {
x.replace(0);
}

pub fn replace_with(x: &mut RefCell<i32>) {
x.replace_with(|&mut old| old + 1);
}

pub fn borrow(x: &mut RefCell<i32>) {
let _: i32 = *x.borrow();
}

pub fn try_borrow(x: &mut RefCell<i32>) {
let _: i32 = *x.try_borrow().unwrap();
}

pub fn borrow_mut(x: &mut RefCell<i32>) {
*x.borrow_mut() += 1;
}

pub fn try_borrow_mut(x: &mut RefCell<i32>) {
*x.try_borrow_mut().unwrap() += 1;
}

pub fn take(x: &mut RefCell<i32>) {
let _: i32 = x.take();
}

// must not lint
pub fn deref_refcell(x: Rc<RefCell<i32>>) {
*x.borrow_mut() += 1;
}

// must not lint
pub fn mut_deref_refcell(x: &mut Rc<RefCell<i32>>) {
*x.borrow_mut() += 1;
}

fn main() {}
Loading