Skip to content

Commit 0f2eab6

Browse files
authored
Merge pull request #3104 from frewsxcv/frewsxcv-ptr-offset-with-cast
New lint: Suggest `ptr.add([usize])` over `ptr.offset([usize] as isize)`.
2 parents d99cea0 + f42442b commit 0f2eab6

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub mod panic_unimplemented;
142142
pub mod partialeq_ne_impl;
143143
pub mod precedence;
144144
pub mod ptr;
145+
pub mod ptr_offset_with_cast;
145146
pub mod question_mark;
146147
pub mod ranges;
147148
pub mod redundant_field_names;
@@ -408,6 +409,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
408409
reg.register_late_lint_pass(box default_trait_access::DefaultTraitAccess);
409410
reg.register_late_lint_pass(box indexing_slicing::IndexingSlicing);
410411
reg.register_late_lint_pass(box non_copy_const::NonCopyConst);
412+
reg.register_late_lint_pass(box ptr_offset_with_cast::Pass);
411413

412414
reg.register_lint_group("clippy_restriction", vec![
413415
arithmetic::FLOAT_ARITHMETIC,
@@ -631,6 +633,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
631633
ptr::CMP_NULL,
632634
ptr::MUT_FROM_REF,
633635
ptr::PTR_ARG,
636+
ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
634637
question_mark::QUESTION_MARK,
635638
ranges::ITERATOR_STEP_BY_ZERO,
636639
ranges::RANGE_MINUS_ONE,
@@ -815,6 +818,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
815818
overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
816819
partialeq_ne_impl::PARTIALEQ_NE_IMPL,
817820
precedence::PRECEDENCE,
821+
ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
818822
ranges::RANGE_MINUS_ONE,
819823
ranges::RANGE_PLUS_ONE,
820824
ranges::RANGE_ZIP_WITH_LEN,
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
}

tests/ui/ptr_offset_with_cast.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
fn main() {
2+
let vec = vec![b'a', b'b', b'c'];
3+
let ptr = vec.as_ptr();
4+
5+
let offset_u8 = 1_u8;
6+
let offset_usize = 1_usize;
7+
let offset_isize = 1_isize;
8+
9+
unsafe {
10+
ptr.offset(offset_usize as isize);
11+
ptr.offset(offset_isize as isize);
12+
ptr.offset(offset_u8 as isize);
13+
14+
ptr.wrapping_offset(offset_usize as isize);
15+
ptr.wrapping_offset(offset_isize as isize);
16+
ptr.wrapping_offset(offset_u8 as isize);
17+
}
18+
}

tests/ui/ptr_offset_with_cast.stderr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: use of `offset` with a `usize` casted to an `isize`
2+
--> $DIR/ptr_offset_with_cast.rs:10:9
3+
|
4+
10 | ptr.offset(offset_usize as isize);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)`
6+
|
7+
= note: `-D ptr-offset-with-cast` implied by `-D warnings`
8+
9+
error: use of `wrapping_offset` with a `usize` casted to an `isize`
10+
--> $DIR/ptr_offset_with_cast.rs:14:9
11+
|
12+
14 | ptr.wrapping_offset(offset_usize as isize);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)`
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)