Skip to content

add manual_dangling_ptr lint #14107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5790,6 +5790,7 @@ Released 2018-09-13
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
[`manual_div_ceil`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_div_ceil
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
Expand Down
82 changes: 82 additions & 0 deletions clippy_lints/src/casts/manual_dangling_ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::is_normalizable;
use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;

use super::MANUAL_DANGLING_PTR;

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
if let TyKind::Ptr(ref ptr_ty) = to.kind {
let init_expr = expr_or_init(cx, from);
if is_expr_const_aligned(cx, init_expr, ptr_ty.ty)
&& let Some(std_or_core) = std_or_core(cx)
{
let sugg_fn = match ptr_ty.mutbl {
Mutability::Not => "ptr::dangling",
Mutability::Mut => "ptr::dangling_mut",
};

let sugg = if let TyKind::Infer(()) = ptr_ty.ty.kind {
format!("{std_or_core}::{sugg_fn}()")
} else if let Some(mut_ty_snip) = ptr_ty.ty.span.get_source_text(cx) {
format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
} else {
return;
};

span_lint_and_sugg(
cx,
MANUAL_DANGLING_PTR,
expr.span,
"manual creation of a dangling pointer",
"use",
sugg,
Applicability::MachineApplicable,
);
}
}
}

// Checks if the given expression is a call to `align_of` whose generic argument matches the target
// type, or a positive constant literal that matches the target type's alignment.
fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool {
match expr.kind {
ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to),
ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to),
_ => false,
}
}

fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind
&& let Some(fun_id) = path_def_id(cx, fun)
&& match_def_path(cx, fun_id, &paths::ALIGN_OF)
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
&& let [GenericArg::Type(generic_ty)] = args.args
{
let typeck = cx.typeck_results();
return typeck.node_type(generic_ty.hir_id) == typeck.node_type(to.hir_id);
}
false
}

fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>) -> bool {
let LitKind::Int(val, _) = lit.node else { return false };
if val == 0 {
return false;
}
let to_mid_ty = cx.typeck_results().node_type(to.hir_id);
is_normalizable(cx, cx.param_env, to_mid_ty)
&& cx
.tcx
.layout_of(cx.typing_env().as_query_input(to_mid_ty))
.is_ok_and(|layout| {
let align = u128::from(layout.align.abi.bytes());
u128::from(val) <= align
})
}
32 changes: 32 additions & 0 deletions clippy_lints/src/casts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod char_lit_as_u8;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
Expand Down Expand Up @@ -759,6 +760,32 @@ declare_clippy_lint! {
"detects `as *mut _` and `as *const _` conversion"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for casts of small constant literals or `mem::align_of` results to raw pointers.
///
/// ### Why is this bad?
/// This creates a dangling pointer and is better expressed as
/// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}.
///
/// ### Example
/// ```no_run
/// let ptr = 4 as *const u32;
/// let aligned = std::mem::align_of::<u32>() as *const u32;
/// let mut_ptr: *mut i64 = 8 as *mut _;
/// ```
/// Use instead:
/// ```no_run
/// let ptr = std::ptr::dangling::<u32>();
/// let aligned = std::ptr::dangling::<u32>();
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
/// ```
#[clippy::version = "1.87.0"]
pub MANUAL_DANGLING_PTR,
style,
"casting small constant literals to pointers to create dangling pointers"
}

pub struct Casts {
msrv: Msrv,
}
Expand Down Expand Up @@ -795,6 +822,7 @@ impl_lint_pass!(Casts => [
ZERO_PTR,
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
]);

impl<'tcx> LateLintPass<'tcx> for Casts {
Expand Down Expand Up @@ -823,6 +851,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);

if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
}

if cast_to.is_numeric() {
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
if cast_from.is_numeric() {
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
Expand Down
2 changes: 1 addition & 1 deletion clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! msrv_aliases {
msrv_aliases! {
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT }
1,85,0 { UINT_FLOAT_MIDPOINT }
1,84,0 { CONST_OPTION_AS_SLICE }
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }
1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION }
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]
pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>", "is_ascii"];
pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];
pub const ALIGN_OF: [&str; 3] = ["core", "mem", "align_of"];

// Paths in clippy itself
pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"];
Expand Down
44 changes: 44 additions & 0 deletions tests/ui/manual_dangling_ptr.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![warn(clippy::manual_dangling_ptr)]
use std::mem;

pub fn foo(_const: *const f32, _mut: *mut i32) {}

fn main() {
let _: *const u8 = std::ptr::dangling();
//~^ manual_dangling_ptr
let _ = std::ptr::dangling::<u32>();
//~^ manual_dangling_ptr
let _ = std::ptr::dangling_mut::<f32>();
//~^ manual_dangling_ptr

let _ = std::ptr::dangling::<u8>();
//~^ manual_dangling_ptr
let _ = std::ptr::dangling::<u32>();
//~^ manual_dangling_ptr
let _ = std::ptr::dangling::<usize>();
//~^ manual_dangling_ptr

foo(std::ptr::dangling(), std::ptr::dangling_mut());
//~^ manual_dangling_ptr
//~| manual_dangling_ptr
}

