Skip to content

Commit 4247354

Browse files
committed
add new lint as_underscore_ptr to check for as *{const,mut} _
1 parent 6589d79 commit 4247354

18 files changed

+396
-19
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4392,6 +4392,7 @@ Released 2018-09-13
43924392
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
43934393
[`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut
43944394
[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
4395+
[`as_underscore_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore_ptr
43954396
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
43964397
[`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states
43974398
[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::ty::is_ptr_like;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, MutTy, Ty, TyKind};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{self, TypeAndMut};
7+
8+
use super::AS_UNDERSCORE_PTR;
9+
10+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, mut target_ty: &Ty<'_>) {
11+
if let TyKind::Ptr(MutTy { mutbl, .. }) = target_ty.kind {
12+
// get this before stripping the pointers, so the suggestion suggests replacing the whole type
13+
let ty_span = target_ty.span;
14+
15+
// strip all pointers from the type
16+
while let TyKind::Ptr(MutTy { ty: new_ty, .. }) = target_ty.kind {
17+
target_ty = new_ty;
18+
}
19+
20+
if matches!(target_ty.kind, TyKind::Infer) {
21+
let mutbl_str = match mutbl {
22+
rustc_ast::Mutability::Not => "const",
23+
rustc_ast::Mutability::Mut => "mut",
24+
};
25+
span_lint_and_then(
26+
cx,
27+
AS_UNDERSCORE_PTR,
28+
expr.span,
29+
format!("using `as *{mutbl_str} _` conversion").as_str(),
30+
|diag| {
31+
let ty_resolved = cx.typeck_results().expr_ty(expr);
32+
if let ty::Error(_) = ty_resolved.kind() {
33+
diag.help("consider giving the type explicitly");
34+
} else {
35+
// strip the first pointer of the resolved type of the cast, to test if the pointed to type
36+
// is also a pointer-like. This might be a logic error, so bring extra notice to it.
37+
if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) = ty_resolved.kind() {
38+
if is_ptr_like(cx.tcx, *pointee_ty).is_some() {
39+
diag.note("the pointed to type is still a pointer-like type");
40+
}
41+
} else {
42+
unreachable!("The target type of a cast for `as_underscore_ptr` is a pointer");
43+
}
44+
45+
diag.span_suggestion(
46+
ty_span,
47+
"consider giving the type explicitly",
48+
ty_resolved,
49+
Applicability::MachineApplicable,
50+
);
51+
}
52+
},
53+
);
54+
}
55+
} else {
56+
// not a pointer
57+
}
58+
}

clippy_lints/src/casts/mod.rs

+49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod as_ptr_cast_mut;
22
mod as_underscore;
3+
mod as_underscore_ptr;
34
mod borrow_as_ptr;
45
mod cast_abs_to_unsigned;
56
mod cast_enum_constructor;
@@ -555,6 +556,52 @@ declare_clippy_lint! {
555556
"detects `as _` conversion"
556557
}
557558

559+
declare_clippy_lint! {
560+
/// ### What it does
561+
/// Checks for the usage of `as *const _` or `as *mut _` where the pointed to type is inferred.
562+
///
563+
/// ### Why is this bad?
564+
/// When converting to a pointer, it can be dangerous to not specify the type. Type inference can
565+
/// be affected by many things, types may not always be obvious, and when working with pointers, while
566+
/// the pointed to type doesn't technically matter until an access, you should always know what types
567+
/// you intend to work with. When using multiple chained `as` casts, it can be especially dangerous,
568+
/// because `*const T as *const U` **always compiles** (for Sized types T and U).
569+
///
570+
/// ### Example
571+
/// ```rust
572+
/// // intent: turn a `&u64` into a `*const u8` that points to the bytes of the u64 (⚠️does not work⚠️)
573+
/// fn owo(x: &u64) -> *const u8 {
574+
/// // ⚠️ `&x` is a `&&u64`, so this turns a double pointer into a single pointer
575+
/// // ⚠️ This pointer is a dangling pointer to a local
576+
/// &x as *const _ as *const u8
577+
/// }
578+
/// ```
579+
/// Use instead:
580+
/// ```rust
581+
/// // intent: turn a `&u64` into a `*const u8` that points to the bytes of the u64 (⚠️does not work⚠️)
582+
/// fn owo(x: &u64) -> *const u8 {
583+
/// // ⚠️ `&x` is a `&&u64`, so this turns a double pointer into a single pointer
584+
/// // ⚠️ This pointer is a dangling pointer to a local
585+
/// &x as *const &u64 as *const u8
586+
/// }
587+
/// ```
588+
/// While this is still buggy, the bug is more visible here: you have a `*const &u64`. To actually fix the
589+
/// underlying issue, use:
590+
/// ```rust
591+
/// // turn a `&u64` into a `*const u8` that points to the bytes of the u64
592+
/// fn owo(x: &u64) -> *const u8 {
593+
/// x as *const &u64 as *const u8
594+
/// // ^ now `x` instead of `&x`
595+
/// }
596+
/// ```
597+
/// This lint cannot suggest this final code because it's a more broad `restriction` lint that forces the
598+
/// user to be more specific, and this transformation is not always valid.
599+
#[clippy::version = "1.70.0"]
600+
pub AS_UNDERSCORE_PTR,
601+
restriction,
602+
"detects `as *{const,mut} _ conversions"
603+
}
604+
558605
declare_clippy_lint! {
559606
/// ### What it does
560607
/// Checks for the usage of `&expr as *const T` or
@@ -693,6 +740,7 @@ impl_lint_pass!(Casts => [
693740
CAST_ENUM_CONSTRUCTOR,
694741
CAST_ABS_TO_UNSIGNED,
695742
AS_UNDERSCORE,
743+
AS_UNDERSCORE_PTR,
696744
BORROW_AS_PTR,
697745
CAST_SLICE_FROM_RAW_PARTS,
698746
AS_PTR_CAST_MUT,
@@ -741,6 +789,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
741789
}
742790

743791
as_underscore::check(cx, expr, cast_to_hir);
792+
as_underscore_ptr::check(cx, expr, cast_to_hir);
744793

745794
if self.msrv.meets(msrvs::BORROW_AS_PTR) {
746795
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
6969
crate::cargo::WILDCARD_DEPENDENCIES_INFO,
7070
crate::casts::AS_PTR_CAST_MUT_INFO,
7171
crate::casts::AS_UNDERSCORE_INFO,
72+
crate::casts::AS_UNDERSCORE_PTR_INFO,
7273
crate::casts::BORROW_AS_PTR_INFO,
7374
crate::casts::CAST_ABS_TO_UNSIGNED_INFO,
7475
crate::casts::CAST_ENUM_CONSTRUCTOR_INFO,

clippy_lints/src/only_used_in_recursion.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_hir::hir_id::HirIdMap;
88
use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind};
99
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::ty::subst::{EarlyBinder, GenericArgKind, SubstsRef};
11-
use rustc_middle::ty::{self, ConstKind};
11+
use rustc_middle::ty::{self, ConstKind, List};
1212
use rustc_session::{declare_tool_lint, impl_lint_pass};
1313
use rustc_span::symbol::{kw, Ident};
1414
use rustc_span::Span;
@@ -249,7 +249,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
249249
{
250250
(
251251
trait_item_id,
252-
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const _ as usize),
252+
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const List<_> as usize),
253253
usize::from(sig.decl.implicit_self.has_implicit_self()),
254254
)
255255
} else {
@@ -390,6 +390,6 @@ fn has_matching_substs(kind: FnKind, substs: SubstsRef<'_>) -> bool {
390390
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
391391
}),
392392
#[allow(trivial_casts)]
393-
FnKind::ImplTraitFn(expected_substs) => substs as *const _ as usize == expected_substs,
393+
FnKind::ImplTraitFn(expected_substs) => substs as *const List<_> as usize == expected_substs,
394394
}
395395
}

