Skip to content

Commit 73b5a59

Browse files
authored
Do not suggest to use implicit DerefMut on ManuallyDrop reached through unions (#14387)
This requires making the `deref_addrof` lint a late lint instead of an early lint to check for types. changelog: [`deref_addrof`]: do not suggest implicit `DerefMut` on `ManuallyDrop` union fields Fix #14386
2 parents feb18ca + 7224dff commit 73b5a59

File tree

7 files changed

+284
-114
lines changed

7 files changed

+284
-114
lines changed

clippy_lints/src/dereference.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
4-
use clippy_utils::ty::{implements_trait, is_manually_drop};
4+
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop};
55
use clippy_utils::{
66
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local,
77
peel_middle_ty_refs,
88
};
9-
use core::mem;
109
use rustc_ast::util::parser::ExprPrecedence;
1110
use rustc_data_structures::fx::FxIndexMap;
1211
use rustc_errors::Applicability;
@@ -707,14 +706,6 @@ fn try_parse_ref_op<'tcx>(
707706
))
708707
}
709708

710-
// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
711-
fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
712-
adjustments.iter().any(|a| {
713-
let ty = mem::replace(&mut ty, a.target);
714-
matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
715-
})
716-
}
717-
718709
// Checks whether the type for a deref call actually changed the type, not just the mutability of
719710
// the reference.
720711
fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
600600
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf)));
601601
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
602602
store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf)));
603-
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
603+
store.register_late_pass(|_| Box::new(reference::DerefAddrOf));
604604
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
605605
let format_args = format_args_storage.clone();
606606
store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone())));

clippy_lints/src/reference.rs

Lines changed: 81 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
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;
45
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};
68
use rustc_session::declare_lint_pass;
7-
use rustc_span::{BytePos, Span};
89

910
declare_clippy_lint! {
1011
/// ### What it does
@@ -37,75 +38,95 @@ declare_clippy_lint! {
3738

3839
declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);
3940

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
5147
// NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section.
5248
// See #12854 for details.
5349
&& !matches!(addrof_target.kind, ExprKind::Array(_))
5450
&& deref_target.span.eq_ctxt(e.span)
5551
&& !addrof_target.span.from_expansion()
5652
{
5753
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);
7155

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+
}
8193

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
84111
} else {
85-
generate_snippet("&")
86-
}
87-
} else {
88-
Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
112+
ManuallyDropThroughUnion::Indirect
113+
};
89114
}
90115
} 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;
108117
}
109118
}
110119
}
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
111132
}

clippy_utils/src/ty/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_lint::LateContext;
1818
use rustc_middle::mir::ConstValue;
1919
use rustc_middle::mir::interpret::Scalar;
2020
use rustc_middle::traits::EvaluationResult;
21+
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
2122
use rustc_middle::ty::layout::ValidityRequirement;
2223
use rustc_middle::ty::{
2324
self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
@@ -31,7 +32,7 @@ use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
3132
use rustc_trait_selection::traits::{Obligation, ObligationCause};
3233
use std::assert_matches::debug_assert_matches;
3334
use std::collections::hash_map::Entry;
34-
use std::iter;
35+
use std::{iter, mem};
3536

3637
use crate::path_res;
3738
use crate::paths::{PathNS, lookup_path_str};
@@ -1382,7 +1383,6 @@ pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
13821383
|| matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did()))
13831384
}
13841385

1385-
/// Gets the index of a field by name.
13861386
pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
13871387
match *ty.kind() {
13881388
ty::Adt(def, _) if def.is_union() || def.is_struct() => {
@@ -1392,3 +1392,11 @@ pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
13921392
_ => None,
13931393
}
13941394
}
1395+
1396+
/// Checks if the adjustments contain a mutable dereference of a `ManuallyDrop<_>`.
1397+
pub fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
1398+
adjustments.iter().any(|a| {
1399+
let ty = mem::replace(&mut ty, a.target);
1400+
matches!(a.kind, Adjust::Deref(Some(op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
1401+
})
1402+
}

tests/ui/deref_addrof.fixed

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
//@aux-build:proc_macros.rs
2-
3-
#![allow(clippy::return_self_not_must_use, clippy::useless_vec)]
1+
#![allow(
2+
dangerous_implicit_autorefs,
3+
clippy::explicit_auto_deref,
4+
clippy::return_self_not_must_use,
5+
clippy::useless_vec
6+
)]
47
#![warn(clippy::deref_addrof)]
58

6-
extern crate proc_macros;
7-
use proc_macros::inline_macros;
8-
99
fn get_number() -> usize {
1010
10
1111
}
@@ -56,19 +56,75 @@ fn main() {
5656
//~^ deref_addrof
5757
// do NOT lint for array as semantic differences with/out `*&`.
5858
let _arr = *&[0, 1, 2, 3, 4];
59+
60+
// Do not lint when text comes from macro
61+
macro_rules! mac {
62+
(dr) => {
63+
*&0
64+
};
65+
(dr $e:expr) => {
66+
*&$e
67+
};
68+
(r $e:expr) => {
69+
&$e
70+
};
71+
}
72+
let b = mac!(dr);
73+
let b = mac!(dr a);
74+
let b = *mac!(r a);
5975
}
6076

61-
#[derive(Copy, Clone)]
62-
pub struct S;
63-
#[inline_macros]
64-
impl S {
65-
pub fn f(&self) -> &Self {
66-
inline!($(@expr self))
67-
//~^ deref_addrof
77+
fn issue14386() {
78+
use std::mem::ManuallyDrop;
79+
80+
#[derive(Copy, Clone)]
81+
struct Data {
82+
num: u64,
6883
}
69-
#[allow(unused_mut)] // mut will be unused, once the macro is fixed
70-
pub fn f_mut(mut self) -> Self {
71-
inline!($(@expr self))
84+
85+
#[derive(Clone, Copy)]
86+
struct M {
87+
md: ManuallyDrop<[u8; 4]>,
88+
}
89+
90+
union DataWithPadding<'lt> {
91+
data: ManuallyDrop<Data>,
92+
prim: ManuallyDrop<u64>,
93+
padding: [u8; size_of::<Data>()],
94+
tup: (ManuallyDrop<Data>, ()),
95+
indirect: M,
96+
indirect_arr: [M; 2],
97+
indirect_ref: &'lt mut M,
98+
}
99+
100+
let mut a = DataWithPadding {
101+
padding: [0; size_of::<DataWithPadding>()],
102+
};
103+
unsafe {
104+
a.padding = [1; size_of::<DataWithPadding>()];
72105
//~^ deref_addrof
106+
a.tup.1 = ();
107+
//~^ deref_addrof
108+
*a.prim = 0;
109+
//~^ deref_addrof
110+
111+
(*a.data).num = 42;
112+
//~^ deref_addrof
113+
(*a.indirect.md)[3] = 1;
114+
//~^ deref_addrof
115+
(*a.indirect_arr[1].md)[3] = 1;
116+
//~^ deref_addrof
117+
(*a.indirect_ref.md)[3] = 1;
118+
//~^ deref_addrof
119+
120+
// Check that raw pointers are properly considered as well
121+
*a.prim = 0;
122+
//~^ deref_addrof
123+
(*a.data).num = 42;
124+
//~^ deref_addrof
125+
126+
// Do not lint, as the dereference happens later, we cannot
127+
// just remove `&mut`
128+
(*&mut a.tup).0.num = 42;
73129
}
74130
}

0 commit comments

Comments
 (0)