Skip to content

Commit dbb10b8

Browse files
Hrishi DharamHKalbasi
authored andcommitted
add xor-swap lint
1 parent 0cce3f6 commit dbb10b8

File tree

6 files changed

+239
-8
lines changed

6 files changed

+239
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2900,6 +2900,7 @@ Released 2018-09-13
29002900
[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention
29012901
[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention
29022902
[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute
2903+
[`xor_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#xor_swap
29032904
[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
29042905
[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
29052906
[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
922922
suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
923923
swap::ALMOST_SWAPPED,
924924
swap::MANUAL_SWAP,
925+
swap::XOR_SWAP,
925926
tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
926927
temporary_assignment::TEMPORARY_ASSIGNMENT,
927928
to_digit_is_some::TO_DIGIT_IS_SOME,
@@ -1419,6 +1420,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14191420
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
14201421
LintId::of(swap::ALMOST_SWAPPED),
14211422
LintId::of(swap::MANUAL_SWAP),
1423+
LintId::of(swap::XOR_SWAP),
14221424
LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
14231425
LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
14241426
LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
@@ -1647,6 +1649,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16471649
LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
16481650
LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
16491651
LintId::of(swap::MANUAL_SWAP),
1652+
LintId::of(swap::XOR_SWAP),
16501653
LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
16511654
LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
16521655
LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),

clippy_lints/src/swap.rs

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
use clippy_utils::diagnostics::span_lint_and_then;
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::sugg::Sugg;
44
use clippy_utils::ty::is_type_diagnostic_item;
55
use clippy_utils::{differing_macro_contexts, eq_expr_value};
66
use if_chain::if_chain;
77
use rustc_errors::Applicability;
8-
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind};
8+
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
99
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::ty;
1111
use rustc_session::{declare_lint_pass, declare_tool_lint};
12+
use rustc_span::source_map::Spanned;
1213
use rustc_span::sym;
1314

1415
declare_clippy_lint! {
@@ -64,12 +65,43 @@ declare_clippy_lint! {
6465
"`foo = bar; bar = foo` sequence"
6566
}
6667

67-
declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]);
68+
declare_clippy_lint! {
69+
/// **What it does:** Checks for uses of xor-based swaps.
70+
///
71+
/// **Why is this bad?** The `std::mem::swap` function exposes the intent better
72+
/// without deinitializing or copying either variable.
73+
///
74+
/// **Known problems:** None.
75+
///
76+
/// **Example:**
77+
///
78+
/// ```rust
79+
/// let mut a = 1;
80+
/// let mut b = 2;
81+
///
82+
/// a ^= b;
83+
/// b ^= a;
84+
/// a ^= b;
85+
/// ```
86+
///
87+
/// Use std::mem::swap() instead:
88+
/// ```rust
89+
/// let mut a = 1;
90+
/// let mut b = 2;
91+
/// std::mem::swap(&mut a, &mut b);
92+
/// ```
93+
pub XOR_SWAP,
94+
complexity,
95+
"xor-based swap of two variables"
96+
}
97+
98+
declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED, XOR_SWAP]);
6899

69100
impl<'tcx> LateLintPass<'tcx> for Swap {
70101
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
71102
check_manual_swap(cx, block);
72103
check_suspicious_swap(cx, block);
104+
check_xor_swap(cx, block);
73105
}
74106
}
75107

