blob: 2fef8404f824d0aede5bbf26c3d8e54506a9eec5 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::ty_from_hir_ty;
use rustc_errors::{Applicability, Diag};
use rustc_hir::{self as hir, Expr, ExprKind, Item, ItemKind, LetStmt, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::{self, IntTy, Ty, UintTy};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `Mutex<X>` where an atomic will do.
///
/// ### Why restrict this?
/// Using a mutex just to make access to a plain bool or
/// reference sequential is shooting flies with cannons.
/// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and
/// faster.
///
/// On the other hand, `Mutex`es are, in general, easier to
/// verify correctness. An atomic does not behave the same as
/// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s
/// commentary for more details.
///
/// ### Known problems
/// * This lint cannot detect if the mutex is actually used
/// for waiting before a critical section.
/// * This lint has a false positive that warns without considering the case
/// where `Mutex` is used together with `Condvar`.
///
/// ### Example
/// ```no_run
/// # let y = true;
/// # use std::sync::Mutex;
/// let x = Mutex::new(&y);
/// ```
///
/// Use instead:
/// ```no_run
/// # let y = true;
/// # use std::sync::atomic::AtomicBool;
/// let x = AtomicBool::new(y);
/// ```
#[clippy::version = "pre 1.29.0"]
pub MUTEX_ATOMIC,
restriction,
"using a mutex where an atomic value could be used instead."
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `Mutex<X>` where `X` is an integral
/// type.
///
/// ### Why restrict this?
/// Using a mutex just to make access to a plain integer
/// sequential is
/// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster.
///
/// On the other hand, `Mutex`es are, in general, easier to
/// verify correctness. An atomic does not behave the same as
/// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s
/// commentary for more details.
///
/// ### Known problems
/// * This lint cannot detect if the mutex is actually used
/// for waiting before a critical section.
/// * This lint has a false positive that warns without considering the case
/// where `Mutex` is used together with `Condvar`.
/// * This lint suggest using `AtomicU64` instead of `Mutex<u64>`, but
/// `AtomicU64` is not available on some 32-bit platforms.
///
/// ### Example
/// ```no_run
/// # use std::sync::Mutex;
/// let x = Mutex::new(0usize);
/// ```
///
/// Use instead:
/// ```no_run
/// # use std::sync::atomic::AtomicUsize;
/// let x = AtomicUsize::new(0usize);
/// ```
#[clippy::version = "pre 1.29.0"]
pub MUTEX_INTEGER,
restriction,
"using a mutex for an integer type"
}
declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]);
// NOTE: we don't use `check_expr` because that would make us lint every _use_ of such mutexes, not
// just their definitions
impl<'tcx> LateLintPass<'tcx> for Mutex {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !item.span.from_expansion()
&& let ItemKind::Static(_, _, ty, body_id) = item.kind
{
let body = cx.tcx.hir_body(body_id);
let mid_ty = ty_from_hir_ty(cx, ty);
check_expr(cx, body.value.peel_blocks(), &TypeAscriptionKind::Required(ty), mid_ty);
}
}
fn check_local(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx LetStmt<'_>) {
if !stmt.span.from_expansion()
&& let Some(init) = stmt.init
{
let mid_ty = cx.typeck_results().expr_ty(init);
check_expr(cx, init.peel_blocks(), &TypeAscriptionKind::Optional(stmt.ty), mid_ty);
}
}
}
/// Whether the type ascription `: Mutex<X>` (which we'll suggest replacing with `AtomicX`) is
/// required
enum TypeAscriptionKind<'tcx> {
/// Yes; for us, this is the case for statics
Required(&'tcx hir::Ty<'tcx>),
/// No; the ascription might've been necessary in an expression like:
/// ```ignore
/// let mutex: Mutex<u64> = Mutex::new(0);
/// ```
/// to specify the type of `0`, but since `AtomicX` already refers to a concrete type, we won't
/// need this ascription anymore.
Optional(Option<&'tcx hir::Ty<'tcx>>),
}
fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty_ascription: &TypeAscriptionKind<'tcx>, ty: Ty<'tcx>) {
if let ty::Adt(_, subst) = ty.kind()
&& ty.is_diag_item(cx, sym::Mutex)
&& let mutex_param = subst.type_at(0)
&& let Some(atomic_name) = get_atomic_name(mutex_param)
{
let msg = "using a `Mutex` where an atomic would do";
let diag = |diag: &mut Diag<'_, _>| {
// if `expr = Mutex::new(arg)`, we can try emitting a suggestion
if let ExprKind::Call(qpath, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(_mutex, new)) = qpath.kind
&& new.ident.name == sym::new
{
let mut applicability = Applicability::MaybeIncorrect;
let arg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability);
let mut suggs = vec![(expr.span, format!("std::sync::atomic::{atomic_name}::new({arg})"))];
match ty_ascription {
TypeAscriptionKind::Required(ty_ascription) => {
suggs.push((ty_ascription.span, format!("std::sync::atomic::{atomic_name}")));
},
TypeAscriptionKind::Optional(Some(ty_ascription)) => {
// See https://github.com/rust-lang/rust-clippy/pull/15386 for why this is
// required
let colon_ascription = (cx.sess().source_map())
.span_extend_to_prev_char_before(ty_ascription.span, ':', true)
.with_leading_whitespace(cx)
.into_span();
suggs.push((colon_ascription, String::new()));
},
TypeAscriptionKind::Optional(None) => {}, // nothing to remove/replace
}
diag.multipart_suggestion("try", suggs, applicability);
} else {
diag.help(format!("consider using an `{atomic_name}` instead"));
}
diag.help("if you just want the locking behavior and not the internal type, consider using `Mutex<()>`");
};
match *mutex_param.kind() {
ty::Uint(t) if t != UintTy::Usize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag),
ty::Int(t) if t != IntTy::Isize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag),
_ => span_lint_and_then(cx, MUTEX_ATOMIC, expr.span, msg, diag),
}
}
}
fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
match ty.kind() {
ty::Bool => Some("AtomicBool"),
ty::Uint(uint_ty) => {
match uint_ty {
UintTy::U8 => Some("AtomicU8"),
UintTy::U16 => Some("AtomicU16"),
UintTy::U32 => Some("AtomicU32"),
UintTy::U64 => Some("AtomicU64"),
UintTy::Usize => Some("AtomicUsize"),
// `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069
UintTy::U128 => None,
}
},
ty::Int(int_ty) => {
match int_ty {
IntTy::I8 => Some("AtomicI8"),
IntTy::I16 => Some("AtomicI16"),
IntTy::I32 => Some("AtomicI32"),
IntTy::I64 => Some("AtomicI64"),
IntTy::Isize => Some("AtomicIsize"),
// `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069
IntTy::I128 => None,
}
},
// `AtomicPtr` only accepts `*mut T`
ty::RawPtr(_, Mutability::Mut) => Some("AtomicPtr"),
_ => None,
}
}