Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
186ea6e
Only run the pull workflow once per week
Kobzol Aug 7, 2025
96083af
Merge pull request #2540 from Kobzol/ci-cron-interval
tshepang Aug 7, 2025
9ade653
Remove mentions of Discord.
kpreid Aug 8, 2025
08461ec
Merge pull request #2541 from kpreid/discord
Noratrieb Aug 8, 2025
c12f83e
add a tip for english writing
chenyukang Aug 11, 2025
5abfd82
Merge pull request #2543 from chenyukang/yukang-add-tip-for-english-w…
tshepang Aug 11, 2025
97564bd
place link on the more suitable text
tshepang Aug 11, 2025
3b52678
Merge pull request #2544 from rust-lang/tshepang-patch-1
tshepang Aug 11, 2025
538299a
fix grammar
tshepang Aug 11, 2025
f2294bb
Merge pull request #2545 from rust-lang/tshepang-patch-1
Noratrieb Aug 11, 2025
a3510c7
Prepare for merging from rust-lang/rust
invalid-email-address Aug 18, 2025
5818cbd
Merge ref '425a9c0a0e36' from rust-lang/rust
invalid-email-address Aug 18, 2025
2787e9e
Merge pull request #2547 from rust-lang/rustc-pull
tshepang Aug 18, 2025
06eb782
modify `LazyLock` poison panic message
connortsui20 Aug 22, 2025
10fde9e
Implement some more checks for `ptr_guaranteed_cmp` in consteval:
zachs18 Jul 28, 2025
399f5e2
std/src/lib.rs: mention "search button" instead of "search bar"
ada4a Aug 23, 2025
d4cbd9a
Add lint against integer to pointer transmutes
Urgau Jul 27, 2025
f25bf37
Allow `integer_to_ptr_transmutes` in core
Urgau Jul 27, 2025
3c66478
Allow `integer_to_ptr_transmutes` in tests
Urgau Jul 27, 2025
02a67cc
Adjust clippy lints for rustc `integer_to_ptr_transmutes` lint
Urgau Jul 27, 2025
1da4959
Prefer verbose suggestions for `integer_to_ptr_transmutes` lint
Urgau Aug 16, 2025
e4557f0
Use unnamed lifetime spans as primary spans for MISMATCHED_LIFETIME_S…
compiler-errors Aug 23, 2025
30b5aaa
Rollup merge of #144531 - Urgau:int_to_ptr_transmutes, r=jackh726
Zalathar Aug 24, 2025
2e6cf07
Rollup merge of #144885 - zachs18:ptr_guaranteed_cmp_more, r=RalfJung
Zalathar Aug 24, 2025
fa990f4
Rollup merge of #145307 - connortsui20:lazylock-poison-msg, r=Amanieu
Zalathar Aug 24, 2025
3c6d3d1
Rollup merge of #145554 - tshepang:rdg-sync, r=BoxyUwU
Zalathar Aug 24, 2025
3d77d44
Rollup merge of #145798 - compiler-errors:unnamed-lt-primary, r=lqd
Zalathar Aug 24, 2025
d17ef1e
Rollup merge of #145799 - ada4a:patch-3, r=GuillaumeGomez
Zalathar Aug 24, 2025
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
118 changes: 103 additions & 15 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,110 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
interp_ok(match (a, b) {
// Comparisons between integers are always known.
(Scalar::Int(a), Scalar::Int(b)) => (a == b) as u8,
// Comparisons of null with an arbitrary scalar can be known if `scalar_may_be_null`
// indicates that the scalar can definitely *not* be null.
(Scalar::Int(int), ptr) | (ptr, Scalar::Int(int))
if int.is_null() && !self.scalar_may_be_null(ptr)? =>
{
0
// Comparing a pointer `ptr` with an integer `int` is equivalent to comparing
// `ptr-int` with null, so we can reduce this case to a `scalar_may_be_null` test.
(Scalar::Int(int), Scalar::Ptr(ptr, _)) | (Scalar::Ptr(ptr, _), Scalar::Int(int)) => {
let int = int.to_target_usize(*self.tcx);
// The `wrapping_neg` here may produce a value that is not
// a valid target usize any more... but `wrapping_offset` handles that correctly.
let offset_ptr = ptr.wrapping_offset(Size::from_bytes(int.wrapping_neg()), self);
if !self.scalar_may_be_null(Scalar::from_pointer(offset_ptr, self))? {
// `ptr.wrapping_sub(int)` is definitely not equal to `0`, so `ptr != int`
0
} else {
// `ptr.wrapping_sub(int)` could be equal to `0`, but might not be,
// so we cannot know for sure if `ptr == int` or not
2
}
}
(Scalar::Ptr(a, _), Scalar::Ptr(b, _)) => {
let (a_prov, a_offset) = a.prov_and_relative_offset();
let (b_prov, b_offset) = b.prov_and_relative_offset();
let a_allocid = a_prov.alloc_id();
let b_allocid = b_prov.alloc_id();
let a_info = self.get_alloc_info(a_allocid);
let b_info = self.get_alloc_info(b_allocid);

// Check if the pointers cannot be equal due to alignment
if a_info.align > Align::ONE && b_info.align > Align::ONE {
let min_align = Ord::min(a_info.align.bytes(), b_info.align.bytes());
let a_residue = a_offset.bytes() % min_align;
let b_residue = b_offset.bytes() % min_align;
if a_residue != b_residue {
// If the two pointers have a different residue modulo their
// common alignment, they cannot be equal.
return interp_ok(0);
}
// The pointers have the same residue modulo their common alignment,
// so they could be equal. Try the other checks.
}

if let (Some(GlobalAlloc::Static(a_did)), Some(GlobalAlloc::Static(b_did))) = (
self.tcx.try_get_global_alloc(a_allocid),
self.tcx.try_get_global_alloc(b_allocid),
) {
if a_allocid == b_allocid {
debug_assert_eq!(
a_did, b_did,
"different static item DefIds had same AllocId? {a_allocid:?} == {b_allocid:?}, {a_did:?} != {b_did:?}"
);
// Comparing two pointers into the same static. As per
// https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.intro
// a static cannot be duplicated, so if two pointers are into the same
// static, they are equal if and only if their offsets are equal.
(a_offset == b_offset) as u8
} else {
debug_assert_ne!(
a_did, b_did,
"same static item DefId had two different AllocIds? {a_allocid:?} != {b_allocid:?}, {a_did:?} == {b_did:?}"
);
// Comparing two pointers into the different statics.
// We can never determine for sure that two pointers into different statics
// are *equal*, but we can know that they are *inequal* if they are both
// strictly in-bounds (i.e. in-bounds and not one-past-the-end) of
// their respective static, as different non-zero-sized statics cannot
// overlap or be deduplicated as per
// https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.intro
// (non-deduplication), and
// https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.storage-disjointness
// (non-overlapping).
if a_offset < a_info.size && b_offset < b_info.size {
0
} else {
// Otherwise, conservatively say we don't know.
// There are some cases we could still return `0` for, e.g.
// if the pointers being equal would require their statics to overlap
// one or more bytes, but for simplicity we currently only check
// strictly in-bounds pointers.
2
}
}
} else {
// All other cases we conservatively say we don't know.
//
// For comparing statics to non-statics, as per https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.storage-disjointness
// immutable statics can overlap with other kinds of allocations sometimes.
//
// FIXME: We could be more decisive for (non-zero-sized) mutable statics,
// which cannot overlap with other kinds of allocations.
//
// Functions and vtables can be duplicated and deduplicated, so we
// cannot be sure of runtime equality of pointers to the same one, or the
// runtime inequality of pointers to different ones (see e.g. #73722),
// so comparing those should return 2, whether they are the same allocation
// or not.
//
// `GlobalAlloc::TypeId` exists mostly to prevent consteval from comparing
// `TypeId`s, so comparing those should always return 2, whether they are the
// same allocation or not.
//
// FIXME: We could revisit comparing pointers into the same
// `GlobalAlloc::Memory` once https://github.com/rust-lang/rust/issues/128775
// is fixed (but they can be deduplicated, so comparing pointers into different
// ones should return 2).
2
}
}
// Other ways of comparing integers and pointers can never be known for sure.
(Scalar::Int { .. }, Scalar::Ptr(..)) | (Scalar::Ptr(..), Scalar::Int { .. }) => 2,
// FIXME: return a `1` for when both sides are the same pointer, *except* that
// some things (like functions and vtables) do not have stable addresses
// so we need to be careful around them (see e.g. #73722).
// FIXME: return `0` for at least some comparisons where we can reliably
// determine the result of runtime inequality tests at compile-time.
// Examples include comparison of addresses in different static items.
(Scalar::Ptr(..), Scalar::Ptr(..)) => 2,
})
}
}
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,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
8 changes: 4 additions & 4 deletions compiler/rustc_lint/src/lifetime_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ impl<T> LifetimeSyntaxCategories<Vec<T>> {
}
}