@@ -262,3 +294,82 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
262294
}
263295
}
264296
}
297+
298+
/// Implementation of the `XOR_SWAP` lint.
299+
fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
300+
for window in block.stmts.windows(3) {
301+
if_chain! {
302+
if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]);
303+
if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]);
304+
if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]);
305+
if eq_expr_value(cx, lhs0, rhs1)
306+
&& eq_expr_value(cx, rhs1, lhs2)
307+
&& eq_expr_value(cx, rhs0, lhs1)
308+
&& eq_expr_value(cx, lhs1, rhs2);
309+
then {
310+
let span = window[0].span.to(window[2].span);
311+
let mut applicability = Applicability::MachineApplicable;
312+
match check_for_slice(cx, lhs0, rhs0) {
313+
Slice::Swappable(slice, idx0, idx1) => {
314+
if let Some(slice) = Sugg::hir_opt(cx, slice) {
315+
span_lint_and_sugg(
316+
cx,
317+
XOR_SWAP,
318+
span,
319+
&format!(
320+
"this xor-based swap of the elements of `{}` can be \
321+
more clearly expressed using `.swap`",
322+
slice
323+
),
324+
"try",
325+
format!(
326+
"{}.swap({}, {})",
327+
slice.maybe_par(),
328+
snippet_with_applicability(cx, idx0.span, "..", &mut applicability),
329+
snippet_with_applicability(cx, idx1.span, "..", &mut applicability)
330+
),
331+
applicability
332+
)
333+
}
334+
}
335+
Slice::None => {
336+
if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs0), Sugg::hir_opt(cx, rhs0)) {
337+
span_lint_and_sugg(
338+
cx,
339+
XOR_SWAP,
340+
span,
341+
&format!(
342+
"this xor-based swap of `{}` and `{}` can be \
343+
more clearly expressed using `std::mem::swap`",
344+
first, second
345+
),
346+
"try",
347+
format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
348+
applicability,
349+
);
350+
}
351+
}
352+
Slice::NotSwappable => {}
353+
}
354+
}
355+
};
356+
}
357+
}
358+
359+
/// Returns the lhs and rhs of an xor assignment statement.
360+
fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
361+
if let StmtKind::Semi(expr) = stmt.kind {
362+
if let ExprKind::AssignOp(
363+
Spanned {
364+
node: BinOpKind::BitXor,
365+
..
366+
},
367+
lhs,
368+
rhs,
369+
) = expr.kind
370+
{
371+
return Some((lhs, rhs));
372+
}
373+
}
374+
None
375+
}

tests/ui/swap.fixed

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,55 @@ fn vec() {
6060
foo.swap(0, 1);
6161
}
6262

