| use clippy_utils::diagnostics::span_lint_and_sugg; | |
| use clippy_utils::source::snippet; | |
| use clippy_utils::sugg::{Sugg, has_enclosing_paren}; | |
| use clippy_utils::ty::adjust_derefs_manually_drop; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp}; | |
| use rustc_lint::{LateContext, LateLintPass, declare_lint_pass}; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for usage of `*&` and `*&mut` in expressions. | |
| /// | |
| /// ### Why is this bad? | |
| /// Immediately dereferencing a reference is no-op and | |
| /// makes the code less clear. | |
| /// | |
| /// ### Known problems | |
| /// Multiple dereference/addrof pairs are not handled so | |
| /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect. | |
| /// | |
| /// ### Example | |
| /// ```rust,ignore | |
| /// let a = f(*&mut b); | |
| /// let c = *&d; | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```rust,ignore | |
| /// let a = f(b); | |
| /// let c = d; | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub DEREF_ADDROF, | |
| complexity, | |
| "use of `*&` or `*&mut` in an expression" | |
| } | |
| declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); | |
| impl LateLintPass<'_> for DerefAddrOf { | |
| fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { | |
| if !e.span.from_expansion() | |
| && let ExprKind::Unary(UnOp::Deref, deref_target) = e.kind | |
| && !deref_target.span.from_expansion() | |
| && let ExprKind::AddrOf(_, _, addrof_target) = deref_target.kind | |
| // NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section. | |
| // See #12854 for details. | |
| && !matches!(addrof_target.kind, ExprKind::Array(_)) | |
| && deref_target.span.eq_ctxt(e.span) | |
| && !addrof_target.span.from_expansion() | |
| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability); | |
| // If this expression is an explicit `DerefMut` of a `ManuallyDrop` reached through a | |
| // union, we may remove the reference if we are at the point where the implicit | |
| // dereference would take place. Otherwise, we should not lint. | |
| let sugg = match is_manually_drop_through_union(cx, e.hir_id, addrof_target) { | |
| ManuallyDropThroughUnion::Directly => sugg().deref(), | |
| ManuallyDropThroughUnion::Indirect => return, | |
| ManuallyDropThroughUnion::No => sugg(), | |
| }; | |
| let sugg = if has_enclosing_paren(snippet(cx, e.span, "")) { | |
| sugg.maybe_paren() | |
| } else { | |
| sugg | |
| }; | |
| span_lint_and_sugg( | |
| cx, | |
| DEREF_ADDROF, | |
| e.span, | |
| "immediately dereferencing a reference", | |
| "try", | |
| sugg.to_string(), | |
| applicability, | |
| ); | |
| } | |
| } | |
| } | |
| /// Is this a `ManuallyDrop` reached through a union, and when is `DerefMut` called on it? | |
| enum ManuallyDropThroughUnion { | |
| /// `ManuallyDrop` reached through a union and immediately explicitely dereferenced | |
| Directly, | |
| /// `ManuallyDrop` reached through a union, and dereferenced later on | |
| Indirect, | |
| /// Any other situation | |
| No, | |
| } | |
| /// Check if `addrof_target` is part of an access to a `ManuallyDrop` entity reached through a | |
| /// union, and when it is dereferenced using `DerefMut` starting from `expr_id` and going up. | |
| fn is_manually_drop_through_union( | |
| cx: &LateContext<'_>, | |
| expr_id: HirId, | |
| addrof_target: &Expr<'_>, | |
| ) -> ManuallyDropThroughUnion { | |
| if is_reached_through_union(cx, addrof_target) { | |
| let typeck = cx.typeck_results(); | |
| for (idx, id) in std::iter::once(expr_id) | |
| .chain(cx.tcx.hir_parent_id_iter(expr_id)) | |
| .enumerate() | |
| { | |
| if let Node::Expr(expr) = cx.tcx.hir_node(id) { | |
| if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) { | |
| return if idx == 0 { | |
| ManuallyDropThroughUnion::Directly | |
| } else { | |
| ManuallyDropThroughUnion::Indirect | |
| }; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| ManuallyDropThroughUnion::No | |
| } | |
| /// Checks whether `expr` denotes an object reached through a union | |
| fn is_reached_through_union(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> bool { | |
| while let ExprKind::Field(parent, _) | ExprKind::Index(parent, _, _) = expr.kind { | |
| if cx.typeck_results().expr_ty_adjusted(parent).is_union() { | |
| return true; | |
| } | |
| expr = parent; | |
| } | |
| false | |
| } |