|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | 2 | use clippy_utils::expr_custom_deref_adjustment; |
3 | 3 | use clippy_utils::res::MaybeDef; |
4 | | -use clippy_utils::ty::peel_and_count_ty_refs; |
| 4 | +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs}; |
5 | 5 | use rustc_errors::Applicability; |
6 | | -use rustc_hir::{Expr, Mutability}; |
| 6 | +use rustc_hir::{Expr, ExprKind, Mutability, UnOp}; |
7 | 7 | use rustc_lint::LateContext; |
| 8 | +use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind}; |
8 | 9 | use rustc_span::{Span, sym}; |
9 | 10 |
|
10 | 11 | use super::MUT_MUTEX_LOCK; |
11 | 12 |
|
12 | 13 | pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { |
| 14 | + let typeck = cx.typeck_results(); |
| 15 | + |
| 16 | + // Make sure that we have a mutable access to `Mutex`: |
| 17 | + // |
| 18 | + // 1. Check that there are no custom deref adjustments turning the access immutable (e.g. |
| 19 | + // `Arc<Mutex>` derefs to `&Mutex`) |
13 | 20 | if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) |
14 | | - // NOTE: the reason we don't use `expr_ty_adjusted` here is that a call to `Mutex::lock` by itself |
15 | | - // adjusts the receiver to be `&Mutex` |
16 | | - && let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv)) |
| 21 | + // 2. Check that the receiver is behind one or more `&mut`s -- we want to have mutable |
| 22 | + // access, while not outright owning it -- if the latter were the case, then changing |
| 23 | + // `.lock()` to `.get_mut()`, could easily result in a conflict with other existing |
| 24 | + // immutable borrows. |
| 25 | + // |
| 26 | + // NOTE: `mutbl` being `Some` is enough to determine that there was at least one layer of |
| 27 | + // references, so no need to check `n` |
| 28 | + // NOTE: the reason we don't use `expr_ty_adjusted` here is that a call |
| 29 | + // to `Mutex::lock` by itself adjusts the receiver to be `&Mutex` |
| 30 | + && let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(typeck.expr_ty(recv)) |
17 | 31 | && recv_ty.is_diag_item(cx, sym::Mutex) |
18 | 32 | { |
| 33 | + let deref_mut_trait = cx.tcx.lang_items().deref_mut_trait(); |
| 34 | + let impls_deref_mut = |ty| deref_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[])); |
| 35 | + |
| 36 | + // The mutex was accessed either directly (`mutex.lock()`), or through a series of |
| 37 | + // deref/field/indexing projections. Since the final `.lock()` call only requires `&Mutex`, |
| 38 | + // those might be immutable, and so we need to manually check whether mutable projections |
| 39 | + // would've been possible. For that, we'll repeatedly peel off projections and check each |
| 40 | + // intermediary receiver. |
| 41 | + let mut r = recv; |
| 42 | + loop { |
| 43 | + // Check that the (deref) adjustments could be made mutable |
| 44 | + if (typeck.expr_adjustments(r)) |
| 45 | + .iter() |
| 46 | + .map_while(|a| match a.kind { |
| 47 | + Adjust::Deref(x) => Some((a.target, x)), |
| 48 | + _ => None, |
| 49 | + }) |
| 50 | + .try_fold(typeck.expr_ty(r), |ty, (target, deref)| match deref { |
| 51 | + // There has been an overloaded deref, most likely an immutable one, as `.lock()` didn't require a |
| 52 | + // mutable one -- we need to check if a mutable deref would've been possible, i.e. if |
| 53 | + // `ty: DerefMut<Target = target>` (we don't need to check the `Target` part, as `Deref` and |
| 54 | + // `DerefMut` impls necessarily have the same one) |
| 55 | + DerefAdjustKind::Overloaded(_) => impls_deref_mut(ty).then_some(target), |
| 56 | + // There has been a simple deref; if it happened on a `&T`, then we know it will can't be changed to |
| 57 | + // provide mutable access |
| 58 | + DerefAdjustKind::Builtin => (ty.ref_mutability() != Some(Mutability::Not)).then_some(target), |
| 59 | + DerefAdjustKind::Pin => Some(target), |
| 60 | + }) |
| 61 | + .is_none() |
| 62 | + { |
| 63 | + return; |
| 64 | + } |
| 65 | + |
| 66 | + // Peel off one projection |
| 67 | + match r.kind { |
| 68 | + ExprKind::Unary(UnOp::Deref, base) => { |
| 69 | + if impls_deref_mut(typeck.expr_ty_adjusted(base)) { |
| 70 | + r = base; |
| 71 | + } else { |
| 72 | + return; |
| 73 | + } |
| 74 | + }, |
| 75 | + ExprKind::Index(..) | ExprKind::Field(..) => { |
| 76 | + // We don't want to lint on indexing and field accesses, as both of those would take exclusive |
| 77 | + // access to only part of a value -- which would conflict with any immutable reborrow over |
| 78 | + // the whole value |
| 79 | + return; |
| 80 | + }, |
| 81 | + _ => { |
| 82 | + // We arrived at the innermost receiver |
| 83 | + if let ExprKind::Path(ref p) = r.kind |
| 84 | + && cx |
| 85 | + .qpath_res(p, r.hir_id) |
| 86 | + .opt_def_id() |
| 87 | + .and_then(|id| cx.tcx.static_mutability(id)) |
| 88 | + == Some(Mutability::Not) |
| 89 | + { |
| 90 | + // The mutex is stored in a `static`, and we don't want to suggest making that |
| 91 | + // mutable |
| 92 | + return; |
| 93 | + } |
| 94 | + // No more projections to check |
| 95 | + break; |
| 96 | + }, |
| 97 | + } |
| 98 | + } |
| 99 | + |
19 | 100 | span_lint_and_sugg( |
20 | 101 | cx, |
21 | 102 | MUT_MUTEX_LOCK, |
|
0 commit comments