-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of
mut_refcell_borrow
Fixes #9044
- Loading branch information
Showing
11 changed files
with
323 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!(), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(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; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
Oops, something went wrong.