clippy_utils/src/ty.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1164,3 +1164,28 @@ pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
11641164
_ => false,
11651165
}
11661166
}
1167+
1168+
/// If a type is a pointer or something that is known to act like a pointer, returns
1169+
/// the wrapped type by recursively unpacking the type. Otherwise returns None.
1170+
/// "pointer-like" types are:
1171+
/// &T
1172+
/// &mut T
1173+
/// *const T
1174+
/// *mut T
1175+
/// Box<T>
1176+
/// Rc<T>
1177+
/// Arc<T>
1178+
pub fn is_ptr_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
1179+
match ty.kind() {
1180+
ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => is_ptr_like(tcx, *ty).or(Some(*ty)),
1181+
ty::Adt(def, generics)
1182+
if def.is_box()
1183+
|| tcx.is_diagnostic_item(sym::Rc, def.did())
1184+
|| tcx.is_diagnostic_item(sym::Arc, def.did()) =>
1185+
{
1186+
let ty = generics.type_at(0);
1187+
is_ptr_like(tcx, ty).or(Some(ty))
1188+
},
1189+
_ => None,
1190+
}
1191+
}

tests/ui/as_underscore_ptr.fixed

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//@run-rustfix
2+
3+
#![allow(unused)]
4+
#![deny(clippy::as_underscore_ptr)]
5+
6+
use std::rc::Rc;
7+
8+
// intent: turn a `&u64` into a `*const u8` that points to the bytes of the u64 (⚠️does not work⚠️)
9+
fn owo(x: &u64) -> *const u8 {
10+
// ⚠️ `&x` is a `&&u64`, so this turns a double pointer into a single pointer
11+
// ⚠️ This pointer is a dangling pointer to a local
12+
&x as *const &u64 as *const u8
13+
}
14+
15+
// note: this test is the same basic idea as above, but uses a `&self` which can be even more
16+
// misleading. In fact this is the case that was found in *real code* (that didn't work)
17+
// that inspired this lint. Make sure that it lints with `&self`!
18+
struct UwU;
19+
impl UwU {
20+
// like above, this creates a double pointer, and then returns a dangling single pointer
21+
// intent: turn a `&UwU` into a `*const u8` that points to the same data
22+
fn as_ptr(&self) -> *const u8 {
23+
// ⚠️ `&self` is a `&&UwU`, so this turns a double pointer into a single pointer
24+
// ⚠️ This pointer is a dangling pointer to a local
25+
&self as *const &UwU as *const u8
26+
}
27+
}
28+
29+
fn use_ptr(_: *const ()) {}
30+
31+
fn main() {
32+
let _: *const u8 = 1 as *const u8;
33+
use_ptr(1 as *const ());
34+
35+
let _: *mut u8 = 1 as *mut u8;
36+
37+
// Pointer-to-pointer note tests
38+
// If a _ resolves to a type that is itself a pointer, it's likely a mistake
39+
// Show a note for all of these cases
40+
41+
// const ptr to ref
42+
let r = &&1;
43+
let _ = r as *const &i32;
44+
45+
// const ptr to mut ref
46+
let r = &&mut 1;
47+
let _ = r as *const &mut i32;
48+
49+
// mut ptr to ref
50+
let r = &mut &1;
51+
let _ = r as *mut &i32;
52+
53+
// mut ptr to mut ref
54+
let r = &mut &mut 1;
55+
let _ = r as *mut &mut i32;
56+
57+
// ptr to Box
58+
let b = Box::new(1);
59+
let r = &b;
60+
let _ = r as *const std::boxed::Box<i32>;
61+
62+
// ptr to Rc
63+
let rc = Rc::new(1);
64+
let r = &rc;
65+
let _ = r as *const std::rc::Rc<i32>;
66+
}

