From fed6279325de02dfa753b9e9a6d968f5a4ef428f Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Fri, 22 May 2026 14:45:46 +0200 Subject: [PATCH 1/2] library: use strict provenance lints consistently The `fuzzy_provenance_casts` lint is enabled in most of the standard library, but its identical twin `lossy_provenance_casts` was not. As discussed in the tracking issue for those lints, there doesn't seem to be any good reason to enable one without the other. This PR applies this principle and as a result removes some unnecessary ptr->int `as` casts. It's also preparation for merging the two lints, which removes the option of only enabling `fuzzy_provenance_casts`. --- library/alloc/src/lib.rs | 1 + library/alloctests/benches/lib.rs | 1 + library/alloctests/tests/boxed.rs | 4 ++-- library/alloctests/tests/heap.rs | 2 +- library/alloctests/tests/lib.rs | 1 + library/alloctests/tests/sort/tests.rs | 2 +- library/alloctests/tests/vec.rs | 8 ++++---- library/core/src/lib.rs | 1 + library/core/src/ptr/const_ptr.rs | 1 + library/core/src/ptr/mod.rs | 6 +++--- library/core/src/ptr/mut_ptr.rs | 1 + library/coretests/tests/char.rs | 4 ++-- library/coretests/tests/lib.rs | 1 + library/coretests/tests/ptr.rs | 2 +- library/coretests/tests/slice.rs | 2 +- library/coretests/tests/waker.rs | 6 +++--- library/std/src/lib.rs | 9 ++++++++- library/std/src/sync/mpmc/select.rs | 2 +- library/std/src/sync/mpmc/zero.rs | 2 +- library/std/src/sys/args/sgx.rs | 3 ++- library/std/src/sys/env/sgx.rs | 3 ++- library/std/src/sys/pal/sgx/mod.rs | 3 ++- library/std/src/sys/thread_local/key/tests.rs | 4 ++-- library/std/src/thread/tests.rs | 6 +++--- 24 files changed, 46 insertions(+), 29 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 5fe5464ab2cdd..e93c1336a7b53 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -73,6 +73,7 @@ // Lints: #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![warn(deprecated_in_future)] #![warn(missing_debug_implementations)] #![warn(missing_docs)] diff --git a/library/alloctests/benches/lib.rs b/library/alloctests/benches/lib.rs index b7e09fc2e162b..2be7a24d2de7b 100644 --- a/library/alloctests/benches/lib.rs +++ b/library/alloctests/benches/lib.rs @@ -7,6 +7,7 @@ #![feature(strict_provenance_lints)] #![feature(test)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] extern crate test; diff --git a/library/alloctests/tests/boxed.rs b/library/alloctests/tests/boxed.rs index 83fd1ef7449a3..41722155f864b 100644 --- a/library/alloctests/tests/boxed.rs +++ b/library/alloctests/tests/boxed.rs @@ -47,9 +47,9 @@ fn box_clone_from_ptr_stability() { for size in (0..8).map(|i| 2usize.pow(i)) { let control = vec![Dummy { _data: 42 }; size].into_boxed_slice(); let mut copy = vec![Dummy { _data: 84 }; size].into_boxed_slice(); - let copy_raw = copy.as_ptr() as usize; + let copy_raw = copy.as_ptr(); copy.clone_from(&control); - assert_eq!(copy.as_ptr() as usize, copy_raw); + assert_eq!(copy.as_ptr(), copy_raw); } } diff --git a/library/alloctests/tests/heap.rs b/library/alloctests/tests/heap.rs index 246b341eeb387..8eb562622c0a4 100644 --- a/library/alloctests/tests/heap.rs +++ b/library/alloctests/tests/heap.rs @@ -25,7 +25,7 @@ fn check_overalign_requests(allocator: T) { .collect(); for &ptr in &pointers { assert_eq!( - (ptr.as_non_null_ptr().as_ptr() as usize) % align, + ptr.as_non_null_ptr().as_ptr().addr() % align, 0, "Got a pointer less aligned than requested" ) diff --git a/library/alloctests/tests/lib.rs b/library/alloctests/tests/lib.rs index 699a5010282b0..01dd7a6e3f48d 100644 --- a/library/alloctests/tests/lib.rs +++ b/library/alloctests/tests/lib.rs @@ -43,6 +43,7 @@ #![feature(vec_try_remove)] #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] extern crate alloc; diff --git a/library/alloctests/tests/sort/tests.rs b/library/alloctests/tests/sort/tests.rs index 09b76773d6b24..ec4c4fa619ddf 100644 --- a/library/alloctests/tests/sort/tests.rs +++ b/library/alloctests/tests/sort/tests.rs @@ -746,7 +746,7 @@ fn self_cmp( pattern_fn(len).into_iter().map(|val| type_into_fn(val)).collect::>(); let comparison_fn = |a: &T, b: &T| { - assert_ne!(a as *const T as usize, b as *const T as usize); + assert_ne!(a as *const T, b as *const T); a.cmp(b) }; diff --git a/library/alloctests/tests/vec.rs b/library/alloctests/tests/vec.rs index d85d2e44cd2ba..7ae8c80963319 100644 --- a/library/alloctests/tests/vec.rs +++ b/library/alloctests/tests/vec.rs @@ -1110,7 +1110,7 @@ fn test_into_iter_zst() { struct AlignedZstWithDrop([u64; 0]); impl Drop for AlignedZstWithDrop { fn drop(&mut self) { - let addr = self as *mut _ as usize; + let addr = (self as *mut Self).addr(); assert!(hint::black_box(addr) % align_of::() == 0); } } @@ -1356,10 +1356,10 @@ fn overaligned_allocations() { for i in 0..0x1000 { v.reserve_exact(i); assert!(v[0].0 == 273); - assert!(v.as_ptr() as usize & 0xff == 0); + assert!(v.as_ptr().addr() & 0xff == 0); v.shrink_to_fit(); assert!(v[0].0 == 273); - assert!(v.as_ptr() as usize & 0xff == 0); + assert!(v.as_ptr().addr() & 0xff == 0); } } @@ -2574,7 +2574,7 @@ fn test_box_zero_allocator() { unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { if layout.size() == 0 { - let addr = ptr.as_ptr() as usize; + let addr = ptr.as_ptr().addr(); let mut state = self.state.borrow_mut(); std::println!("freeing {addr}"); assert!(state.0.remove(&addr), "ZST free that wasn't allocated"); diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index acc758a75e77b..9d9837c846de3 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -80,6 +80,7 @@ #![deny(rust_2021_incompatible_or_patterns)] #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![warn(deprecated_in_future)] #![warn(missing_debug_implementations)] #![warn(missing_docs)] diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 8b7b08bf82317..8504d9b5e217d 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -183,6 +183,7 @@ impl *const T { /// [`with_exposed_provenance`]: with_exposed_provenance #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] + #[expect(lossy_provenance_casts, reason = "this *is* the replacement")] pub fn expose_provenance(self) -> usize { self.cast::<()>() as usize } diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 15fc36332baca..89a054b7272d8 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -2588,21 +2588,21 @@ impl Ord for F { #[stable(feature = "fnptr_impls", since = "1.4.0")] impl hash::Hash for F { fn hash(&self, state: &mut HH) { - state.write_usize(self.addr() as _) + state.write_usize(self.addr().addr()) } } #[stable(feature = "fnptr_impls", since = "1.4.0")] impl fmt::Pointer for F { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::pointer_fmt_inner(self.addr() as _, f) + fmt::pointer_fmt_inner(self.addr().addr(), f) } } #[stable(feature = "fnptr_impls", since = "1.4.0")] impl fmt::Debug for F { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::pointer_fmt_inner(self.addr() as _, f) + fmt::pointer_fmt_inner(self.addr().addr(), f) } } diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 98b70a77fad7b..b9a1381c4ed8f 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -174,6 +174,7 @@ impl *mut T { /// [`with_exposed_provenance_mut`]: with_exposed_provenance_mut #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] + #[expect(lossy_provenance_casts, reason = "this *is* the replacement")] pub fn expose_provenance(self) -> usize { self.cast::<()>() as usize } diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index 877017f682c97..43372005ad5f3 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -318,7 +318,7 @@ fn test_encode_utf8() { let mut buf = [0; char::MAX_LEN_UTF8]; let ptr = buf.as_ptr(); let s = input.encode_utf8(&mut buf); - assert_eq!(s.as_ptr() as usize, ptr as usize); + assert_eq!(s.as_ptr(), ptr); assert!(str::from_utf8(s.as_bytes()).is_ok()); assert_eq!(s.as_bytes(), expect); } @@ -335,7 +335,7 @@ fn test_encode_utf16() { let mut buf = [0; 2]; let ptr = buf.as_mut_ptr(); let b = input.encode_utf16(&mut buf); - assert_eq!(b.as_mut_ptr() as usize, ptr as usize); + assert_eq!(b.as_mut_ptr(), ptr); assert_eq!(b, expect); } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 18e09c707ebad..64c53756acead 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -129,6 +129,7 @@ // tidy-alphabetical-end #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] /// Version of `assert_matches` that ignores fancy runtime printing in const context and uses structural equality. diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs index 93f9454d71378..9dbaf6690c81c 100644 --- a/library/coretests/tests/ptr.rs +++ b/library/coretests/tests/ptr.rs @@ -384,7 +384,7 @@ fn align_offset_stride_one() { #[test] fn align_offset_various_strides() { unsafe fn test_stride(ptr: *const T, align: usize) -> bool { - let numptr = ptr as usize; + let numptr = ptr.addr(); let mut expected = usize::MAX; // Naive but definitely correct way to find the *first* aligned element of stride::. for el in 0..align { diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index 2bb62f36bb0e6..a4db7304fff90 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1886,7 +1886,7 @@ fn test_align_to_empty_mid() { type Chunk = u32; for offset in 0..4 { let (_, mid, _) = unsafe { bytes[offset..offset + 1].align_to::() }; - assert_eq!(mid.as_ptr() as usize % align_of::(), 0); + assert_eq!(mid.as_ptr().addr() % align_of::(), 0); } } diff --git a/library/coretests/tests/waker.rs b/library/coretests/tests/waker.rs index 4889b8959ece4..be8b07b8ad009 100644 --- a/library/coretests/tests/waker.rs +++ b/library/coretests/tests/waker.rs @@ -5,16 +5,16 @@ use std::task::{RawWaker, RawWakerVTable, Waker}; fn test_waker_getters() { let raw_waker = RawWaker::new(ptr::without_provenance_mut(42usize), &WAKER_VTABLE); let waker = unsafe { Waker::from_raw(raw_waker) }; - assert_eq!(waker.data() as usize, 42); + assert_eq!(waker.data().addr(), 42); assert!(ptr::eq(waker.vtable(), &WAKER_VTABLE)); let waker2 = waker.clone(); - assert_eq!(waker2.data() as usize, 43); + assert_eq!(waker2.data().addr(), 43); assert!(ptr::eq(waker2.vtable(), &WAKER_VTABLE)); } static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new( - |data| RawWaker::new(ptr::without_provenance_mut(data as usize + 1), &WAKER_VTABLE), + |data| RawWaker::new(ptr::without_provenance_mut(data.addr() + 1), &WAKER_VTABLE), |_| {}, |_| {}, |_| {}, diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index cb0f8edb7b852..e2fe7d4aa5448 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -246,6 +246,7 @@ #![allow(unused_lifetimes)] #![allow(internal_features)] #![deny(fuzzy_provenance_casts)] +#![deny(lossy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] #![allow(rustdoc::redundant_explicit_links)] #![warn(rustdoc::unescaped_backticks)] @@ -714,7 +715,13 @@ pub mod alloc; mod panicking; #[path = "../../backtrace/src/lib.rs"] -#[allow(dead_code, unused_attributes, fuzzy_provenance_casts, unsafe_op_in_unsafe_fn)] +#[allow( + dead_code, + unused_attributes, + fuzzy_provenance_casts, + lossy_provenance_casts, + unsafe_op_in_unsafe_fn +)] mod backtrace_rs; #[stable(feature = "cfg_select", since = "1.95.0")] diff --git a/library/std/src/sync/mpmc/select.rs b/library/std/src/sync/mpmc/select.rs index 56a83fee2e119..ff537aa686157 100644 --- a/library/std/src/sync/mpmc/select.rs +++ b/library/std/src/sync/mpmc/select.rs @@ -22,7 +22,7 @@ impl Operation { /// and is alive for the entire duration of a blocking operation. #[inline] pub fn hook(r: &mut T) -> Operation { - let val = r as *mut T as usize; + let val = (r as *mut T).addr(); // Make sure that the pointer address doesn't equal the numerical representation of // `Selected::{Waiting, Aborted, Disconnected}`. assert!(val > 2); diff --git a/library/std/src/sync/mpmc/zero.rs b/library/std/src/sync/mpmc/zero.rs index c743462501922..4f645e16fbb6f 100644 --- a/library/std/src/sync/mpmc/zero.rs +++ b/library/std/src/sync/mpmc/zero.rs @@ -25,7 +25,7 @@ impl Default for ZeroToken { impl fmt::Debug for ZeroToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&(self.0 as usize), f) + fmt::Debug::fmt(&self.0.addr(), f) } } diff --git a/library/std/src/sys/args/sgx.rs b/library/std/src/sys/args/sgx.rs index 6ff94f5681b6f..9403059e7c607 100644 --- a/library/std/src/sys/args/sgx.rs +++ b/library/std/src/sys/args/sgx.rs @@ -1,4 +1,5 @@ -#![allow(fuzzy_provenance_casts)] // FIXME: this module systematically confuses pointers and integers +// FIXME: this module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] use crate::ffi::OsString; use crate::num::NonZero; diff --git a/library/std/src/sys/env/sgx.rs b/library/std/src/sys/env/sgx.rs index 09090ec7cf0dd..0c19fcc848f4d 100644 --- a/library/std/src/sys/env/sgx.rs +++ b/library/std/src/sys/env/sgx.rs @@ -1,4 +1,5 @@ -#![allow(fuzzy_provenance_casts)] // FIXME: this module systematically confuses pointers and integers +// FIXME: this module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] pub use super::common::Env; use crate::collections::HashMap; diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 2b284cc40b94b..7b2c8e5a8024a 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -3,7 +3,8 @@ //! This module contains the facade (aka platform-specific) implementations of //! OS level functionality for Fortanix SGX. #![deny(unsafe_op_in_unsafe_fn)] -#![allow(fuzzy_provenance_casts)] // FIXME: this entire module systematically confuses pointers and integers +// FIXME: this entire module systematically confuses pointers and integers +#![allow(fuzzy_provenance_casts, lossy_provenance_casts)] use crate::io; use crate::sync::atomic::{Atomic, AtomicBool, Ordering}; diff --git a/library/std/src/sys/thread_local/key/tests.rs b/library/std/src/sys/thread_local/key/tests.rs index c7d2c8e6301ef..5e5243d9835ed 100644 --- a/library/std/src/sys/thread_local/key/tests.rs +++ b/library/std/src/sys/thread_local/key/tests.rs @@ -18,8 +18,8 @@ fn smoke() { assert!(get(k2).is_null()); set(k1, ptr::without_provenance_mut(1)); set(k2, ptr::without_provenance_mut(2)); - assert_eq!(get(k1) as usize, 1); - assert_eq!(get(k2) as usize, 2); + assert_eq!(get(k1).addr(), 1); + assert_eq!(get(k2).addr(), 2); } } diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 4b934c039a36f..78b6f7c35e8db 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -152,11 +152,11 @@ where { let (tx, rx) = channel(); - let x: Box<_> = Box::new(1); - let x_in_parent = (&*x) as *const i32 as usize; + let x: Box = Box::new(1); + let x_in_parent = (&raw const *x).addr(); spawnfn(Box::new(move || { - let x_in_child = (&*x) as *const i32 as usize; + let x_in_child = (&raw const *x).addr(); tx.send(x_in_child).unwrap(); })); From 732998ab95c0179eb2b588acf7b05f68f35c9829 Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Sun, 31 May 2026 09:54:38 +0200 Subject: [PATCH 2/2] allow target-dependent int2ptr cast for pthread_t --- library/std/src/os/unix/thread.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/std/src/os/unix/thread.rs b/library/std/src/os/unix/thread.rs index 32085e525942e..efb349b18d6b5 100644 --- a/library/std/src/os/unix/thread.rs +++ b/library/std/src/os/unix/thread.rs @@ -31,10 +31,15 @@ pub trait JoinHandleExt { #[stable(feature = "thread_extensions", since = "1.9.0")] impl JoinHandleExt for JoinHandle { + // This is an int2ptr cast on some platforms (e.g., *-musl) where RawPthread + // is an integer but libc::pthread_t is a pointer. Exposed provenance is the + // safe choice here, but `as` also works when it's int2int or ptr2ptr. + #[allow(lossy_provenance_casts)] fn as_pthread_t(&self) -> RawPthread { self.as_inner().id() as RawPthread } + #[allow(lossy_provenance_casts)] // see above for why fn into_pthread_t(self) -> RawPthread { self.into_inner().into_id() as RawPthread }