|
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 2 | +use clippy_utils::source::snippet_with_context; |
| 3 | +use clippy_utils::{match_def_path, path_def_id, paths}; |
| 4 | +use rustc_errors::Applicability; |
| 5 | +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp}; |
| 6 | +use rustc_lint::{LateContext, LateLintPass}; |
| 7 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 8 | +use rustc_span::{Span, SyntaxContext}; |
| 9 | + |
| 10 | +declare_clippy_lint! { |
| 11 | + /// ### What it does |
| 12 | + /// Checks for calls to `core::mem::swap` where either parameter is derived from a pointer |
| 13 | + /// |
| 14 | + /// ### Why is this bad? |
| 15 | + /// When at least one parameter to `swap` is derived from a pointer it may overlap with the |
| 16 | + /// other. This would then lead to undefined behavior. |
| 17 | + /// |
| 18 | + /// ### Example |
| 19 | + /// ```rust |
| 20 | + /// unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) { |
| 21 | + /// for (&x, &y) in x.iter().zip(y) { |
| 22 | + /// core::mem::swap(&mut *x, &mut *y); |
| 23 | + /// } |
| 24 | + /// } |
| 25 | + /// ``` |
| 26 | + /// Use instead: |
| 27 | + /// ```rust |
| 28 | + /// unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) { |
| 29 | + /// for (&x, &y) in x.iter().zip(y) { |
| 30 | + /// core::ptr::swap(x, y); |
| 31 | + /// } |
| 32 | + /// } |
| 33 | + /// ``` |
| 34 | + #[clippy::version = "1.63.0"] |
| 35 | + pub SWAP_PTR_TO_REF, |
| 36 | + suspicious, |
| 37 | + "call to `mem::swap` using pointer derived references" |
| 38 | +} |
| 39 | +declare_lint_pass!(SwapPtrToRef => [SWAP_PTR_TO_REF]); |
| 40 | + |
| 41 | +impl LateLintPass<'_> for SwapPtrToRef { |
| 42 | + fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { |
| 43 | + if let ExprKind::Call(fn_expr, [arg1, arg2]) = e.kind |
| 44 | + && let Some(fn_id) = path_def_id(cx, fn_expr) |
| 45 | + && match_def_path(cx, fn_id, &paths::MEM_SWAP) |
| 46 | + && let ctxt = e.span.ctxt() |
| 47 | + && let (from_ptr1, arg1_span) = is_ptr_to_ref(cx, arg1, ctxt) |
| 48 | + && let (from_ptr2, arg2_span) = is_ptr_to_ref(cx, arg2, ctxt) |
| 49 | + && (from_ptr1 || from_ptr2) |
| 50 | + { |
| 51 | + span_lint_and_then( |
| 52 | + cx, |
| 53 | + SWAP_PTR_TO_REF, |
| 54 | + e.span, |
| 55 | + "call to `core::mem::swap` with a parameter derived from a raw pointer", |
| 56 | + |diag| { |
| 57 | + if !((from_ptr1 && arg1_span.is_none()) || (from_ptr2 && arg2_span.is_none())) { |
| 58 | + let mut app = Applicability::MachineApplicable; |
| 59 | + let snip1 = snippet_with_context(cx, arg1_span.unwrap_or(arg1.span), ctxt, "..", &mut app).0; |
| 60 | + let snip2 = snippet_with_context(cx, arg2_span.unwrap_or(arg2.span), ctxt, "..", &mut app).0; |
| 61 | + diag.span_suggestion(e.span, "use ptr::swap", format!("core::ptr::swap({}, {})", snip1, snip2), app); |
| 62 | + } |
| 63 | + } |
| 64 | + ); |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +/// Checks if the expression converts a mutable pointer to a mutable reference. If it is, also |
| 70 | +/// returns the span of the pointer expression if it's suitable for making a suggestion. |
| 71 | +fn is_ptr_to_ref(cx: &LateContext<'_>, e: &Expr<'_>, ctxt: SyntaxContext) -> (bool, Option<Span>) { |
| 72 | + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, borrowed_expr) = e.kind |
| 73 | + && let ExprKind::Unary(UnOp::Deref, derefed_expr) = borrowed_expr.kind |
| 74 | + && cx.typeck_results().expr_ty(derefed_expr).is_unsafe_ptr() |
| 75 | + { |
| 76 | + (true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then(|| derefed_expr.span)) |
| 77 | + } else { |
| 78 | + (false, None) |
| 79 | + } |
| 80 | +} |
0 commit comments