Skip to content

Add lint against integer to pointer transmutes #144531

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,14 @@ lint_invalid_reference_casting_note_book = for more information, visit <https://

lint_invalid_reference_casting_note_ty_has_interior_mutability = even for types with interior mutability, the only legal way to obtain a mutable pointer from a shared reference is through `UnsafeCell::get`

lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance
.note = this is dangerous because dereferencing the resulting pointer is undefined behavior
.note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
.help_transmute = for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
.help_exposed_provenance = for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
.suggestion_with_exposed_provenance = use `std::ptr::with_exposed_provenance{$suffix}` instead to use a previously exposed provenance
.suggestion_without_provenance_mut = if you truly mean to create a pointer without provenance, use `std::ptr::without_provenance_mut`

lint_legacy_derive_helpers = derive helper attribute is used before it is introduced
.label = the attribute is introduced here

Expand Down
44 changes: 44 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore-tidy-filelength

#![allow(rustc::untranslatable_diagnostic)]
use std::num::NonZero;

Expand Down Expand Up @@ -1618,6 +1620,48 @@ impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
}
}

// transmute.rs
#[derive(LintDiagnostic)]
#[diag(lint_int_to_ptr_transmutes)]
#[note]
#[note(lint_note_exposed_provenance)]
#[help(lint_suggestion_without_provenance_mut)]
#[help(lint_help_transmute)]
#[help(lint_help_exposed_provenance)]
pub(crate) struct IntegerToPtrTransmutes<'tcx> {
#[subdiagnostic]
pub suggestion: IntegerToPtrTransmutesSuggestion<'tcx>,
}

#[derive(Subdiagnostic)]
pub(crate) enum IntegerToPtrTransmutesSuggestion<'tcx> {
#[multipart_suggestion(
lint_suggestion_with_exposed_provenance,
applicability = "machine-applicable",
style = "verbose"
)]
ToPtr {
dst: Ty<'tcx>,
suffix: &'static str,
#[suggestion_part(code = "std::ptr::with_exposed_provenance{suffix}::<{dst}>(")]
start_call: Span,
},
#[multipart_suggestion(
lint_suggestion_with_exposed_provenance,
applicability = "machine-applicable",
style = "verbose"
)]
ToRef {
dst: Ty<'tcx>,
suffix: &'static str,
ref_mutbl: &'static str,
#[suggestion_part(
code = "&{ref_mutbl}*std::ptr::with_exposed_provenance{suffix}::<{dst}>("
)]
start_call: Span,
},
}

// types.rs
#[derive(LintDiagnostic)]
#[diag(lint_range_endpoint_out_of_range)]
Expand Down
84 changes: 83 additions & 1 deletion compiler/rustc_lint/src/transmute.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::LocalDefId;
Expand All @@ -7,6 +8,7 @@ use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint, impl_lint_pass};
use rustc_span::sym;

use crate::lints::{IntegerToPtrTransmutes, IntegerToPtrTransmutesSuggestion};
use crate::{LateContext, LateLintPass};

declare_lint! {
Expand Down Expand Up @@ -67,9 +69,44 @@ declare_lint! {
"detects transmutes that can also be achieved by other operations"
}

declare_lint! {
/// The `integer_to_ptr_transmutes` lint detects integer to pointer
/// transmutes where the resulting pointers are undefined behavior to dereference.
///
/// ### Example
///
/// ```rust
/// fn foo(a: usize) -> *const u8 {
/// unsafe {
/// std::mem::transmute::<usize, *const u8>(a)
/// }
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Any attempt to use the resulting pointers are undefined behavior as the resulting
/// pointers won't have any provenance.
///
/// Alternatively, [`std::ptr::with_exposed_provenance`] should be used, as they do not
/// carry the provenance requirement. If wanting to create pointers without provenance
/// [`std::ptr::without_provenance`] should be used instead.
///
/// See [`std::mem::transmute`] in the reference for more details.
///
/// [`std::mem::transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
/// [`std::ptr::with_exposed_provenance`]: https://doc.rust-lang.org/std/ptr/fn.with_exposed_provenance.html
/// [`std::ptr::without_provenance`]: https://doc.rust-lang.org/std/ptr/fn.without_provenance.html
pub INTEGER_TO_PTR_TRANSMUTES,
Warn,
"detects integer to pointer transmutes",
}

pub(crate) struct CheckTransmutes;

impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES, INTEGER_TO_PTR_TRANSMUTES]);

impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
Expand All @@ -94,6 +131,51 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {

check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
check_int_to_ptr_transmute(cx, expr, arg, src, dst);
}
}

/// Check for transmutes from integer to pointers (*const/*mut, &/&mut and fn()).
///
/// Using the resulting pointers would be undefined behavior.
fn check_int_to_ptr_transmute<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
arg: &'tcx hir::Expr<'tcx>,
src: Ty<'tcx>,
dst: Ty<'tcx>,
) {
if matches!(src.kind(), ty::Uint(_) | ty::Int(_))
&& let ty::Ref(_, inner_ty, mutbl) | ty::RawPtr(inner_ty, mutbl) = dst.kind()
// bail-out if the argument is literal 0 as we have other lints for those cases
&& !matches!(arg.kind, hir::ExprKind::Lit(hir::Lit { node: LitKind::Int(v, _), .. }) if v == 0)
// bail-out if the inner type if a ZST
&& cx.tcx
.layout_of(cx.typing_env().as_query_input(*inner_ty))
.is_ok_and(|layout| !layout.is_1zst())
{
let suffix = if mutbl.is_mut() { "_mut" } else { "" };
cx.tcx.emit_node_span_lint(
INTEGER_TO_PTR_TRANSMUTES,
expr.hir_id,
expr.span,
IntegerToPtrTransmutes {
suggestion: if dst.is_ref() {
IntegerToPtrTransmutesSuggestion::ToRef {
dst: *inner_ty,
suffix,
ref_mutbl: mutbl.prefix_str(),
start_call: expr.span.shrink_to_lo().until(arg.span),
}
} else {
IntegerToPtrTransmutesSuggestion::ToPtr {
dst: *inner_ty,
suffix,
start_call: expr.span.shrink_to_lo().until(arg.span),
}
},
},
);
}
}

Expand Down
1 change: 1 addition & 0 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ pub const fn dangling<T>() -> *const T {
#[must_use]
#[stable(feature = "strict_provenance", since = "1.84.0")]
#[rustc_const_stable(feature = "strict_provenance", since = "1.84.0")]
#[allow(integer_to_ptr_transmutes)] // Expected semantics here.
pub const fn without_provenance_mut<T>(addr: usize) -> *mut T {
// An int-to-pointer transmute currently has exactly the intended semantics: it creates a
// pointer without provenance. Note that this is *not* a stable guarantee about transmute
Expand Down
12 changes: 1 addition & 11 deletions src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,7 @@ pub(super) fn check<'tcx>(
true
},
(ty::Int(_) | ty::Uint(_), ty::RawPtr(_, _)) => {
span_lint_and_then(
cx,
USELESS_TRANSMUTE,
e.span,
"transmute from an integer to a pointer",
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
diag.span_suggestion(e.span, "try", arg.as_ty(to_ty.to_string()), Applicability::Unspecified);
}
},
);
// Handled by the upstream rustc `integer_to_ptr_transmutes` lint
true
},
_ => false,
Expand Down
3 changes: 1 addition & 2 deletions src/tools/clippy/tests/ui/transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
dead_code,
clippy::borrow_as_ptr,
unnecessary_transmutes,
integer_to_ptr_transmutes,
clippy::needless_lifetimes,
clippy::missing_transmute_annotations
)]
Expand Down Expand Up @@ -60,12 +61,10 @@ fn useless() {
//~^ useless_transmute

let _: *const usize = std::mem::transmute(5_isize);
//~^ useless_transmute

let _ = std::ptr::dangling::<usize>();

let _: *const usize = std::mem::transmute(1 + 1usize);
//~^ useless_transmute

let _ = (1 + 1_usize) as *const usize;
}
Expand Down
46 changes: 17 additions & 29 deletions src/tools/clippy/tests/ui/transmute.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: transmute from a reference to a pointer
--> tests/ui/transmute.rs:33:27
--> tests/ui/transmute.rs:34:27
|
LL | let _: *const T = core::mem::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T`
Expand All @@ -8,61 +8,49 @@ LL | let _: *const T = core::mem::transmute(t);
= help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]`

error: transmute from a reference to a pointer
--> tests/ui/transmute.rs:36:25
--> tests/ui/transmute.rs:37:25
|
LL | let _: *mut T = core::mem::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T`

error: transmute from a reference to a pointer
--> tests/ui/transmute.rs:39:27
--> tests/ui/transmute.rs:40:27
|
LL | let _: *const U = core::mem::transmute(t);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U`

