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 @@ -476,6 +476,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_as = use `as` cast 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
57 changes: 57 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,63 @@ 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)]
// FIXME: always recommend `with_exposed_provenance` when it's const-stable
pub(crate) enum IntegerToPtrTransmutesSuggestion<'tcx> {
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
ToPtr {
dst: Ty<'tcx>,
suffix: &'static str,
#[suggestion_part(code = "std::ptr::with_exposed_provenance{suffix}::<{dst}>(")]
start_call: Span,
},
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
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,
},
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
ToPtrInConst {
dst: Ty<'tcx>,
paren_left: &'static str,
paren_right: &'static str,
#[suggestion_part(code = "{paren_left}")]
start_call: Span,
#[suggestion_part(code = "{paren_right} as {dst}")]
end_call: Span,
},
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
ToRefInConst {
dst: Ty<'tcx>,
ptr_mutbl: &'static str,
ref_mutbl: &'static str,
paren_left: &'static str,
paren_right: &'static str,
#[suggestion_part(code = "&{ref_mutbl}*({paren_left}")]
start_call: Span,
#[suggestion_part(code = "{paren_right} as *{ptr_mutbl} {dst})")]
end_call: Span,
},
}

// types.rs
#[derive(LintDiagnostic)]
#[diag(lint_range_endpoint_out_of_range)]
Expand Down
116 changes: 115 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`] or `as` casts should be used,
/// as they do not carry the provenance requirement. If the 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,83 @@ 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())
{
cx.tcx.emit_node_span_lint(
INTEGER_TO_PTR_TRANSMUTES,
expr.hir_id,
expr.span,
IntegerToPtrTransmutes {
// FIXME: once https://github.com/rust-lang/rust/issues/144538 finishes,
// we can recommend the method in const context too.
suggestion: if !cx.tcx.hir_is_inside_const_context(expr.hir_id) {
let suffix = if mutbl.is_mut() { "_mut" } else { "" };
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),
}
}
} else {
// does the argument needs parenthesis
let mut paren_left = "";
let mut paren_right = "";
if matches!(arg.kind, hir::ExprKind::Binary(..)) {
paren_left = "(";
paren_right = ")";
}

if dst.is_ref() {
IntegerToPtrTransmutesSuggestion::ToRefInConst {
dst: *inner_ty,
ref_mutbl: mutbl.prefix_str(),
ptr_mutbl: mutbl.ptr_str(),
paren_left,
paren_right,
start_call: expr.span.shrink_to_lo().until(arg.span),
end_call: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
}
} else {
IntegerToPtrTransmutesSuggestion::ToPtrInConst {
dst,
paren_left,
paren_right,
start_call: expr.span.shrink_to_lo().until(arg.span),
end_call: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
}
}
},
},
);
}
}

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
Loading
Loading