63+
fn xor_swap_locals() {
64+
// This is an xor-based swap of local variables.
65+
let mut a = 0;
66+
let mut b = 1;
67+
std::mem::swap(&mut a, &mut b)
68+
}
69+
70+
fn xor_field_swap() {
71+
// This is an xor-based swap of fields in a struct.
72+
let mut bar = Bar { a: 0, b: 1 };
73+
std::mem::swap(&mut bar.a, &mut bar.b)
74+
}
75+
76+
fn xor_slice_swap() {
77+
// This is an xor-based swap of a slice
78+
let foo = &mut [1, 2];
79+
foo.swap(0, 1)
80+
}
81+
82+
fn xor_no_swap() {
83+
// This is a sequence of xor-assignment statements that doesn't result in a swap.
84+
let mut a = 0;
85+
let mut b = 1;
86+
let mut c = 2;
87+
a ^= b;
88+
b ^= c;
89+
a ^= c;
90+
c ^= a;
91+
}
92+
93+
fn xor_unswappable_slice() {
94+
let foo = &mut [vec![1, 2], vec![3, 4]];
95+
foo[0][1] ^= foo[1][0];
96+
foo[1][0] ^= foo[0][0];
97+
foo[0][1] ^= foo[1][0];
98+
}
99+
63100
#[rustfmt::skip]
64101
fn main() {
65102
field();
66103
array();
67104
slice();
68105
unswappable_slice();
69106
vec();
107+
xor_swap_locals();
108+
xor_field_swap();
109+
xor_slice_swap();
110+
xor_no_swap();
111+
xor_unswappable_slice();
70112

71113
let mut a = 42;
72114
let mut b = 1337;

tests/ui/swap.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,61 @@ fn vec() {
6666
foo.swap(0, 1);
6767
}
6868

69+
fn xor_swap_locals() {
70+
// This is an xor-based swap of local variables.
71+
let mut a = 0;
72+
let mut b = 1;
73+
a ^= b;
74+
b ^= a;
75+
a ^= b;
76+
}
77+
78+
fn xor_field_swap() {
79+
// This is an xor-based swap of fields in a struct.
80+
let mut bar = Bar { a: 0, b: 1 };
81+
bar.a ^= bar.b;
82+
bar.b ^= bar.a;
83+
bar.a ^= bar.b;
84+
}
85+
86+
fn xor_slice_swap() {
87+
// This is an xor-based swap of a slice
88+
let foo = &mut [1, 2];
89+
foo[0] ^= foo[1];
90+
foo[1] ^= foo[0];
91+
foo[0] ^= foo[1];
92+
}
93+
94+
fn xor_no_swap() {
95+
// This is a sequence of xor-assignment statements that doesn't result in a swap.
96+
let mut a = 0;
97+
let mut b = 1;
98+
let mut c = 2;
99+
a ^= b;
100+
b ^= c;
101+
a ^= c;
102+
c ^= a;
103+
}
104+
105+
fn xor_unswappable_slice() {
106+
let foo = &mut [vec![1, 2], vec![3, 4]];
107+
foo[0][1] ^= foo[1][0];
108+
foo[1][0] ^= foo[0][0];
109+
foo[0][1] ^= foo[1][0];
110+
}
111+
69112
#[rustfmt::skip]
70113
fn main() {
71114
field();
72115
array();
73116
slice();
74117
unswappable_slice();
75118
vec();
119+
xor_swap_locals();
120+
xor_field_swap();
121+
xor_slice_swap();
122+
xor_no_swap();
123+
xor_unswappable_slice();
76124

77125
let mut a = 42;
78126
let mut b = 1337;

tests/ui/swap.stderr

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,34 @@ LL | | foo[0] = foo[1];
2424
LL | | foo[1] = temp;
2525
| |_________________^ help: try: `foo.swap(0, 1)`
2626

27+
error: this xor-based swap of `a` and `b` can be more clearly expressed using `std::mem::swap`
28+
--> $DIR/swap.rs:73:5
29+
|
30+
LL | / a ^= b;
31+
LL | | b ^= a;
32+
LL | | a ^= b;
33+
| |___________^ help: try: `std::mem::swap(&mut a, &mut b)`
34+
|
35+
= note: `-D clippy::xor-swap` implied by `-D warnings`
36+
37+
error: this xor-based swap of `bar.a` and `bar.b` can be more clearly expressed using `std::mem::swap`
38+
--> $DIR/swap.rs:81:5
39+
|
40+
LL | / bar.a ^= bar.b;
41+
LL | | bar.b ^= bar.a;
42+
LL | | bar.a ^= bar.b;
43+
| |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)`
44+
45+
error: this xor-based swap of the elements of `foo` can be more clearly expressed using `.swap`
46+
--> $DIR/swap.rs:89:5
47+
|
48+
LL | / foo[0] ^= foo[1];
49+
LL | | foo[1] ^= foo[0];
50+
LL | | foo[0] ^= foo[1];
51+
| |_____________________^ help: try: `foo.swap(0, 1)`
52+
2753
error: this looks like you are swapping `a` and `b` manually
28-
--> $DIR/swap.rs:83:7
54+
--> $DIR/swap.rs:131:7
2955
|
3056
LL | ; let t = a;
3157
| _______^
@@ -36,7 +62,7 @@ LL | | b = t;
3662
= note: or maybe you should use `std::mem::replace`?
3763

3864
error: this looks like you are swapping `c.0` and `a` manually
39-
--> $DIR/swap.rs:92:7
65+
--> $DIR/swap.rs:140:7
4066
|
4167
LL | ; let t = c.0;
4268
| _______^
@@ -47,7 +73,7 @@ LL | | a = t;
4773
= note: or maybe you should use `std::mem::replace`?
4874

4975
error: this looks like you are trying to swap `a` and `b`
50-
--> $DIR/swap.rs:80:5
76+
--> $DIR/swap.rs:128:5
5177
|
5278
LL | / a = b;
5379
LL | | b = a;
@@ -57,13 +83,13 @@ LL | | b = a;
5783
= note: or maybe you should use `std::mem::replace`?
5884

5985
error: this looks like you are trying to swap `c.0` and `a`
60-
--> $DIR/swap.rs:89:5
86+
--> $DIR/swap.rs:137:5
6187
|
6288
LL | / c.0 = a;
6389
LL | | a = c.0;
6490
| |___________^ help: try: `std::mem::swap(&mut c.0, &mut a)`
6591
|
6692
= note: or maybe you should use `std::mem::replace`?
6793

68-
error: aborting due to 7 previous errors
94+
error: aborting due to 10 previous errors
6995

0 commit comments

Comments
 (0)