summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs
blob: 4dc65da3ea1fd9fd8227823a0ce416c6c573a466 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use std::fmt;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for usage of the `offset` pointer method with a `usize` casted to an
    /// `isize`.
    ///
    /// ### Why is this bad?
    /// If we’re always increasing the pointer address, we can avoid the numeric
    /// cast by using the `add` method instead.
    ///
    /// ### Example
    /// ```rust
    /// let vec = vec![b'a', b'b', b'c'];
    /// let ptr = vec.as_ptr();
    /// let offset = 1_usize;
    ///
    /// unsafe {
    ///     ptr.offset(offset as isize);
    /// }
    /// ```
    ///
    /// Could be written:
    ///
    /// ```rust
    /// let vec = vec![b'a', b'b', b'c'];
    /// let ptr = vec.as_ptr();
    /// let offset = 1_usize;
    ///
    /// unsafe {
    ///     ptr.add(offset);
    /// }
    /// ```
    #[clippy::version = "1.30.0"]
    pub PTR_OFFSET_WITH_CAST,
    complexity,
    "unneeded pointer offset cast"
}

declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]);

impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
        // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call
        let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) {
            Some(call_arg) => call_arg,
            None => return,
        };

        // Check if the argument to the method call is a cast from usize
        let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) {
            Some(cast_lhs_expr) => cast_lhs_expr,
            None => return,
        };

        let msg = format!("use of `{}` with a `usize` casted to an `isize`", method);
        if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) {
            span_lint_and_sugg(
                cx,
                PTR_OFFSET_WITH_CAST,
                expr.span,
                &msg,
                "try",
                sugg,
                Applicability::MachineApplicable,
            );
        } else {
            span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg);
        }
    }
}

// If the given expression is a cast from a usize, return the lhs of the cast
fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
    if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind {
        if is_expr_ty_usize(cx, cast_lhs_expr) {
            return Some(cast_lhs_expr);
        }
    }
    None
}

// If the given expression is a ptr::offset  or ptr::wrapping_offset method call, return the
// receiver, the arg of the method call, and the method.
fn expr_as_ptr_offset_call<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx Expr<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
    if let ExprKind::MethodCall(path_segment, arg_0, [arg_1, ..], _) = &expr.kind {
        if is_expr_ty_raw_ptr(cx, arg_0) {
            if path_segment.ident.name == sym::offset {
                return Some((arg_0, arg_1, Method::Offset));
            }
            if path_segment.ident.name == sym!(wrapping_offset) {
                return Some((arg_0, arg_1, Method::WrappingOffset));
            }
        }
    }
    None
}

// Is the type of the expression a usize?
fn is_expr_ty_usize<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
    cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize
}

// Is the type of the expression a raw pointer?
fn is_expr_ty_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
    cx.typeck_results().expr_ty(expr).is_unsafe_ptr()
}

fn build_suggestion<'tcx>(
    cx: &LateContext<'tcx>,
    method: Method,
    receiver_expr: &Expr<'_>,
    cast_lhs_expr: &Expr<'_>,
) -> Option<String> {
    let receiver = snippet_opt(cx, receiver_expr.span)?;
    let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?;
    Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs))
}

#[derive(Copy, Clone)]
enum Method {
    Offset,
    WrappingOffset,
}

impl Method {
    #[must_use]
    fn suggestion(self) -> &'static str {
        match self {
            Self::Offset => "add",
            Self::WrappingOffset => "wrapping_add",
        }
    }
}

impl fmt::Display for Method {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Offset => write!(f, "offset"),
            Self::WrappingOffset => write!(f, "wrapping_offset"),
        }
    }
}