about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-08-09 13:34:42 +0000
committerbors <bors@rust-lang.org>2021-08-09 13:34:42 +0000
commit0084195dee22d498291d92603d18f2f79946671f (patch)
tree3711908560999d7f772eb88a18b098176a9dd93a
parent176df7ca9a30d97e22a3b6ea5e5c6599ef931086 (diff)
parent6d36d1a835370a01cbee58aba038ea0bd712c82b (diff)
downloadrust-0084195dee22d498291d92603d18f2f79946671f.tar.gz
rust-0084195dee22d498291d92603d18f2f79946671f.zip
Auto merge of #7506 - HKalbasi:add-xor-swap, r=camsteffen
Add xor case to `manual swap` lint

Continue of #7153
closes #6598

changelog: Add "xor swap" case to [`manual_swap`]
-rw-r--r--clippy_lints/src/swap.rs216
-rw-r--r--clippy_utils/src/lib.rs48
-rw-r--r--tests/ui/swap.fixed57
-rw-r--r--tests/ui/swap.rs61
-rw-r--r--tests/ui/swap.stderr63
5 files changed, 307 insertions, 138 deletions
diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs
index 4fa8e77a67b..f126908e84b 100644
--- a/clippy_lints/src/swap.rs
+++ b/clippy_lints/src/swap.rs
@@ -1,15 +1,16 @@
-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 clippy_utils::{can_mut_borrow_both, 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::sym;
+use rustc_span::source_map::Spanned;
+use rustc_span::{sym, Span};
 
 declare_clippy_lint! {
     /// ### What it does
@@ -70,9 +71,67 @@ 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);
     }
 }
 
+fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) {
+    let mut applicability = Applicability::MachineApplicable;
+
+    if !can_mut_borrow_both(cx, e1, e2) {
+        if let ExprKind::Index(lhs1, idx1) = e1.kind {
+            if let ExprKind::Index(lhs2, idx2) = e2.kind {
+                if eq_expr_value(cx, lhs1, lhs2) {
+                    let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
+
+                    if matches!(ty.kind(), ty::Slice(_))
+                        || matches!(ty.kind(), ty::Array(_, _))
+                        || is_type_diagnostic_item(cx, ty, sym::vec_type)
+                        || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
+                    {
+                        let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
+                        span_lint_and_sugg(
+                            cx,
+                            MANUAL_SWAP,
+                            span,
+                            &format!("this looks like you are swapping elements of `{}` manually", slice),
+                            "try",
+                            format!(
+                                "{}.swap({}, {})",
+                                slice.maybe_par(),
+                                snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
+                                snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
+                            ),
+                            applicability,
+                        );
+                    }
+                }
+            }
+        }
+        return;
+    }
+
+    let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability);
+    let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability);
+    span_lint_and_then(
+        cx,
+        MANUAL_SWAP,
+        span,
+        &format!("this looks like you are swapping `{}` and `{}` manually", first, second),
+        |diag| {
+            diag.span_suggestion(
+                span,
+                "try",
+                format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
+                applicability,
+            );
+            if !is_xor_based {
+                diag.note("or maybe you should use `std::mem::replace`?");
+            }
+        },
+    );
+}
+
 /// Implementation of the `MANUAL_SWAP` lint.
 fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
     for w in block.stmts.windows(3) {
@@ -96,121 +155,11 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
             if eq_expr_value(cx, tmp_init, lhs1);
             if eq_expr_value(cx, rhs1, lhs2);
             then {
-                if let ExprKind::Field(lhs1, _) = lhs1.kind {
-                    if let ExprKind::Field(lhs2, _) = lhs2.kind {
-                        if lhs1.hir_id.owner == lhs2.hir_id.owner {
-                            return;
-                        }
-                    }
-                }
-
-                let mut applicability = Applicability::MachineApplicable;
-
-                let slice = check_for_slice(cx, lhs1, lhs2);
-                let (replace, what, sugg) = if let Slice::NotSwappable = slice {
-                    return;
-                } else if let Slice::Swappable(slice, idx1, idx2) = slice {
-                    if let Some(slice) = Sugg::hir_opt(cx, slice) {
-                        (
-                            false,
-                            format!(" elements of `{}`", slice),
-                            format!(
-                                "{}.swap({}, {})",
-                                slice.maybe_par(),
-                                snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
-                                snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
-                            ),
-                        )
-                    } else {
-                        (false, String::new(), String::new())
-                    }
-                } else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) {
-                    (
-                        true,
-                        format!(" `{}` and `{}`", first, second),
-                        format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
-                    )
-                } else {
-                    (true, String::new(), String::new())
-                };
-
                 let span = w[0].span.to(second.span);
-
-                span_lint_and_then(
-                    cx,
-                    MANUAL_SWAP,
-                    span,
-                    &format!("this looks like you are swapping{} manually", what),
-                    |diag| {
-                        if !sugg.is_empty() {
-                            diag.span_suggestion(
-                                span,
-                                "try",
-                                sugg,
-                                applicability,
-                            );
-
-                            if replace {
-                                diag.note("or maybe you should use `std::mem::replace`?");
-                            }
-                        }
-                    }
-                );
-            }
-        }
-    }
-}
-
-enum Slice<'a> {
-    /// `slice.swap(idx1, idx2)` can be used
-    ///
-    /// ## Example
-    ///
-    /// ```rust
-    /// # let mut a = vec![0, 1];
-    /// let t = a[1];
-    /// a[1] = a[0];
-    /// a[0] = t;
-    /// // can be written as
-    /// a.swap(0, 1);
-    /// ```
-    Swappable(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>),
-    /// The `swap` function cannot be used.
-    ///
-    /// ## Example
-    ///
-    /// ```rust
-    /// # let mut a = [vec![1, 2], vec![3, 4]];
-    /// let t = a[0][1];
-    /// a[0][1] = a[1][0];
-    /// a[1][0] = t;
-    /// ```
-    NotSwappable,
-    /// Not a slice
-    None,
-}
-
-/// Checks if both expressions are index operations into "slice-like" types.
-fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> {
-    if let ExprKind::Index(lhs1, idx1) = lhs1.kind {
-        if let ExprKind::Index(lhs2, idx2) = lhs2.kind {
-            if eq_expr_value(cx, lhs1, lhs2) {
-                let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
-
-                if matches!(ty.kind(), ty::Slice(_))
-                    || matches!(ty.kind(), ty::Array(_, _))
-                    || is_type_diagnostic_item(cx, ty, sym::vec_type)
-                    || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
-                {
-                    return Slice::Swappable(lhs1, idx1, idx2);
-                }
-            } else {
-                return Slice::NotSwappable;
+                generate_swap_warning(cx, lhs1, lhs2, span, false);
             }
         }
     }
-
-    Slice::None
 }
 
 /// Implementation of the `ALMOST_SWAPPED` lint.
