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