|
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