-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Do not suggest to use implicit DerefMut
on ManuallyDrop
reached through unions
#14387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3688b33
2cfbb05
27478be
ff496ad
7224dff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::{SpanRangeExt, snippet_with_applicability}; | ||
use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; | ||
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_lint::{EarlyContext, EarlyLintPass}; | ||
use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::declare_lint_pass; | ||
use rustc_span::{BytePos, Span}; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
|
@@ -37,75 +38,95 @@ declare_clippy_lint! { | |
|
||
declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); | ||
|
||
fn without_parens(mut e: &Expr) -> &Expr { | ||
while let ExprKind::Paren(ref child_e) = e.kind { | ||
e = child_e; | ||
} | ||
e | ||
} | ||
|
||
impl EarlyLintPass for DerefAddrOf { | ||
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { | ||
if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind | ||
&& let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind | ||
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 sugg = if e.span.from_expansion() { | ||
if let Some(macro_source) = e.span.get_source_text(cx) { | ||
// Remove leading whitespace from the given span | ||
// e.g: ` $visitor` turns into `$visitor` | ||
let trim_leading_whitespaces = |span: Span| { | ||
span.get_source_text(cx) | ||
.and_then(|snip| { | ||
#[expect(clippy::cast_possible_truncation)] | ||
snip.find(|c: char| !c.is_whitespace()) | ||
.map(|pos| span.lo() + BytePos(pos as u32)) | ||
}) | ||
.map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace)) | ||
}; | ||
let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability); | ||
|
||
let mut generate_snippet = |pattern: &str| { | ||
#[expect(clippy::cast_possible_truncation)] | ||
macro_source.rfind(pattern).map(|pattern_pos| { | ||
let rpos = pattern_pos + pattern.len(); | ||
let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32)); | ||
let span = trim_leading_whitespaces(span_after_ref); | ||
snippet_with_applicability(cx, span, "_", &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, | ||
} | ||
|
||
if *mutability == Mutability::Mut { | ||
generate_snippet("mut") | ||
/// 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of getting the node, we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In fact, |
||
if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) { | ||
return if idx == 0 { | ||
ManuallyDropThroughUnion::Directly | ||
} else { | ||
generate_snippet("&") | ||
} | ||
} else { | ||
Some(snippet_with_applicability(cx, e.span, "_", &mut applicability)) | ||
ManuallyDropThroughUnion::Indirect | ||
}; | ||
} | ||
} else { | ||
Some(snippet_with_applicability( | ||
cx, | ||
addrof_target.span, | ||
"_", | ||
&mut applicability, | ||
)) | ||
}; | ||
if let Some(sugg) = sugg { | ||
span_lint_and_sugg( | ||
cx, | ||
DEREF_ADDROF, | ||
e.span, | ||
"immediately dereferencing a reference", | ||
"try", | ||
sugg.to_string(), | ||
applicability, | ||
); | ||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at
hir_parent_id_iter
, I think that it already includes the currentexpr_id
in the traversal, doesn't it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so: the
.next()
ends withSo it never yields
current_id
(which is the initialization value).