]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/ptr_offset_with_cast.rs
Merge branch 'master' into fix-3739
[rust.git] / clippy_lints / src / ptr_offset_with_cast.rs
1 use crate::utils;
2 use rustc::{declare_tool_lint, hir, lint, lint_array};
3 use rustc_errors::Applicability;
4 use std::fmt;
5
6 declare_clippy_lint! {
7     /// **What it does:** Checks for usage of the `offset` pointer method with a `usize` casted to an
8     /// `isize`.
9     ///
10     /// **Why is this bad?** If we’re always increasing the pointer address, we can avoid the numeric
11     /// cast by using the `add` method instead.
12     ///
13     /// **Known problems:** None
14     ///
15     /// **Example:**
16     /// ```rust
17     /// let vec = vec![b'a', b'b', b'c'];
18     /// let ptr = vec.as_ptr();
19     /// let offset = 1_usize;
20     ///
21     /// unsafe {
22     ///     ptr.offset(offset as isize);
23     /// }
24     /// ```
25     ///
26     /// Could be written:
27     ///
28     /// ```rust
29     /// let vec = vec![b'a', b'b', b'c'];
30     /// let ptr = vec.as_ptr();
31     /// let offset = 1_usize;
32     ///
33     /// unsafe {
34     ///     ptr.add(offset);
35     /// }
36     /// ```
37     pub PTR_OFFSET_WITH_CAST,
38     complexity,
39     "unneeded pointer offset cast"
40 }
41
42 #[derive(Copy, Clone, Debug)]
43 pub struct Pass;
44
45 impl lint::LintPass for Pass {
46     fn get_lints(&self) -> lint::LintArray {
47         lint_array!(PTR_OFFSET_WITH_CAST)
48     }
49
50     fn name(&self) -> &'static str {
51         "PtrOffsetWithCast"
52     }
53 }
54
55 impl<'a, 'tcx> lint::LateLintPass<'a, 'tcx> for Pass {
56     fn check_expr(&mut self, cx: &lint::LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
57         // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call
58         let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) {
59             Some(call_arg) => call_arg,
60             None => return,
61         };
62
63         // Check if the argument to the method call is a cast from usize
64         let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) {
65             Some(cast_lhs_expr) => cast_lhs_expr,
66             None => return,
67         };
68
69         let msg = format!("use of `{}` with a `usize` casted to an `isize`", method);
70         if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) {
71             utils::span_lint_and_sugg(
72                 cx,
73                 PTR_OFFSET_WITH_CAST,
74                 expr.span,
75                 &msg,
76                 "try",
77                 sugg,
78                 Applicability::MachineApplicable,
79             );
80         } else {
81             utils::span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg);
82         }
83     }
84 }
85
86 // If the given expression is a cast from a usize, return the lhs of the cast
87 fn expr_as_cast_from_usize<'a, 'tcx>(
88     cx: &lint::LateContext<'a, 'tcx>,
89     expr: &'tcx hir::Expr,
90 ) -> Option<&'tcx hir::Expr> {
91     if let hir::ExprKind::Cast(ref cast_lhs_expr, _) = expr.node {
92         if is_expr_ty_usize(cx, &cast_lhs_expr) {
93             return Some(cast_lhs_expr);
94         }
95     }
96     None
97 }
98
99 // If the given expression is a ptr::offset  or ptr::wrapping_offset method call, return the
100 // receiver, the arg of the method call, and the method.
101 fn expr_as_ptr_offset_call<'a, 'tcx>(
102     cx: &lint::LateContext<'a, 'tcx>,
103     expr: &'tcx hir::Expr,
104 ) -> Option<(&'tcx hir::Expr, &'tcx hir::Expr, Method)> {
105     if let hir::ExprKind::MethodCall(ref path_segment, _, ref args) = expr.node {
106         if is_expr_ty_raw_ptr(cx, &args[0]) {
107             if path_segment.ident.name == "offset" {
108                 return Some((&args[0], &args[1], Method::Offset));
109             }
110             if path_segment.ident.name == "wrapping_offset" {
111                 return Some((&args[0], &args[1], Method::WrappingOffset));
112             }
113         }
114     }
115     None
116 }
117
118 // Is the type of the expression a usize?
119 fn is_expr_ty_usize<'a, 'tcx>(cx: &lint::LateContext<'a, 'tcx>, expr: &hir::Expr) -> bool {
120     cx.tables.expr_ty(expr) == cx.tcx.types.usize
121 }
122
123 // Is the type of the expression a raw pointer?
124 fn is_expr_ty_raw_ptr<'a, 'tcx>(cx: &lint::LateContext<'a, 'tcx>, expr: &hir::Expr) -> bool {
125     cx.tables.expr_ty(expr).is_unsafe_ptr()
126 }
127
128 fn build_suggestion<'a, 'tcx>(
129     cx: &lint::LateContext<'a, 'tcx>,
130     method: Method,
131     receiver_expr: &hir::Expr,
132     cast_lhs_expr: &hir::Expr,
133 ) -> Option<String> {
134     let receiver = utils::snippet_opt(cx, receiver_expr.span)?;
135     let cast_lhs = utils::snippet_opt(cx, cast_lhs_expr.span)?;
136     Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs))
137 }
138
139 #[derive(Copy, Clone)]
140 enum Method {
141     Offset,
142     WrappingOffset,
143 }
144
145 impl Method {
146     fn suggestion(self) -> &'static str {
147         match self {
148             Method::Offset => "add",
149             Method::WrappingOffset => "wrapping_add",
150         }
151     }
152 }
153
154 impl fmt::Display for Method {
155     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156         match self {
157             Method::Offset => write!(f, "offset"),
158             Method::WrappingOffset => write!(f, "wrapping_offset"),
159         }
160     }
161 }