fn should_not_lint() {
let _ = 0x10 as *mut i32;
let _ = mem::align_of::<u32>() as *const u8;

foo(0 as _, 0 as _);
}

#[clippy::msrv = "1.83"]
fn _msrv_1_83() {
// `{core, std}::ptr::dangling` was stabilized in 1.84. Do not lint this
foo(4 as *const _, 4 as *mut _);
}

#[clippy::msrv = "1.84"]
fn _msrv_1_84() {
foo(std::ptr::dangling(), std::ptr::dangling_mut());
//~^ manual_dangling_ptr
//~| manual_dangling_ptr
}
44 changes: 44 additions & 0 deletions tests/ui/manual_dangling_ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![warn(clippy::manual_dangling_ptr)]
use std::mem;

pub fn foo(_const: *const f32, _mut: *mut i32) {}
Comment on lines +1 to +4
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarcho I have modified the tests to use at most 32-bit types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add the PR to the merge queue?


fn main() {
let _: *const u8 = 1 as *const _;
//~^ manual_dangling_ptr
let _ = 2 as *const u32;
//~^ manual_dangling_ptr
let _ = 4 as *mut f32;
//~^ manual_dangling_ptr

let _ = mem::align_of::<u8>() as *const u8;
//~^ manual_dangling_ptr
let _ = mem::align_of::<u32>() as *const u32;
//~^ manual_dangling_ptr
let _ = mem::align_of::<usize>() as *const usize;
//~^ manual_dangling_ptr

foo(4 as *const _, 4 as *mut _);
//~^ manual_dangling_ptr
//~| manual_dangling_ptr
}

fn should_not_lint() {
let _ = 0x10 as *mut i32;
let _ = mem::align_of::<u32>() as *const u8;

foo(0 as _, 0 as _);
}

#[clippy::msrv = "1.83"]
fn _msrv_1_83() {
// `{core, std}::ptr::dangling` was stabilized in 1.84. Do not lint this
foo(4 as *const _, 4 as *mut _);
}

#[clippy::msrv = "1.84"]
fn _msrv_1_84() {
foo(4 as *const _, 4 as *mut _);
//~^ manual_dangling_ptr
//~| manual_dangling_ptr
}
65 changes: 65 additions & 0 deletions tests/ui/manual_dangling_ptr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:7:24
|
LL | let _: *const u8 = 1 as *const _;
| ^^^^^^^^^^^^^ help: use: `std::ptr::dangling()`
|
= note: `-D clippy::manual-dangling-ptr` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_dangling_ptr)]`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:9:13
|
LL | let _ = 2 as *const u32;
| ^^^^^^^^^^^^^^^ help: use: `std::ptr::dangling::<u32>()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:11:13
|
LL | let _ = 4 as *mut f32;
| ^^^^^^^^^^^^^ help: use: `std::ptr::dangling_mut::<f32>()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:14:13
|
LL | let _ = mem::align_of::<u8>() as *const u8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `std::ptr::dangling::<u8>()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:16:13
|
LL | let _ = mem::align_of::<u32>() as *const u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `std::ptr::dangling::<u32>()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:18:13
|
LL | let _ = mem::align_of::<usize>() as *const usize;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `std::ptr::dangling::<usize>()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:21:9
|
LL | foo(4 as *const _, 4 as *mut _);
| ^^^^^^^^^^^^^ help: use: `std::ptr::dangling()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:21:24
|
LL | foo(4 as *const _, 4 as *mut _);
| ^^^^^^^^^^^ help: use: `std::ptr::dangling_mut()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:41:9
|
LL | foo(4 as *const _, 4 as *mut _);
| ^^^^^^^^^^^^^ help: use: `std::ptr::dangling()`

error: manual creation of a dangling pointer
--> tests/ui/manual_dangling_ptr.rs:41:24
|
LL | foo(4 as *const _, 4 as *mut _);
| ^^^^^^^^^^^ help: use: `std::ptr::dangling_mut()`

error: aborting due to 10 previous errors

2 changes: 1 addition & 1 deletion tests/ui/transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn useless() {
let _: *const usize = std::mem::transmute(5_isize);
//~^ useless_transmute

let _ = 5_isize as *const usize;
let _ = std::ptr::dangling::<usize>();

let _: *const usize = std::mem::transmute(1 + 1usize);
//~^ useless_transmute
Expand Down
1 change: 1 addition & 0 deletions tests/ui/transmute_null_to_fn.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(dead_code)]
#![warn(clippy::transmute_null_to_fn)]
#![allow(clippy::zero_ptr, clippy::missing_transmute_annotations)]
#![allow(clippy::manual_dangling_ptr)]

// Easy to lint because these only span one line.
fn one_liners() {
Expand Down
Loading