Skip to content

Commit 526ab45

Browse files
authored
Rewrite EndianBytes lint pass (#17363)
Changes: * Check the method name before type checking. * Don't use `get_def_path` for `format!` when linting. * Check that the `from_*_bytes` method used is defined on one of the primitives. * Add suggestions for the other endian conversions if applicable. * Lint in the context of the method name. * Lint all uses of the methods, not just calls. * Split the tests into one file per lint. The old test file broke `ui_test` so something had to be done. It was also hard to actually check if it was correct with all the repeated macro expansions. changelog: [`big_endian_bytes`], [`host_endian_bytes`], [`little_endian_bytes`]: Lint any time the methods are named instead of just when called. changelog: [`big_endian_bytes`], [`host_endian_bytes`], [`little_endian_bytes`]: Lint when `to_*_bytes` is called via UFCS. changelog: [`big_endian_bytes`], [`host_endian_bytes`], [`little_endian_bytes`]: Always lint when the method name comes from the current crate.
2 parents 0547eab + d04b314 commit 526ab45

17 files changed

Lines changed: 3100 additions & 1373 deletions

clippy_lints/src/endian_bytes.rs

Lines changed: 58 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use crate::Lint;
21
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::res::{MaybeDef as _, MaybeTypeckRes as _};
33
use clippy_utils::{is_lint_allowed, sym};
4-
use rustc_hir::{Expr, ExprKind};
5-
use rustc_lint::{LateContext, LateLintPass, LintContext as _};
6-
use rustc_middle::ty::Ty;
4+
use core::ptr;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, QPath};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_middle::ty;
79
use rustc_session::declare_lint_pass;
8-
use rustc_span::Symbol;
9-
use std::fmt::Write as _;
1010

