|  | 
|  | 1 | +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; | 
|  | 2 | +use clippy_utils::source::SpanRangeExt; | 
|  | 3 | +use clippy_utils::sym; | 
|  | 4 | +use rustc_ast::LitKind; | 
|  | 5 | +use rustc_errors::Applicability; | 
|  | 6 | +use rustc_hir::{Expr, ExprKind, UnOp}; | 
|  | 7 | +use rustc_lint::{LateContext, LateLintPass}; | 
|  | 8 | +use rustc_session::declare_lint_pass; | 
|  | 9 | +use std::cmp::Ordering; | 
|  | 10 | +use std::fmt; | 
|  | 11 | + | 
|  | 12 | +declare_clippy_lint! { | 
|  | 13 | +    /// ### What it does | 
|  | 14 | +    /// Checks for usage of the `offset` pointer method with an integer | 
|  | 15 | +    /// literal. | 
|  | 16 | +    /// | 
|  | 17 | +    /// ### Why is this bad? | 
|  | 18 | +    /// The `add` and `sub` methods more accurately express the intent. | 
|  | 19 | +    /// | 
|  | 20 | +    /// ### Example | 
|  | 21 | +    /// ```no_run | 
|  | 22 | +    /// let vec = vec![b'a', b'b', b'c']; | 
|  | 23 | +    /// let ptr = vec.as_ptr(); | 
|  | 24 | +    /// | 
|  | 25 | +    /// unsafe { | 
|  | 26 | +    ///     ptr.offset(-8); | 
|  | 27 | +    /// } | 
|  | 28 | +    /// ``` | 
|  | 29 | +    /// | 
|  | 30 | +    /// Could be written: | 
|  | 31 | +    /// | 
|  | 32 | +    /// ```no_run | 
|  | 33 | +    /// let vec = vec![b'a', b'b', b'c']; | 
|  | 34 | +    /// let ptr = vec.as_ptr(); | 
|  | 35 | +    /// | 
|  | 36 | +    /// unsafe { | 
|  | 37 | +    ///     ptr.sub(8); | 
|  | 38 | +    /// } | 
|  | 39 | +    /// ``` | 
|  | 40 | +    #[clippy::version = "CURRENT_RUSTC_VERSION"] | 
|  | 41 | +    pub PTR_OFFSET_BY_LITERAL, | 
|  | 42 | +    complexity, | 
|  | 43 | +    "unneeded pointer offset" | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +declare_lint_pass!(PtrOffsetByLiteral => [PTR_OFFSET_BY_LITERAL]); | 
|  | 47 | + | 
|  | 48 | +impl<'tcx> LateLintPass<'tcx> for PtrOffsetByLiteral { | 
|  | 49 | +    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | 
|  | 50 | +        // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call | 
|  | 51 | +        let Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else { | 
|  | 52 | +            return; | 
|  | 53 | +        }; | 
|  | 54 | + | 
|  | 55 | +        // Check if the argument to the method call is a (negated) literal. | 
|  | 56 | +        let Some(literal) = expr_as_literal(arg_expr) else { | 
|  | 57 | +            return; | 
|  | 58 | +        }; | 
|  | 59 | + | 
|  | 60 | +        let msg = format!("use of `{method}` with a literal"); | 
|  | 61 | +        if let Some(sugg) = build_suggestion(cx, method, receiver_expr, literal) { | 
|  | 62 | +            span_lint_and_sugg( | 
|  | 63 | +                cx, | 
|  | 64 | +                PTR_OFFSET_BY_LITERAL, | 
|  | 65 | +                expr.span, | 
|  | 66 | +                msg, | 
|  | 67 | +                "try", | 
|  | 68 | +                sugg, | 
|  | 69 | +                Applicability::MachineApplicable, | 
|  | 70 | +            ); | 
|  | 71 | +        } else { | 
|  | 72 | +            span_lint(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg); | 
|  | 73 | +        } | 
|  | 74 | +    } | 
|  | 75 | +} | 
|  | 76 | + | 
|  | 77 | +fn get_literal_bits<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<u128> { | 
|  | 78 | +    let ExprKind::Lit(lit) = expr.kind else { | 
|  | 79 | +        return None; | 
|  | 80 | +    }; | 
|  | 81 | + | 
|  | 82 | +    let LitKind::Int(packed_u128, _) = lit.node else { | 
|  | 83 | +        return None; | 
|  | 84 | +    }; | 
|  | 85 | + | 
|  | 86 | +    Some(packed_u128.get()) | 
|  | 87 | +} | 
|  | 88 | + | 
|  | 89 | +// If the given expression is a (negated) literal, return its value. | 
|  | 90 | +fn expr_as_literal<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<i128> { | 
|  | 91 | +    if let Some(literal_bits) = get_literal_bits(expr) { | 
|  | 92 | +        // The value must fit in a isize, so we can't have overflow here. | 
|  | 93 | +        return Some(literal_bits as i128); | 
|  | 94 | +    } | 
|  | 95 | + | 
|  | 96 | +    if let ExprKind::Unary(UnOp::Neg, inner) = expr.kind { | 
|  | 97 | +        if let Some(literal_bits) = get_literal_bits(inner) { | 
|  | 98 | +            return Some(-1 * literal_bits as i128); | 
|  | 99 | +        } | 
|  | 100 | +    } | 
|  | 101 | + | 
|  | 102 | +    None | 
|  | 103 | +} | 
|  | 104 | + | 
|  | 105 | +// If the given expression is a ptr::offset  or ptr::wrapping_offset method call, return the | 
|  | 106 | +// receiver, the arg of the method call, and the method. | 
|  | 107 | +fn expr_as_ptr_offset_call<'tcx>( | 
|  | 108 | +    cx: &LateContext<'tcx>, | 
|  | 109 | +    expr: &'tcx Expr<'_>, | 
|  | 110 | +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { | 
|  | 111 | +    if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind | 
|  | 112 | +        && cx.typeck_results().expr_ty(arg_0).is_raw_ptr() | 
|  | 113 | +    { | 
|  | 114 | +        if path_segment.ident.name == sym::offset { | 
|  | 115 | +            return Some((arg_0, arg_1, Method::Offset)); | 
|  | 116 | +        } | 
|  | 117 | +        if path_segment.ident.name == sym::wrapping_offset { | 
|  | 118 | +            return Some((arg_0, arg_1, Method::WrappingOffset)); | 
|  | 119 | +        } | 
|  | 120 | +    } | 
|  | 121 | +    None | 
|  | 122 | +} | 
|  | 123 | + | 
|  | 124 | +fn build_suggestion(cx: &LateContext<'_>, method: Method, receiver_expr: &Expr<'_>, literal: i128) -> Option<String> { | 
|  | 125 | +    let receiver = receiver_expr.span.get_source_text(cx)?; | 
|  | 126 | + | 
|  | 127 | +    let new_method = match Ord::cmp(&literal, &0) { | 
|  | 128 | +        Ordering::Greater => match method { | 
|  | 129 | +            Method::Offset => "add", | 
|  | 130 | +            Method::WrappingOffset => "wrapping_add", | 
|  | 131 | +        }, | 
|  | 132 | +        Ordering::Equal => return Some(format!("{receiver}")), | 
|  | 133 | +        Ordering::Less => match method { | 
|  | 134 | +            Method::Offset => "sub", | 
|  | 135 | +            Method::WrappingOffset => "wrapping_sub", | 
|  | 136 | +        }, | 
|  | 137 | +    }; | 
|  | 138 | + | 
|  | 139 | +    let literal = literal.unsigned_abs(); | 
|  | 140 | +    Some(format!("{receiver}.{new_method}({literal})")) | 
|  | 141 | +} | 
|  | 142 | + | 
|  | 143 | +#[derive(Copy, Clone)] | 
|  | 144 | +enum Method { | 
|  | 145 | +    Offset, | 
|  | 146 | +    WrappingOffset, | 
|  | 147 | +} | 
|  | 148 | + | 
|  | 149 | +impl fmt::Display for Method { | 
|  | 150 | +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | 151 | +        match self { | 
|  | 152 | +            Self::Offset => write!(f, "offset"), | 
|  | 153 | +            Self::WrappingOffset => write!(f, "wrapping_offset"), | 
|  | 154 | +        } | 
|  | 155 | +    } | 
|  | 156 | +} | 
0 commit comments