@@ -262,3 +211,40 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
         }
     }
 }
+
+/// Implementation of the xor case for `MANUAL_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);
+            if eq_expr_value(cx, lhs2, rhs1);
+            if eq_expr_value(cx, lhs1, rhs0);
+            if eq_expr_value(cx, lhs1, rhs2);
+            then {
+                let span = window[0].span.to(window[2].span);
+                generate_swap_warning(cx, lhs0, rhs0, span, true);
+            }
+        };
+    }
+}
+
+/// 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/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 59f878f8b20..e930338270c 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -558,6 +558,54 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
     None
 }
 
+/// This method will return tuple of projection stack and root of the expression,
+/// used in `can_mut_borrow_both`.
+///
+/// For example, if `e` represents the `v[0].a.b[x]`
+/// this method will return a tuple, composed of a `Vec`
+/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
+/// and a `Expr` for root of them, `v`
+fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
+    let mut result = vec![];
+    let root = loop {
+        match e.kind {
+            ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
+                result.push(e);
+                e = ep;
+            },
+            _ => break e,
+        };
+    };
+    result.reverse();
+    (result, root)
+}
+
+/// Checks if two expressions can be mutably borrowed simultaneously
+/// and they aren't dependent on borrowing same thing twice
+pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
+    let (s1, r1) = projection_stack(e1);
+    let (s2, r2) = projection_stack(e2);
+    if !eq_expr_value(cx, r1, r2) {
+        return true;
+    }
+    for (x1, x2) in s1.iter().zip(s2.iter()) {
+        match (&x1.kind, &x2.kind) {
+            (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
+                if i1 != i2 {
+                    return true;
+                }
+            },
+            (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
+                if !eq_expr_value(cx, i1, i2) {
+                    return false;
+                }
+            },
+            _ => return false,
+        }
+    }
+    false
+}
+
 /// Checks if the top level expression can be moved into a closure as is.
 pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
     match expr.kind {
diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed
index 0f8f839a0d5..ef518359ec5 100644
--- a/tests/ui/swap.fixed
+++ b/tests/ui/swap.fixed
@@ -6,6 +6,7 @@
     clippy::no_effect,
     clippy::redundant_clone,
     redundant_semicolons,
+    dead_code,
     unused_assignments
 )]
 
