|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | | -use clippy_utils::source::{SpanRangeExt, snippet_with_applicability}; |
3 | | -use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; |
| 2 | +use clippy_utils::source::snippet; |
| 3 | +use clippy_utils::sugg::{Sugg, has_enclosing_paren}; |
| 4 | +use clippy_utils::ty::adjust_derefs_manually_drop; |
4 | 5 | use rustc_errors::Applicability; |
5 | | -use rustc_lint::{EarlyContext, EarlyLintPass}; |
| 6 | +use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp}; |
| 7 | +use rustc_lint::{LateContext, LateLintPass}; |
6 | 8 | use rustc_session::declare_lint_pass; |
7 | | -use rustc_span::{BytePos, Span}; |
8 | 9 |
|
9 | 10 | declare_clippy_lint! { |
10 | 11 | /// ### What it does |
@@ -37,75 +38,95 @@ declare_clippy_lint! { |
37 | 38 |
|
38 | 39 | declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); |
39 | 40 |
|
40 | | -fn without_parens(mut e: &Expr) -> &Expr { |
41 | | - while let ExprKind::Paren(ref child_e) = e.kind { |
42 | | - e = child_e; |
43 | | - } |
44 | | - e |
45 | | -} |
46 | | - |
47 | | -impl EarlyLintPass for DerefAddrOf { |
48 | | - fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { |
49 | | - if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind |
50 | | - && let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind |
| 41 | +impl LateLintPass<'_> for DerefAddrOf { |
| 42 | + fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { |
| 43 | + if !e.span.from_expansion() |
| 44 | + && let ExprKind::Unary(UnOp::Deref, deref_target) = e.kind |
| 45 | + && !deref_target.span.from_expansion() |
| 46 | + && let ExprKind::AddrOf(_, _, addrof_target) = deref_target.kind |
51 | 47 | // NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section. |
52 | 48 | // See #12854 for details. |
53 | 49 | && !matches!(addrof_target.kind, ExprKind::Array(_)) |
54 | 50 | && deref_target.span.eq_ctxt(e.span) |
55 | 51 | && !addrof_target.span.from_expansion() |
56 | 52 | { |
57 | 53 | let mut applicability = Applicability::MachineApplicable; |
58 | | - let sugg = if e.span.from_expansion() { |
59 | | - if let Some(macro_source) = e.span.get_source_text(cx) { |
60 | | - // Remove leading whitespace from the given span |
61 | | - // e.g: ` $visitor` turns into `$visitor` |
62 | | - let trim_leading_whitespaces = |span: Span| { |
63 | | - span.get_source_text(cx) |
64 | | - .and_then(|snip| { |
65 | | - #[expect(clippy::cast_possible_truncation)] |
66 | | - snip.find(|c: char| !c.is_whitespace()) |
67 | | - .map(|pos| span.lo() + BytePos(pos as u32)) |
68 | | - }) |
69 | | - .map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace)) |
70 | | - }; |
| 54 | + let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability); |
71 | 55 |
|
72 | | - let mut generate_snippet = |pattern: &str| { |
73 | | - #[expect(clippy::cast_possible_truncation)] |
74 | | - macro_source.rfind(pattern).map(|pattern_pos| { |
75 | | - let rpos = pattern_pos + pattern.len(); |
76 | | - let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32)); |
77 | | - let span = trim_leading_whitespaces(span_after_ref); |
78 | | - snippet_with_applicability(cx, span, "_", &mut applicability) |
79 | | - }) |
80 | | - }; |
| 56 | + // If this expression is an explicit `DerefMut` of a `ManuallyDrop` reached through a |
| 57 | + // union, we may remove the reference if we are at the point where the implicit |
| 58 | + // dereference would take place. Otherwise, we should not lint. |
| 59 | + let sugg = match is_manually_drop_through_union(cx, e.hir_id, addrof_target) { |
| 60 | + ManuallyDropThroughUnion::Directly => sugg().deref(), |
| 61 | + ManuallyDropThroughUnion::Indirect => return, |
| 62 | + ManuallyDropThroughUnion::No => sugg(), |
| 63 | + }; |
| 64 | + |
| 65 | + let sugg = if has_enclosing_paren(snippet(cx, e.span, "")) { |
| 66 | + sugg.maybe_paren() |
| 67 | + } else { |
| 68 | + sugg |
| 69 | + }; |
| 70 | + |
| 71 | + span_lint_and_sugg( |
| 72 | + cx, |
| 73 | + DEREF_ADDROF, |
| 74 | + e.span, |
| 75 | + "immediately dereferencing a reference", |
| 76 | + "try", |
| 77 | + sugg.to_string(), |
| 78 | + applicability, |
| 79 | + ); |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +/// Is this a `ManuallyDrop` reached through a union, and when is `DerefMut` called on it? |
| 85 | +enum ManuallyDropThroughUnion { |
| 86 | + /// `ManuallyDrop` reached through a union and immediately explicitely dereferenced |
| 87 | + Directly, |
| 88 | + /// `ManuallyDrop` reached through a union, and dereferenced later on |
| 89 | + Indirect, |
| 90 | + /// Any other situation |
| 91 | + No, |
| 92 | +} |
81 | 93 |
|
82 | | - if *mutability == Mutability::Mut { |
83 | | - generate_snippet("mut") |
| 94 | +/// Check if `addrof_target` is part of an access to a `ManuallyDrop` entity reached through a |
| 95 | +/// union, and when it is dereferenced using `DerefMut` starting from `expr_id` and going up. |
| 96 | +fn is_manually_drop_through_union( |
| 97 | + cx: &LateContext<'_>, |
| 98 | + expr_id: HirId, |
| 99 | + addrof_target: &Expr<'_>, |
| 100 | +) -> ManuallyDropThroughUnion { |
| 101 | + if is_reached_through_union(cx, addrof_target) { |
| 102 | + let typeck = cx.typeck_results(); |
| 103 | + for (idx, id) in std::iter::once(expr_id) |
| 104 | + .chain(cx.tcx.hir_parent_id_iter(expr_id)) |
| 105 | + .enumerate() |
| 106 | + { |
| 107 | + if let Node::Expr(expr) = cx.tcx.hir_node(id) { |
| 108 | + if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) { |
| 109 | + return if idx == 0 { |
| 110 | + ManuallyDropThroughUnion::Directly |
84 | 111 | } else { |
85 | | - generate_snippet("&") |
86 | | - } |
87 | | - } else { |
88 | | - Some(snippet_with_applicability(cx, e.span, "_", &mut applicability)) |
| 112 | + ManuallyDropThroughUnion::Indirect |
| 113 | + }; |
89 | 114 | } |
90 | 115 | } else { |
91 | | - Some(snippet_with_applicability( |
92 | | - cx, |
93 | | - addrof_target.span, |
94 | | - "_", |
95 | | - &mut applicability, |
96 | | - )) |
97 | | - }; |
98 | | - if let Some(sugg) = sugg { |
99 | | - span_lint_and_sugg( |
100 | | - cx, |
101 | | - DEREF_ADDROF, |
102 | | - e.span, |
103 | | - "immediately dereferencing a reference", |
104 | | - "try", |
105 | | - sugg.to_string(), |
106 | | - applicability, |
107 | | - ); |
| 116 | + break; |
108 | 117 | } |
109 | 118 | } |
110 | 119 | } |
| 120 | + ManuallyDropThroughUnion::No |
| 121 | +} |
| 122 | + |
| 123 | +/// Checks whether `expr` denotes an object reached through a union |
| 124 | +fn is_reached_through_union(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> bool { |
| 125 | + while let ExprKind::Field(parent, _) | ExprKind::Index(parent, _, _) = expr.kind { |
| 126 | + if cx.typeck_results().expr_ty_adjusted(parent).is_union() { |
| 127 | + return true; |
| 128 | + } |
| 129 | + expr = parent; |
| 130 | + } |
| 131 | + false |
111 | 132 | } |
0 commit comments