Skip to content

Commit 901d95d

Browse files
committed
Add new lint manual_memmove
1 parent 788a8bc commit 901d95d

File tree

9 files changed

+414
-23
lines changed

9 files changed

+414
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,6 +3078,7 @@ Released 2018-09-13
30783078
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
30793079
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
30803080
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
3081+
[`manual_memmove`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memmove
30813082
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
30823083
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
30833084
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
104104
LintId::of(loops::ITER_NEXT_LOOP),
105105
LintId::of(loops::MANUAL_FLATTEN),
106106
LintId::of(loops::MANUAL_MEMCPY),
107+
LintId::of(loops::MANUAL_MEMMOVE),
107108
LintId::of(loops::MUT_RANGE_BOUND),
108109
LintId::of(loops::NEEDLESS_COLLECT),
109110
LintId::of(loops::NEEDLESS_RANGE_LOOP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ store.register_lints(&[
213213
loops::ITER_NEXT_LOOP,
214214
loops::MANUAL_FLATTEN,
215215
loops::MANUAL_MEMCPY,
216+
loops::MANUAL_MEMMOVE,
216217
loops::MUT_RANGE_BOUND,
217218
loops::NEEDLESS_COLLECT,
218219
loops::NEEDLESS_RANGE_LOOP,

clippy_lints/src/lib.register_perf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
1010
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
1111
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
1212
LintId::of(loops::MANUAL_MEMCPY),
13+
LintId::of(loops::MANUAL_MEMMOVE),
1314
LintId::of(loops::NEEDLESS_COLLECT),
1415
LintId::of(methods::EXPECT_FUN_CALL),
1516
LintId::of(methods::EXTEND_WITH_DRAIN),

clippy_lints/src/loops/manual_memcpy.rs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ fn build_manual_memcpy_suggestion<'tcx>(
208208
/// and also, it avoids subtracting a variable from the same one by replacing it with `0`.
209209
/// it exists for the convenience of the overloaded operators while normal functions can do the
210210
/// same.
211-
#[derive(Clone)]
212-
struct MinifyingSugg<'a>(Sugg<'a>);
211+
#[derive(Clone, PartialEq)]
212+
pub(super) struct MinifyingSugg<'a>(Sugg<'a>);
213213

214214
impl<'a> Display for MinifyingSugg<'a> {
215215
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -218,7 +218,7 @@ impl<'a> Display for MinifyingSugg<'a> {
218218
}
219219

220220
impl<'a> MinifyingSugg<'a> {
221-
fn into_sugg(self) -> Sugg<'a> {
221+
pub fn into_sugg(self) -> Sugg<'a> {
222222
self.0
223223
}
224224
}
@@ -277,13 +277,14 @@ impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
277277

278278
/// a wrapper around `MinifyingSugg`, which carries an operator like currying
279279
/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`).
280-
struct Offset {
281-
value: MinifyingSugg<'static>,
282-
sign: OffsetSign,
280+
#[derive(PartialEq)]
281+
pub(super) struct Offset {
282+
pub value: MinifyingSugg<'static>,
283+
pub sign: OffsetSign,
283284
}
284285

285-
#[derive(Clone, Copy)]
286-
enum OffsetSign {
286+
#[derive(Clone, Copy, PartialEq)]
287+
pub(super) enum OffsetSign {
287288
Positive,
288289
Negative,
289290
}
@@ -308,31 +309,31 @@ impl Offset {
308309
}
309310
}
310311

311-
fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> {
312+
pub(super) fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> {
312313
match rhs.sign {
313314
OffsetSign::Positive => lhs + &rhs.value,
314315
OffsetSign::Negative => lhs - &rhs.value,
315316
}
316317
}
317318

318319
#[derive(Debug, Clone, Copy)]
319-
enum StartKind<'hir> {
320+
pub(super) enum StartKind<'hir> {
320321
Range,
321322
Counter { initializer: &'hir Expr<'hir> },
322323
}
323324

324-
struct IndexExpr<'hir> {
325-
base: &'hir Expr<'hir>,
326-
idx: StartKind<'hir>,
327-
idx_offset: Offset,
325+
pub(super) struct IndexExpr<'hir> {
326+
pub base: &'hir Expr<'hir>,
327+
pub idx: StartKind<'hir>,
328+
pub idx_offset: Offset,
328329
}
329330

330-
struct Start<'hir> {
331-
id: HirId,
332-
kind: StartKind<'hir>,
331+
pub(super) struct Start<'hir> {
332+
pub id: HirId,
333+
pub kind: StartKind<'hir>,
333334
}
334335

335-
fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
336+
pub(super) fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
336337
match ty.kind() {
337338
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did) => Some(subs.type_at(0)),
338339
ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, subty),
@@ -351,7 +352,7 @@ fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
351352
}
352353
}
353354

354-
fn get_details_from_idx<'tcx>(
355+
pub(super) fn get_details_from_idx<'tcx>(
355356
cx: &LateContext<'tcx>,
356357
idx: &Expr<'_>,
357358
starts: &[Start<'tcx>],
@@ -391,7 +392,7 @@ fn get_details_from_idx<'tcx>(
391392
}
392393
}
393394

394-
fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
395+
pub(super) fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
395396
if let ExprKind::Assign(lhs, rhs, _) = e.kind {
396397
Some((lhs, rhs))
397398
} else {
@@ -403,7 +404,7 @@ fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx
403404
/// The returned iterator yields `None` if no assignment expressions are there,
404405
/// filtering out the increments of the given whitelisted loop counters;
405406
/// because its job is to make sure there's nothing other than assignments and the increments.
406-
fn get_assignments<'a, 'tcx>(
407+
pub(super) fn get_assignments<'a, 'tcx>(
407408
Block { stmts, expr, .. }: &'tcx Block<'tcx>,
408409
loop_counters: &'a [Start<'tcx>],
409410
) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'a {
@@ -433,7 +434,7 @@ fn get_assignments<'a, 'tcx>(
433434
.map(get_assignment)
434435
}
435436

436-
fn get_loop_counters<'a, 'tcx>(
437+
pub(super) fn get_loop_counters<'a, 'tcx>(
437438
cx: &'a LateContext<'tcx>,
438439
body: &'tcx Block<'tcx>,
439440
expr: &'tcx Expr<'_>,
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use super::MANUAL_MEMCPY;
2+
use crate::loops::manual_memcpy::{
3+
apply_offset, get_assignment, get_assignments, get_details_from_idx, get_loop_counters, get_slice_like_element_ty,
4+
IndexExpr, MinifyingSugg, Start, StartKind,
5+
};
6+
use clippy_utils::diagnostics::span_lint_and_sugg;
7+
use clippy_utils::source::snippet;
8+
use clippy_utils::sugg::Sugg;
9+
use clippy_utils::ty::is_copy;
10+
use clippy_utils::{higher, path_to_local, sugg};
11+
use if_chain::if_chain;
12+
use rustc_ast::ast;
13+
use rustc_errors::Applicability;
14+
use rustc_hir::{BinOpKind, Expr, ExprKind, Pat, PatKind};
15+
use rustc_lint::LateContext;
16+
use rustc_middle::ty;
17+
use rustc_span::symbol::sym;
18+
use std::iter::Iterator;
19+
20+
/// Checks for for loops that sequentially copy items within a slice
21+
pub(super) fn check<'tcx>(
22+
cx: &LateContext<'tcx>,
23+
pat: &'tcx Pat<'_>,
24+
arg: &'tcx Expr<'_>,
25+
body: &'tcx Expr<'_>,
26+
expr: &'tcx Expr<'_>,
27+
) -> bool {
28+
if let Some(higher::Range {
29+
start: Some(start),
30+
end: Some(end),
31+
limits,
32+
}) = higher::Range::hir(arg)
33+
{
34+
// the var must be a single name
35+
if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
36+
let starts = &[Start {
37+
id: canonical_id,
38+
kind: StartKind::Range,
39+
}];
40+
41+
// This is one of few ways to return different iterators
42+
// derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
43+
let mut iter_a = None;
44+
let mut iter_b = None;
45+
46+
if let ExprKind::Block(block, _) = body.kind {
47+
if let Some(mut loop_counters) = get_loop_counters(cx, block, expr) {
48+
// we currently do not support loop counters at all, as we would need to know
49+
// their initial value to assess whether the copy is safe to do (same reason we
50+
// require positive offsets in source)
51+
if loop_counters.next().is_some() {
52+
return false;
53+
}
54+
}
55+
iter_a = Some(get_assignments(block, starts));
56+
} else {
57+
iter_b = Some(get_assignment(body));
58+
}
59+
60+
let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter());
61+
62+
let big_sugg = assignments
63+
// The only statements in the for loops can be indexed assignments from
64+
// indexed retrievals (except increments of loop counters).
65+
.map(|o| {
66+
o.and_then(|(lhs, rhs)| {
67+
if_chain! {
68+
if let ExprKind::Index(base_left, idx_left) = lhs.kind;
69+
if let ExprKind::Index(base_right, idx_right) = rhs.kind;
70+
// Source and destination must be same
71+
if let Some(base_left_local) = path_to_local(base_left);
72+
if let Some(base_right_local) = path_to_local(base_right);
73+
if base_left_local == base_right_local;
74+
if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left));
75+
if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, starts);
76+
if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, starts);
77+
78+
if left_is_smaller_than_right(cx, idx_left, idx_right);
79+
80+
if let StartKind::Range = start_left;
81+
if let StartKind::Range = start_right;
82+
83+
if is_copy(cx, ty);
84+
85+
then {
86+
Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left },
87+
IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }))
88+
} else {
89+
None
90+
}
91+
}
92+
})
93+
})
94+
.map(|o| o.map(|(dst, src)| build_manual_memmove_suggestion(cx, start, end, limits, &dst, &src)))
95+
.collect::<Option<Vec<_>>>()
96+
.filter(|v| {
97+
// we currently do not support more than one assignment, as it's "too hard" to
98+
// prove that the to-be-moved slices (+ their destinations) are nonoverlapping
99+
v.len() == 1
100+
})
101+
.map(|v| v.join("\n "));
102+
103+
if let Some(big_sugg) = big_sugg {
104+
span_lint_and_sugg(
105+
cx,
106+
MANUAL_MEMCPY,
107+
expr.span,
108+
"it looks like you're manually copying within a slice",
109+
"try replacing the loop by",
110+
big_sugg,
111+
Applicability::Unspecified,
112+
);
113+
return true;
114+
}
115+
}
116+
}
117+
false
118+
}
119+
120+
fn build_manual_memmove_suggestion<'tcx>(
121+
cx: &LateContext<'tcx>,
122+
start: &Expr<'_>,
123+
end: &Expr<'_>,
124+
limits: ast::RangeLimits,
125+
src: &IndexExpr<'_>,
126+
dst: &IndexExpr<'_>,
127+
) -> String {
128+
fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> {
129+
if offset.to_string() == "0" {
130+
sugg::EMPTY.into()
131+
} else {
132+
offset
133+
}
134+
}
135+
136+
let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
137+
if_chain! {
138+
if let ExprKind::MethodCall(method, _, len_args, _) = end.kind;
139+
if method.ident.name == sym::len;
140+
if len_args.len() == 1;
141+
if let Some(arg) = len_args.get(0);
142+
if path_to_local(arg) == path_to_local(base);
143+
then {
144+
if sugg.to_string() == end_str {
145+
sugg::EMPTY.into()
146+
} else {
147+
sugg
148+
}
149+
} else {
150+
match limits {
151+
ast::RangeLimits::Closed => {
152+
sugg + &sugg::ONE.into()
153+
},
154+
ast::RangeLimits::HalfOpen => sugg,
155+
}
156+
}
157+
}
158+
};
159+
160+
let start_str = Sugg::hir(cx, start, "").into();
161+
let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into();
162+
163+
let (src_offset, src_limit) = match src.idx {
164+
StartKind::Range => (
165+
print_offset(apply_offset(
166+
&apply_offset(&start_str, &src.idx_offset),
167+
&dst.idx_offset,
168+
))
169+
.into_sugg(),
170+
print_limit(
171+
end,
172+
end_str.to_string().as_str(),
173+
src.base,
174+
apply_offset(&apply_offset(&end_str, &src.idx_offset), &dst.idx_offset),
175+
)
176+
.into_sugg(),
177+
),
178+
StartKind::Counter { initializer } => {
179+
let counter_start = Sugg::hir(cx, initializer, "").into();
180+
(
181+
print_offset(apply_offset(
182+
&apply_offset(&counter_start, &src.idx_offset),
183+
&dst.idx_offset,
184+
))
185+
.into_sugg(),
186+
print_limit(
187+
end,
188+
end_str.to_string().as_str(),
189+
src.base,
190+
apply_offset(&apply_offset(&end_str, &src.idx_offset), &dst.idx_offset) + &counter_start
191+
- &start_str,
192+
)
193+
.into_sugg(),
194+
)
195+
},
196+
};
197+
198+
let src_base_str = snippet(cx, src.base.span, "???");
199+
200+
format!(
201+
"{}.copy_within({}..{}, {});",
202+
src_base_str,
203+
src_offset.maybe_par(),
204+
src_limit.maybe_par(),
205+
start_str,
206+
)
207+
}
208+
209+
fn left_is_smaller_than_right<'tcx>(
210+
cx: &LateContext<'tcx>,
211+
idx_left: &'tcx Expr<'_>,
212+
idx_right: &'tcx Expr<'_>,
213+
) -> bool {
214+
// in order to be a memmove-type loop, read indices must be iterated
215+
// over at a later point than written indices. we currently enforce
216+
// this by ensuring that:
217+
//
218+
// - rhs is `path + offset` with offset >= 0
219+
// - lhs is just `path` (same path as rhs)
220+
if_chain! {
221+
if let ExprKind::Binary(idx_right_op, idx_right_lhs, idx_right_rhs) = idx_right.kind;
222+
if idx_right_op.node == BinOpKind::Add;
223+
if let Some(idx_left_local) = path_to_local(idx_left);
224+
if let Some(idx_right_local) = path_to_local(idx_right_lhs);
225+
if idx_right_local == idx_left_local;
226+
if let ty::Uint(_) = cx.typeck_results().expr_ty(idx_right_rhs).kind();
227+
then {
228+
true
229+
} else {
230+
false
231+
}
232+
}
233+
}

0 commit comments

Comments
 (0)