@@ -20,9 +21,7 @@ struct Bar {
 fn field() {
     let mut bar = Bar { a: 1, b: 2 };
 
-    let temp = bar.a;
-    bar.a = bar.b;
-    bar.b = temp;
+    std::mem::swap(&mut bar.a, &mut bar.b);
 
     let mut baz = vec![bar.clone(), bar.clone()];
     let temp = baz[0].a;
@@ -51,6 +50,7 @@ fn unswappable_slice() {
     foo[1][0] = temp;
 
     // swap(foo[0][1], foo[1][0]) would fail
+    // this could use split_at_mut and mem::swap, but that is not much simpler.
 }
 
 fn vec() {
@@ -60,13 +60,54 @@ 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];
+
+    // swap(foo[0][1], foo[1][0]) would fail
+    // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn distinct_slice() {
+    let foo = &mut [vec![1, 2], vec![3, 4]];
+    let bar = &mut [vec![1, 2], vec![3, 4]];
+    std::mem::swap(&mut foo[0][1], &mut bar[1][0]);
+}
+
 #[rustfmt::skip]
 fn main() {
-    field();
-    array();
-    slice();
-    unswappable_slice();
-    vec();
 
     let mut a = 42;
     let mut b = 1337;
diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs
index 5763d9e82d4..8518659ccf3 100644
--- a/tests/ui/swap.rs
+++ b/tests/ui/swap.rs
@@ -6,6 +6,7 @@
     clippy::no_effect,
     clippy::redundant_clone,
     redundant_semicolons,
+    dead_code,
     unused_assignments
 )]
 
@@ -55,6 +56,7 @@ fn unswappable_slice() {
     foo[1][0] = temp;
 
     // swap(foo[0][1], foo[1][0]) would fail
+    // this could use split_at_mut and mem::swap, but that is not much simpler.
 }
 
 fn vec() {
@@ -66,13 +68,62 @@ 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];
+
+    // swap(foo[0][1], foo[1][0]) would fail
+    // this could use split_at_mut and mem::swap, but that is not much simpler.
+}
+
+fn distinct_slice() {
+    let foo = &mut [vec![1, 2], vec![3, 4]];
+    let bar = &mut [vec![1, 2], vec![3, 4]];
+    let temp = foo[0][1];
+    foo[0][1] = bar[1][0];
+    bar[1][0] = temp;
+}
+
 #[rustfmt::skip]
 fn main() {
-    field();
-    array();
-    slice();
-    unswappable_slice();
-    vec();
 
     let mut a = 42;
     let mut b = 1337;
diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr
index f49bcfedf3a..614d16ced40 100644
--- a/tests/ui/swap.stderr
+++ b/tests/ui/swap.stderr
@@ -1,15 +1,24 @@
+error: this looks like you are swapping `bar.a` and `bar.b` manually
+  --> $DIR/swap.rs:24:5
+   |
+LL | /     let temp = bar.a;
+LL | |     bar.a = bar.b;
+LL | |     bar.b = temp;
+   | |________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)`
+   |
+   = note: `-D clippy::manual-swap` implied by `-D warnings`
+   = note: or maybe you should use `std::mem::replace`?
+
 error: this looks like you are swapping elements of `foo` manually
-  --> $DIR/swap.rs:35:5
+  --> $DIR/swap.rs:36:5
    |
 LL | /     let temp = foo[0];
 LL | |     foo[0] = foo[1];
 LL | |     foo[1] = temp;
    | |_________________^ help: try: `foo.swap(0, 1)`
-   |
-   = note: `-D clippy::manual-swap` implied by `-D warnings`
 
 error: this looks like you are swapping elements of `foo` manually
-  --> $DIR/swap.rs:44:5
+  --> $DIR/swap.rs:45:5
    |
 LL | /     let temp = foo[0];
 LL | |     foo[0] = foo[1];
@@ -17,7 +26,7 @@ LL | |     foo[1] = temp;
    | |_________________^ help: try: `foo.swap(0, 1)`
 
 error: this looks like you are swapping elements of `foo` manually
-  --> $DIR/swap.rs:62:5
+  --> $DIR/swap.rs:64:5
    |
 LL | /     let temp = foo[0];
 LL | |     foo[0] = foo[1];
@@ -25,7 +34,41 @@ LL | |     foo[1] = temp;
    | |_________________^ 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:75:5
+   |
+LL | /     a ^= b;
+LL | |     b ^= a;
+LL | |     a ^= b;
+   | |___________^ help: try: `std::mem::swap(&mut a, &mut b)`
+
+error: this looks like you are swapping `bar.a` and `bar.b` manually
+  --> $DIR/swap.rs:83: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 looks like you are swapping elements of `foo` manually
+  --> $DIR/swap.rs:91: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 `foo[0][1]` and `bar[1][0]` manually
+  --> $DIR/swap.rs:120:5
+   |
+LL | /     let temp = foo[0][1];
+LL | |     foo[0][1] = bar[1][0];
+LL | |     bar[1][0] = temp;
+   | |____________________^ help: try: `std::mem::swap(&mut foo[0][1], &mut bar[1][0])`
+   |
+   = note: or maybe you should use `std::mem::replace`?
+
+error: this looks like you are swapping `a` and `b` manually
+  --> $DIR/swap.rs:134:7
    |
 LL |       ; let t = a;
    |  _______^
@@ -36,7 +79,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:143:7
    |
 LL |       ; let t = c.0;
    |  _______^
@@ -47,7 +90,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:131:5
    |
 LL | /     a = b;
 LL | |     b = a;
@@ -57,7 +100,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:140:5
    |
 LL | /     c.0 = a;
 LL | |     a = c.0;
@@ -65,5 +108,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 12 previous errors