error: transmute from a type (`std::vec::Vec<i32>`) to itself
--> tests/ui/transmute.rs:47:27
--> tests/ui/transmute.rs:48:27
|
LL | let _: Vec<i32> = core::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`std::vec::Vec<i32>`) to itself
--> tests/ui/transmute.rs:50:27
--> tests/ui/transmute.rs:51:27
|
LL | let _: Vec<i32> = core::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`std::vec::Vec<i32>`) to itself
--> tests/ui/transmute.rs:53:27
--> tests/ui/transmute.rs:54:27
|
LL | let _: Vec<i32> = std::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`std::vec::Vec<i32>`) to itself
--> tests/ui/transmute.rs:56:27
--> tests/ui/transmute.rs:57:27
|
LL | let _: Vec<i32> = std::mem::transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`std::vec::Vec<i32>`) to itself
--> tests/ui/transmute.rs:59:27
--> tests/ui/transmute.rs:60:27
|
LL | let _: Vec<i32> = my_transmute(my_vec());
| ^^^^^^^^^^^^^^^^^^^^^^

error: transmute from an integer to a pointer
--> tests/ui/transmute.rs:62:31
|
LL | let _: *const usize = std::mem::transmute(5_isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize`

error: transmute from an integer to a pointer
--> tests/ui/transmute.rs:67:31
|
LL | let _: *const usize = std::mem::transmute(1 + 1usize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize`

error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`)
--> tests/ui/transmute.rs:99:24
--> tests/ui/transmute.rs:98:24
|
LL | let _: Usize = core::mem::transmute(int_const_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -71,25 +59,25 @@ LL | let _: Usize = core::mem::transmute(int_const_ptr);
= help: to override `-D warnings` add `#[allow(clippy::crosspointer_transmute)]`

error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`)
--> tests/ui/transmute.rs:102:24
--> tests/ui/transmute.rs:101:24
|
LL | let _: Usize = core::mem::transmute(int_mut_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`)
--> tests/ui/transmute.rs:105:31
--> tests/ui/transmute.rs:104:31
|
LL | let _: *const Usize = core::mem::transmute(my_int());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`)
--> tests/ui/transmute.rs:108:29
--> tests/ui/transmute.rs:107:29
|
LL | let _: *mut Usize = core::mem::transmute(my_int());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: transmute from a `u8` to a `bool`
--> tests/ui/transmute.rs:115:28
--> tests/ui/transmute.rs:114:28
|
LL | let _: bool = unsafe { std::mem::transmute(0_u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0`
Expand All @@ -98,7 +86,7 @@ LL | let _: bool = unsafe { std::mem::transmute(0_u8) };
= help: to override `-D warnings` add `#[allow(clippy::transmute_int_to_bool)]`

error: transmute from a `&[u8]` to a `&str`
--> tests/ui/transmute.rs:122:28
--> tests/ui/transmute.rs:121:28
|
LL | let _: &str = unsafe { std::mem::transmute(B) };
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(B).unwrap()`
Expand All @@ -107,16 +95,16 @@ LL | let _: &str = unsafe { std::mem::transmute(B) };
= help: to override `-D warnings` add `#[allow(clippy::transmute_bytes_to_str)]`

error: transmute from a `&mut [u8]` to a `&mut str`
--> tests/ui/transmute.rs:125:32
--> tests/ui/transmute.rs:124:32
|
LL | let _: &mut str = unsafe { std::mem::transmute(mb) };
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()`

error: transmute from a `&[u8]` to a `&str`
--> tests/ui/transmute.rs:128:30
--> tests/ui/transmute.rs:127:30
|
LL | const _: &str = unsafe { std::mem::transmute(B) };
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_unchecked(B)`

error: aborting due to 18 previous errors
error: aborting due to 16 previous errors

Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ fn main() {
// We should see an error message for each transmute, and no error messages for
// the casts, since the casts are the recommended fixes.

// e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
let _ptr_i32_transmute = unsafe { usize::MAX as *const i32 };
//~^ useless_transmute
let ptr_i32 = usize::MAX as *const i32;

// e has type *T, U is *U_0, and either U_0: Sized ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ fn main() {
// We should see an error message for each transmute, and no error messages for
// the casts, since the casts are the recommended fixes.

// e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
let _ptr_i32_transmute = unsafe { transmute::<usize, *const i32>(usize::MAX) };
//~^ useless_transmute
let ptr_i32 = usize::MAX as *const i32;

// e has type *T, U is *U_0, and either U_0: Sized ...
Expand Down
Loading
Loading