pub fn flatten(&self) -> impl Iterator<Item = &T> {
let Self { hidden, elided, named } = self;
[hidden.iter(), elided.iter(), named.iter()].into_iter().flatten()
pub fn iter_unnamed(&self) -> impl Iterator<Item = &T> {
let Self { hidden, elided, named: _ } = self;
[hidden.iter(), elided.iter()].into_iter().flatten()
}
}

Expand Down Expand Up @@ -495,7 +495,7 @@ fn emit_mismatch_diagnostic<'tcx>(

cx.emit_span_lint(
MISMATCHED_LIFETIME_SYNTAXES,
inputs.flatten().copied().collect::<Vec<_>>(),
inputs.iter_unnamed().chain(outputs.iter_unnamed()).copied().collect::<Vec<_>>(),
lints::MismatchedLifetimeSyntaxes { inputs, outputs, suggestions },
);
}
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 @@ -1542,6 +1544,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
92 changes: 91 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,9 +131,62 @@ 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 and &/&mut).
///
/// 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(_)) {
return;
}
let (ty::Ref(_, inner_ty, mutbl) | ty::RawPtr(inner_ty, mutbl)) = dst.kind() else {
return;
};
// bail-out if the argument is literal 0 as we have other lints for those cases
if matches!(arg.kind, hir::ExprKind::Lit(hir::Lit { node: LitKind::Int(v, _), .. }) if v == 0) {
return;
}
// bail-out if the inner type is a ZST
let Ok(layout_inner_ty) = cx.tcx.layout_of(cx.typing_env().as_query_input(*inner_ty)) else {
return;
};
if layout_inner_ty.is_1zst() {
return;
}

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),
}
},
},
);
}