1111
declare_clippy_lint! {
1212
/// ### What it does
@@ -69,133 +69,67 @@ declare_lint_pass!(EndianBytes => [
6969
LITTLE_ENDIAN_BYTES,
7070
]);
7171

72-
const HOST_NAMES: [Symbol; 2] = [sym::from_ne_bytes, sym::to_ne_bytes];
73-
const LITTLE_NAMES: [Symbol; 2] = [sym::from_le_bytes, sym::to_le_bytes];
74-
const BIG_NAMES: [Symbol; 2] = [sym::from_be_bytes, sym::to_be_bytes];
75-
76-
#[derive(Clone, Debug)]
77-
enum LintKind {
78-
Host,
79-
Little,
80-
Big,
81-
}
82-
83-
#[derive(Clone, Copy, PartialEq)]
84-
enum Prefix {
72+
#[derive(Clone, Copy)]
73+
enum Direction {
8574
From,
8675
To,
8776
}
8877

89-
impl LintKind {
90-
fn allowed(&self, cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
91-
is_lint_allowed(cx, self.as_lint(), expr.hir_id)
92-
}
93-
94-
fn as_lint(&self) -> &'static Lint {
95-
match self {
96-
LintKind::Host => HOST_ENDIAN_BYTES,
97-
LintKind::Little => LITTLE_ENDIAN_BYTES,
98-
LintKind::Big => BIG_ENDIAN_BYTES,
99-
}
100-
}
101-
102-
fn as_name(&self, prefix: Prefix) -> Symbol {
103-
let index = usize::from(prefix == Prefix::To);
104-
105-
match self {
106-
LintKind::Host => HOST_NAMES[index],
107-
LintKind::Little => LITTLE_NAMES[index],
108-
LintKind::Big => BIG_NAMES[index],
109-
}
110-
}
111-
}
112-
11378
impl LateLintPass<'_> for EndianBytes {
114-
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
115-
let (prefix, name, ty_expr) = match expr.kind {
116-
ExprKind::MethodCall(method_name, receiver, [], ..) => (Prefix::To, method_name.ident.name, receiver),
117-
ExprKind::Call(function, ..)
118-
if let ExprKind::Path(qpath) = function.kind
119-
&& let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id()
120-
&& let Some(function_name) = cx.get_def_path(def_id).last() =>
121-
{
122-
(Prefix::From, *function_name, expr)
79+
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
80+
let (sp, direction, lint, msg) = match e.kind {
81+
// rustfmt wants to break each arm into one line per tuple element which
82+
// really hurts readability.
83+
#[rustfmt::skip]
84+
ExprKind::MethodCall(seg, _, [], _) => match seg.ident.name {
85+
sym::to_ne_bytes => (seg.ident.span, Direction::To, HOST_ENDIAN_BYTES, "use of `to_ne_bytes`"),
86+
sym::to_le_bytes => (seg.ident.span, Direction::To, LITTLE_ENDIAN_BYTES, "use of `to_le_bytes`"),
87+
sym::to_be_bytes => (seg.ident.span, Direction::To, BIG_ENDIAN_BYTES, "use of `to_be_bytes`"),
88+
_ => return,
89+
},
90+
#[rustfmt::skip]
91+
ExprKind::Path(QPath::TypeRelative(_, seg)) => match seg.ident.name {
92+
sym::from_ne_bytes => (seg.ident.span, Direction::From, HOST_ENDIAN_BYTES, "use of `from_ne_bytes`"),
93+
sym::from_le_bytes => (seg.ident.span, Direction::From, LITTLE_ENDIAN_BYTES, "use of `from_le_bytes`"),
94+
sym::from_be_bytes => (seg.ident.span, Direction::From, BIG_ENDIAN_BYTES, "use of `from_be_bytes`"),
95+
sym::to_ne_bytes => (seg.ident.span, Direction::To, HOST_ENDIAN_BYTES, "use of `to_ne_bytes`"),
96+
sym::to_le_bytes => (seg.ident.span, Direction::To, LITTLE_ENDIAN_BYTES, "use of `to_le_bytes`"),
97+
sym::to_be_bytes => (seg.ident.span, Direction::To, BIG_ENDIAN_BYTES, "use of `to_be_bytes`"),
98+
_ => return,
12399
},
124100
_ => return,
125101
};
126-
if !expr.span.in_external_macro(cx.sess().source_map())
127-
&& let ty = cx.typeck_results().expr_ty(ty_expr)
128-
&& ty.is_primitive_ty()
102+
if let Some(ty) = cx.ty_based_def(e.hir_id).opt_parent(cx).opt_impl_ty(cx)
103+
&& let ty::Uint(_) | ty::Int(_) | ty::Float(_) = *ty.instantiate_identity().skip_normalization().kind()
104+
// Only check where the name itself comes from. The point of the lints is to
105+
// catch when the wrong byte order is used so we only care if the current crate
106+
// decided on the byte order. Which crate actually assembled the path/call
107+
// isn't relevant for these lints.
108+
&& !sp.in_external_macro(cx.tcx.sess.source_map())
129109
{
130-
maybe_lint_endian_bytes(cx, expr, prefix, name, ty);
131-
}
132-
}
133-
}
134-
135-
fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix, name: Symbol, ty: Ty<'_>) {
136-
let ne = LintKind::Host.as_name(prefix);
137-
let le = LintKind::Little.as_name(prefix);
138-
let be = LintKind::Big.as_name(prefix);
139-
140-
let (lint, other_lints) = match name {
141-
name if name == ne => ((&LintKind::Host), [(&LintKind::Little), (&LintKind::Big)]),
142-
name if name == le => ((&LintKind::Little), [(&LintKind::Host), (&LintKind::Big)]),
143-
name if name == be => ((&LintKind::Big), [(&LintKind::Host), (&LintKind::Little)]),
144-
_ => return,
145-
};
146-
147-
span_lint_and_then(
148-
cx,
149-
lint.as_lint(),
150-
expr.span,
151-
format!(
152-
"usage of the {}`{ty}::{}`{}",
153-
if prefix == Prefix::From { "function " } else { "" },
154-
lint.as_name(prefix),
155-
if prefix == Prefix::To { " method" } else { "" },
156-
),
157-
move |diag| {
158-
// all lints disallowed, don't give help here
159-
if [&[lint], other_lints.as_slice()]
160-
.concat()
161-
.iter()
162-
.all(|lint| !lint.allowed(cx, expr))
163-
{
164-
return;
165-
}
166-
167-
// ne_bytes and all other lints allowed
168-
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
169-
diag.help("specify the desired endianness explicitly");
170-
return;
171-
}
172-
173-
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
174-
// le_bytes is not
175-
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
176-
diag.help("use the native endianness instead");
177-
return;
178-
}
179-
180-
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
181-
let len = allowed_lints.clone().count();
182-
183-
let mut help_str = "use ".to_owned();
184-
185-
for (i, lint) in allowed_lints.enumerate() {
186-
let only_one = len == 1;
187-
if !only_one {
188-
help_str.push_str("either of ");
110+
span_lint_and_then(cx, lint, sp, msg, |diag| {
111+
if !ptr::addr_eq(lint, HOST_ENDIAN_BYTES) && is_lint_allowed(cx, HOST_ENDIAN_BYTES, e.hir_id) {
112+
let (msg, sugg) = match direction {
113+
Direction::From => ("convert from native endian", "from_ne_bytes"),
114+
Direction::To => ("convert to native endian", "to_ne_bytes"),
115+
};
116+
diag.span_suggestion(sp, msg, sugg, Applicability::MaybeIncorrect);
189117
}
190-
191-
write!(help_str, "`{ty}::{}` ", lint.as_name(prefix)).unwrap();
192-
193-
if i != len && !only_one {
194-
help_str.push_str("or ");
118+
if !ptr::addr_eq(lint, LITTLE_ENDIAN_BYTES) && is_lint_allowed(cx, LITTLE_ENDIAN_BYTES, e.hir_id) {
119+
let (msg, sugg) = match direction {
120+
Direction::From => ("convert from little endian", "from_le_bytes"),
121+
Direction::To => ("convert to little endian", "to_le_bytes"),
122+
};
123+
diag.span_suggestion(sp, msg, sugg, Applicability::MaybeIncorrect);
195124
}
196-
}
197-
help_str.push_str("instead");
198-
diag.help(help_str);
199-
},
200-
);
125+
if !ptr::addr_eq(lint, BIG_ENDIAN_BYTES) && is_lint_allowed(cx, BIG_ENDIAN_BYTES, e.hir_id) {
126+
let (msg, sugg) = match direction {
127+
Direction::From => ("convert from big endian", "from_be_bytes"),
128+
Direction::To => ("convert to big endian", "to_be_bytes"),
129+
};
130+
diag.span_suggestion(sp, msg, sugg, Applicability::MaybeIncorrect);
131+
}
132+
});
133+
}
134+
}
201135
}

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ mod zombie_processes;
418418
use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
419419
use clippy_utils::macros::FormatArgsStorage;
420420
use rustc_data_structures::fx::FxHashSet;
421-
use rustc_lint::{Lint, is_lint_pass_required};
421+
use rustc_lint::is_lint_pass_required;
422422
use rustc_middle::ty::TyCtxt;
423423
use utils::attr_collector::AttrStorage;
424424