tests/ui/as_underscore_ptr.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//@run-rustfix
2+
3+
#![allow(unused)]
4+
#![deny(clippy::as_underscore_ptr)]
5+
6+
use std::rc::Rc;
7+
8+
// intent: turn a `&u64` into a `*const u8` that points to the bytes of the u64 (⚠️does not work⚠️)
9+
fn owo(x: &u64) -> *const u8 {
10+
// ⚠️ `&x` is a `&&u64`, so this turns a double pointer into a single pointer
11+
// ⚠️ This pointer is a dangling pointer to a local
12+
&x as *const _ as *const u8
13+
}
14+
15+
// note: this test is the same basic idea as above, but uses a `&self` which can be even more
16+
// misleading. In fact this is the case that was found in *real code* (that didn't work)
17+
// that inspired this lint. Make sure that it lints with `&self`!
18+
struct UwU;
19+
impl UwU {
20+
// like above, this creates a double pointer, and then returns a dangling single pointer
21+
// intent: turn a `&UwU` into a `*const u8` that points to the same data
22+
fn as_ptr(&self) -> *const u8 {
23+
// ⚠️ `&self` is a `&&UwU`, so this turns a double pointer into a single pointer
24+
// ⚠️ This pointer is a dangling pointer to a local
25+
&self as *const _ as *const u8
26+
}
27+
}
28+
29+
fn use_ptr(_: *const ()) {}
30+
31+
fn main() {
32+
let _: *const u8 = 1 as *const _;
33+
use_ptr(1 as *const _);
34+
35+
let _: *mut u8 = 1 as *mut _;
36+
37+
// Pointer-to-pointer note tests
38+
// If a _ resolves to a type that is itself a pointer, it's likely a mistake
39+
// Show a note for all of these cases
40+
41+
// const ptr to ref
42+
let r = &&1;
43+
let _ = r as *const _;
44+
45+
// const ptr to mut ref
46+
let r = &&mut 1;
47+
let _ = r as *const _;
48+
49+
// mut ptr to ref
50+
let r = &mut &1;
51+
let _ = r as *mut _;
52+
53+
// mut ptr to mut ref
54+
let r = &mut &mut 1;
55+
let _ = r as *mut _;
56+
57+
// ptr to Box
58+
let b = Box::new(1);
59+
let r = &b;
60+
let _ = r as *const _;
61+
62+
// ptr to Rc
63+
let rc = Rc::new(1);
64+
let r = &rc;
65+
let _ = r as *const _;
66+
}

0 commit comments

Comments
 (0)