/// Check for transmutes that exhibit undefined behavior.
/// For example, transmuting pointers to integers in a const context.
///
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
2 changes: 1 addition & 1 deletion library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//!
//! If you already know the name of what you are looking for, the fastest way to
//! find it is to use the <a href="#" onclick="window.searchState.focus();">search
//! bar</a> at the top of the page.
//! button</a> at the top of the page.
//!
//! Otherwise, you may want to jump to one of these useful sections:
//!
Expand Down
9 changes: 6 additions & 3 deletions library/std/src/sync/lazy_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
#[inline]
#[stable(feature = "lazy_cell", since = "1.80.0")]
pub fn force(this: &LazyLock<T, F>) -> &T {
this.once.call_once(|| {
this.once.call_once_force(|state| {
if state.is_poisoned() {
panic_poisoned();
}

// SAFETY: `call_once` only runs this closure once, ever.
let data = unsafe { &mut *this.data.get() };
let f = unsafe { ManuallyDrop::take(&mut data.f) };
Expand All @@ -257,8 +261,7 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
// * the closure was called and initialized `value`.
// * the closure was called and panicked, so this point is never reached.
// * the closure was not called, but a previous call initialized `value`.
// * the closure was not called because the Once is poisoned, so this point
// is never reached.
// * the closure was not called because the Once is poisoned, which we handled above.
// So `value` has definitely been initialized and will not be modified again.
unsafe { &*(*this.data.get()).value }
}
Expand Down
Loading
Loading