diff options
| author | Hrishi Dharam <hdharam@berkeley.edu> | 2021-05-02 12:42:28 -0400 |
|---|---|---|
| committer | hamidreza kalbasi <hamidrezakalbasi@protonmail.com> | 2021-07-29 15:25:08 +0430 |
| commit | dbb10b87f8e4365298ed33a6ed96bffe66a1951e (patch) | |
| tree | 78516da3a3be2f1e8311acbb2c642e36e96c5dd6 | |
| parent | 0cce3f643bfcbb92d5a1bb71858c9cbaff749d6b (diff) | |
| download | rust-dbb10b87f8e4365298ed33a6ed96bffe66a1951e.tar.gz rust-dbb10b87f8e4365298ed33a6ed96bffe66a1951e.zip | |
add xor-swap lint
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | clippy_lints/src/lib.rs | 3 | ||||
| -rw-r--r-- | clippy_lints/src/swap.rs | 117 | ||||
| -rw-r--r-- | tests/ui/swap.fixed | 42 | ||||
| -rw-r--r-- | tests/ui/swap.rs | 48 | ||||
| -rw-r--r-- | tests/ui/swap.stderr | 36 |
6 files changed, 239 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index acbefc8064d..601fa83ad89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2900,6 +2900,7 @@ Released 2018-09-13 [`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention [`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention [`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute +[`xor_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#xor_swap [`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero [`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal [`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index f49b382c5ea..c4197bab82a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -922,6 +922,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL, swap::ALMOST_SWAPPED, swap::MANUAL_SWAP, + swap::XOR_SWAP, tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, temporary_assignment::TEMPORARY_ASSIGNMENT, to_digit_is_some::TO_DIGIT_IS_SOME, @@ -1419,6 +1420,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), LintId::of(swap::ALMOST_SWAPPED), LintId::of(swap::MANUAL_SWAP), + LintId::of(swap::XOR_SWAP), LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT), 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: LintId::of(strings::STRING_FROM_UTF8_AS_BYTES), LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS), LintId::of(swap::MANUAL_SWAP), + LintId::of(swap::XOR_SWAP), LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(transmute::CROSSPOINTER_TRANSMUTE), LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS), diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 4fa8e77a67b..6dceec65574 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -1,14 +1,15 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{differing_macro_contexts, eq_expr_value}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; use rustc_span::sym; declare_clippy_lint! { @@ -64,12 +65,43 @@ declare_clippy_lint! { "`foo = bar; bar = foo` sequence" } -declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]); +declare_clippy_lint! { + /// **What it does:** Checks for uses of xor-based swaps. + /// + /// **Why is this bad?** The `std::mem::swap` function exposes the intent better + /// without deinitializing or copying either variable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let mut a = 1; + /// let mut b = 2; + /// + /// a ^= b; + /// b ^= a; + /// a ^= b; + /// ``` + /// + /// Use std::mem::swap() instead: + /// ```rust + /// let mut a = 1; + /// let mut b = 2; + /// std::mem::swap(&mut a, &mut b); + /// ``` + pub XOR_SWAP, + complexity, + "xor-based swap of two variables" +} + +declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED, XOR_SWAP]); impl<'tcx> LateLintPass<'tcx> for Swap { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { check_manual_swap(cx, block); check_suspicious_swap(cx, block); + check_xor_swap(cx, block); } } @@ -262,3 +294,82 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { } } } + +/// Implementation of the `XOR_SWAP` lint. +fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { + for window in block.stmts.windows(3) { + if_chain! { + if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]); + if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]); + if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]); + if eq_expr_value(cx, lhs0, rhs1) + && eq_expr_value(cx, rhs1, lhs2) + && eq_expr_value(cx, rhs0, lhs1) + && eq_expr_value(cx, lhs1, rhs2); + then { + let span = window[0].span.to(window[2].span); + let mut applicability = Applicability::MachineApplicable; + match check_for_slice(cx, lhs0, rhs0) { + Slice::Swappable(slice, idx0, idx1) => { + if let Some(slice) = Sugg::hir_opt(cx, slice) { + span_lint_and_sugg( + cx, + XOR_SWAP, + span, + &format!( + "this xor-based swap of the elements of `{}` can be \ + more clearly expressed using `.swap`", + slice + ), + "try", + format!( + "{}.swap({}, {})", + slice.maybe_par(), + snippet_with_applicability(cx, idx0.span, "..", &mut applicability), + snippet_with_applicability(cx, idx1.span, "..", &mut applicability) + ), + applicability + ) + } + } + Slice::None => { + if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs0), Sugg::hir_opt(cx, rhs0)) { + span_lint_and_sugg( + cx, + XOR_SWAP, + span, + &format!( + "this xor-based swap of `{}` and `{}` can be \ + more clearly expressed using `std::mem::swap`", + first, second + ), + "try", + format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), + applicability, + ); + } + } + Slice::NotSwappable => {} + } + } + }; + } +} + +/// Returns the lhs and rhs of an xor assignment statement. +fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> { + if let StmtKind::Semi(expr) = stmt.kind { + if let ExprKind::AssignOp( + Spanned { + node: BinOpKind::BitXor, + .. + }, + lhs, + rhs, + ) = expr.kind + { + return Some((lhs, rhs)); + } + } + None +} diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index 0f8f839a0d5..e0d412b2198 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -60,6 +60,43 @@ fn vec() { foo.swap(0, 1); } +fn xor_swap_locals() { + // This is an xor-based swap of local variables. + let mut a = 0; + let mut b = 1; + std::mem::swap(&mut a, &mut b) +} + +fn xor_field_swap() { + // This is an xor-based swap of fields in a struct. + let mut bar = Bar { a: 0, b: 1 }; + std::mem::swap(&mut bar.a, &mut bar.b) +} + +fn xor_slice_swap() { + // This is an xor-based swap of a slice + let foo = &mut [1, 2]; + foo.swap(0, 1) +} + +fn xor_no_swap() { + // This is a sequence of xor-assignment statements that doesn't result in a swap. + let mut a = 0; + let mut b = 1; + let mut c = 2; + a ^= b; + b ^= c; + a ^= c; + c ^= a; +} + +fn xor_unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + foo[0][1] ^= foo[1][0]; + foo[1][0] ^= foo[0][0]; + foo[0][1] ^= foo[1][0]; +} + #[rustfmt::skip] fn main() { field(); @@ -67,6 +104,11 @@ fn main() { slice(); unswappable_slice(); vec(); + xor_swap_locals(); + xor_field_swap(); + xor_slice_swap(); + xor_no_swap(); + xor_unswappable_slice(); let mut a = 42; let mut b = 1337; diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs index 5763d9e82d4..6f8fd6e4c91 100644 --- a/tests/ui/swap.rs +++ b/tests/ui/swap.rs @@ -66,6 +66,49 @@ fn vec() { foo.swap(0, 1); } +fn xor_swap_locals() { + // This is an xor-based swap of local variables. + let mut a = 0; + let mut b = 1; + a ^= b; + b ^= a; + a ^= b; +} + +fn xor_field_swap() { + // This is an xor-based swap of fields in a struct. + let mut bar = Bar { a: 0, b: 1 }; + bar.a ^= bar.b; + bar.b ^= bar.a; + bar.a ^= bar.b; +} + +fn xor_slice_swap() { + // This is an xor-based swap of a slice + let foo = &mut [1, 2]; + foo[0] ^= foo[1]; + foo[1] ^= foo[0]; + foo[0] ^= foo[1]; +} + +fn xor_no_swap() { + // This is a sequence of xor-assignment statements that doesn't result in a swap. + let mut a = 0; + let mut b = 1; + let mut c = 2; + a ^= b; + b ^= c; + a ^= c; + c ^= a; +} + +fn xor_unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + foo[0][1] ^= foo[1][0]; + foo[1][0] ^= foo[0][0]; + foo[0][1] ^= foo[1][0]; +} + #[rustfmt::skip] fn main() { field(); @@ -73,6 +116,11 @@ fn main() { slice(); unswappable_slice(); vec(); + xor_swap_locals(); + xor_field_swap(); + xor_slice_swap(); + xor_no_swap(); + xor_unswappable_slice(); let mut a = 42; let mut b = 1337; diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index f49bcfedf3a..55b470821e2 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -24,8 +24,34 @@ LL | | foo[0] = foo[1]; LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` +error: this xor-based swap of `a` and `b` can be more clearly expressed using `std::mem::swap` + --> $DIR/swap.rs:73:5 + | +LL | / a ^= b; +LL | | b ^= a; +LL | | a ^= b; + | |___________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: `-D clippy::xor-swap` implied by `-D warnings` + +error: this xor-based swap of `bar.a` and `bar.b` can be more clearly expressed using `std::mem::swap` + --> $DIR/swap.rs:81:5 + | +LL | / bar.a ^= bar.b; +LL | | bar.b ^= bar.a; +LL | | bar.a ^= bar.b; + | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)` + +error: this xor-based swap of the elements of `foo` can be more clearly expressed using `.swap` + --> $DIR/swap.rs:89:5 + | +LL | / foo[0] ^= foo[1]; +LL | | foo[1] ^= foo[0]; +LL | | foo[0] ^= foo[1]; + | |_____________________^ help: try: `foo.swap(0, 1)` + error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:83:7 + --> $DIR/swap.rs:131:7 | LL | ; let t = a; | _______^ @@ -36,7 +62,7 @@ LL | | b = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `c.0` and `a` manually - --> $DIR/swap.rs:92:7 + --> $DIR/swap.rs:140:7 | LL | ; let t = c.0; | _______^ @@ -47,7 +73,7 @@ LL | | a = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:80:5 + --> $DIR/swap.rs:128:5 | LL | / a = b; LL | | b = a; @@ -57,7 +83,7 @@ LL | | b = a; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `c.0` and `a` - --> $DIR/swap.rs:89:5 + --> $DIR/swap.rs:137:5 | LL | / c.0 = a; LL | | a = c.0; @@ -65,5 +91,5 @@ LL | | a = c.0; | = note: or maybe you should use `std::mem::replace`? -error: aborting due to 7 previous errors +error: aborting due to 10 previous errors |