clippy_utils/src/res.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ impl<'tcx, T: Copy + MaybeQPath<'tcx>> MaybeQPath<'tcx> for &Option<T> {
339339
/// A resolved path and the explicit `Self` type if there is one.
340340
type OptResPath<'tcx> = (Option<&'tcx hir::Ty<'tcx>>, Option<&'tcx Path<'tcx>>);
341341

342+
type OptTyRelPath<'tcx> = Option<(&'tcx hir::Ty<'tcx>, &'tcx PathSegment<'tcx>)>;
343+
342344
/// A HIR node which might be a `QPath::Resolved`.
343345
///
344346
/// The following are resolved paths:
@@ -354,6 +356,10 @@ pub trait MaybeResPath<'a>: Copy {
354356
/// type associated with it.
355357
fn opt_res_path(self) -> OptResPath<'a>;
356358

359+
/// If this node is a type relative path gets both the type and the final
360+
/// segments of the path.
361+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a>;
362+
357363
/// If this node is a resolved path gets it's resolution. Returns `Res::Err`
358364
/// otherwise.
359365
#[inline]
@@ -391,6 +397,11 @@ impl<'a> MaybeResPath<'a> for &'a Path<'a> {
391397
(None, Some(self))
392398
}
393399

400+
#[inline]
401+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
402+
None
403+
}
404+
394405
#[inline]
395406
fn basic_res(self) -> &'a Res {
396407
&self.res
@@ -404,6 +415,14 @@ impl<'a> MaybeResPath<'a> for &QPath<'a> {
404415
QPath::TypeRelative(..) => (None, None),
405416
}
406417
}
418+
419+
#[inline]
420+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
421+
match *self {
422+
QPath::TypeRelative(ty, seg) => Some((ty, seg)),
423+
QPath::Resolved(..) => None,
424+
}
425+
}
407426
}
408427
impl<'a> MaybeResPath<'a> for &Expr<'a> {
409428
#[inline]
@@ -413,6 +432,14 @@ impl<'a> MaybeResPath<'a> for &Expr<'a> {
413432
_ => (None, None),
414433
}
415434
}
435+
436+
#[inline]
437+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
438+
match &self.kind {
439+
ExprKind::Path(qpath) => qpath.opt_ty_rel_path(),
440+
_ => None,
441+
}
442+
}
416443
}
417444
impl<'a> MaybeResPath<'a> for &PatExpr<'a> {
418445
#[inline]
@@ -422,6 +449,14 @@ impl<'a> MaybeResPath<'a> for &PatExpr<'a> {
422449
PatExprKind::Lit { .. } => (None, None),
423450
}
424451
}
452+
453+
#[inline]
454+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
455+
match &self.kind {
456+
PatExprKind::Path(qpath) => qpath.opt_ty_rel_path(),
457+
PatExprKind::Lit { .. } => None,
458+
}
459+
}
425460
}
426461
impl<'a, AmbigArg> MaybeResPath<'a> for &hir::Ty<'a, AmbigArg> {
427462
#[inline]
@@ -431,6 +466,14 @@ impl<'a, AmbigArg> MaybeResPath<'a> for &hir::Ty<'a, AmbigArg> {
431466
_ => (None, None),
432467
}
433468
}
469+
470+
#[inline]
471+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
472+
match &self.kind {
473+
TyKind::Path(qpath) => qpath.opt_ty_rel_path(),
474+
_ => None,
475+
}
476+
}
434477
}
435478
impl<'a> MaybeResPath<'a> for &Pat<'a> {
436479
#[inline]
@@ -440,6 +483,14 @@ impl<'a> MaybeResPath<'a> for &Pat<'a> {
440483
_ => (None, None),
441484
}
442485
}
486+
487+
#[inline]
488+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
489+
match self.kind {
490+
PatKind::Expr(e) => e.opt_ty_rel_path(),
491+
_ => None,
492+
}
493+
}
443494
}
444495
impl<'a, T: MaybeResPath<'a>> MaybeResPath<'a> for Option<T> {
445496
#[inline]
@@ -450,6 +501,11 @@ impl<'a, T: MaybeResPath<'a>> MaybeResPath<'a> for Option<T> {
450501
}
451502
}
452503

504+
#[inline]
505+
fn opt_ty_rel_path(self) -> OptTyRelPath<'a> {
506+
self.and_then(T::opt_ty_rel_path)
507+
}
508+
453509
#[inline]
454510
fn basic_res(self) -> &'a Res {
455511
self.map_or(&Res::Err, T::basic_res)

0 commit comments

Comments
 (0)