From 1bbabb7ff744f18edaf3da30f841a287de2077d6 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 4 Apr 2024 23:36:13 +0100 Subject: [PATCH 01/21] std::net: adding `unix_socket_exclbind` feature for solaris/illumos. allows to have a tigher control over the binding exclusivness of the socket. --- library/std/src/os/illumos/mod.rs | 1 + library/std/src/os/illumos/net.rs | 50 +++++++++++++++++++ library/std/src/os/solaris/mod.rs | 1 + library/std/src/os/solaris/net.rs | 50 +++++++++++++++++++ .../std/src/sys/net/connection/socket/unix.rs | 15 ++++++ 5 files changed, 117 insertions(+) create mode 100644 library/std/src/os/illumos/net.rs create mode 100644 library/std/src/os/solaris/net.rs diff --git a/library/std/src/os/illumos/mod.rs b/library/std/src/os/illumos/mod.rs index e61926f89356..5fbe352e7cfb 100644 --- a/library/std/src/os/illumos/mod.rs +++ b/library/std/src/os/illumos/mod.rs @@ -3,4 +3,5 @@ #![stable(feature = "raw_ext", since = "1.1.0")] pub mod fs; +pub mod net; pub mod raw; diff --git a/library/std/src/os/illumos/net.rs b/library/std/src/os/illumos/net.rs new file mode 100644 index 000000000000..5ef4e1ec89e3 --- /dev/null +++ b/library/std/src/os/illumos/net.rs @@ -0,0 +1,50 @@ +//! illumos-specific networking functionality. + +#![unstable(feature = "unix_socket_exclbind", issue = "123481")] + +use crate::io; +use crate::os::unix::net; +use crate::sealed::Sealed; +use crate::sys_common::AsInner; + +/// illumos-specific functionality for `AF_UNIX` sockets [`UnixDatagram`] +/// and [`UnixStream`]. +/// +/// [`UnixDatagram`]: net::UnixDatagram +/// [`UnixStream`]: net::UnixStream +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +pub trait UnixSocketExt: Sealed { + /// Enables exclusive binding on the socket. + /// + /// If true and if the socket had been set with `SO_REUSEADDR`, + /// it neutralises its effect. + /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html) + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn so_exclbind(&self, excl: bool) -> io::Result<()>; + + /// Get the bind exclusivity bind state of the socket. + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn exclbind(&self) -> io::Result; +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixDatagram { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixStream { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} diff --git a/library/std/src/os/solaris/mod.rs b/library/std/src/os/solaris/mod.rs index e4cfd53291a6..b4e836208491 100644 --- a/library/std/src/os/solaris/mod.rs +++ b/library/std/src/os/solaris/mod.rs @@ -3,4 +3,5 @@ #![stable(feature = "raw_ext", since = "1.1.0")] pub mod fs; +pub mod net; pub mod raw; diff --git a/library/std/src/os/solaris/net.rs b/library/std/src/os/solaris/net.rs new file mode 100644 index 000000000000..ca841f15b0e7 --- /dev/null +++ b/library/std/src/os/solaris/net.rs @@ -0,0 +1,50 @@ +//! solaris-specific networking functionality. + +#![unstable(feature = "unix_socket_exclbind", issue = "123481")] + +use crate::io; +use crate::os::unix::net; +use crate::sealed::Sealed; +use crate::sys_common::AsInner; + +/// solaris-specific functionality for `AF_UNIX` sockets [`UnixDatagram`] +/// and [`UnixStream`]. +/// +/// [`UnixDatagram`]: net::UnixDatagram +/// [`UnixStream`]: net::UnixStream +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +pub trait UnixSocketExt: Sealed { + /// Enables exclusive binding on the socket. + /// + /// If true and if the socket had been set with `SO_REUSEADDR`, + /// it neutralises its effect. + /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html) + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn so_exclbind(&self, excl: bool) -> io::Result<()>; + + /// Get the bind exclusivity bind state of the socket. + #[unstable(feature = "unix_socket_exclbind", issue = "123481")] + fn exclbind(&self) -> io::Result; +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixDatagram { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} + +#[unstable(feature = "unix_socket_exclbind", issue = "123481")] +impl UnixSocketExt for net::UnixStream { + fn exclbind(&self) -> io::Result { + self.as_inner().exclbind() + } + + fn so_exclbind(&self, excl: bool) -> io::Result<()> { + self.as_inner().set_exclbind(excl) + } +} diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index b35d5d2aa841..37d4078b9639 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -514,6 +514,21 @@ impl Socket { Ok(name) } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub fn set_exclbind(&self, excl: bool) -> io::Result<()> { + // not yet on libc crate + const SO_EXCLBIND: i32 = 0x1015; + setsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND, excl) + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub fn exclbind(&self) -> io::Result { + // not yet on libc crate + const SO_EXCLBIND: i32 = 0x1015; + let raw: c_int = getsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND)?; + Ok(raw != 0) + } + #[cfg(any(target_os = "android", target_os = "linux",))] pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int) From f3383e4942bca64ce523b7736a2bedd1b8426c11 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 12 Jun 2025 10:56:43 +0000 Subject: [PATCH 02/21] Do not include NUL-terminator in computed length --- .../src/util/caller_location.rs | 12 +++---- library/core/src/panic/location.rs | 35 ++++++++++++------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 671214002a0d..a51a5751dc47 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -22,13 +22,14 @@ fn alloc_caller_location<'tcx>( assert!(!filename.as_str().as_bytes().contains(&0)); let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail; - let file_wide_ptr = { + let filename = { let filename = if loc_details.file { filename.as_str() } else { "" }; let filename_with_nul = filename.to_owned() + "\0"; // This can fail if rustc runs out of memory right here. Trying to emit an error would be // pointless, since that would require allocating more memory than these short strings. let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap(); - Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx) + let file_len = u64::try_from(filename.len()).unwrap(); + Immediate::new_slice(file_ptr.into(), file_len, ecx) }; let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) }; @@ -42,11 +43,8 @@ fn alloc_caller_location<'tcx>( let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); // Initialize fields. - ecx.write_immediate( - file_wide_ptr, - &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap(), - ) - .expect("writing to memory we just allocated cannot fail"); + ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap()) + .expect("writing to memory we just allocated cannot fail"); ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap()) .expect("writing to memory we just allocated cannot fail"); ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap()) diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs index f1eedede8aab..1e950d6e4988 100644 --- a/library/core/src/panic/location.rs +++ b/library/core/src/panic/location.rs @@ -1,5 +1,6 @@ use crate::ffi::CStr; use crate::fmt; +use crate::marker::PhantomData; /// A struct containing information about the location of a panic. /// @@ -33,14 +34,13 @@ use crate::fmt; #[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] #[stable(feature = "panic_hooks", since = "1.10.0")] pub struct Location<'a> { - // Note: this filename will have exactly one nul byte at its end, but otherwise - // it must never contain interior nul bytes. This is relied on for the conversion - // to `CStr` below. - // - // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`. - file_bytes_with_nul: &'a [u8], + // A raw pointer is used rather than a reference because the pointer is valid for one more byte + // than the length stored in this pointer; the additional byte is the NUL-terminator used by + // `Location::file_with_nul`. + filename: *const str, line: u32, col: u32, + _filename: PhantomData<&'a str>, } #[stable(feature = "panic_hooks", since = "1.10.0")] @@ -143,10 +143,8 @@ impl<'a> Location<'a> { #[stable(feature = "panic_hooks", since = "1.10.0")] #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")] pub const fn file(&self) -> &str { - let str_len = self.file_bytes_with_nul.len() - 1; - // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be - // valid UTF8. - unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) } + // SAFETY: The filename is valid. + unsafe { &*self.filename } } /// Returns the name of the source file as a nul-terminated `CStr`. @@ -157,9 +155,15 @@ impl<'a> Location<'a> { #[unstable(feature = "file_with_nul", issue = "141727")] #[inline] pub const fn file_with_nul(&self) -> &CStr { - // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no - // interior nul bytes. - unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) } + // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't + // overflow. + let cstr_len = unsafe { crate::mem::size_of_val_raw(self.filename).unchecked_add(1) }; + + // SAFETY: The filename is valid for `filename_len+1` bytes. + let slice = unsafe { crate::slice::from_raw_parts(self.filename as *const _, cstr_len) }; + + // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes. + unsafe { CStr::from_bytes_with_nul_unchecked(slice) } } /// Returns the line number from which the panic originated. @@ -220,3 +224,8 @@ impl fmt::Display for Location<'_> { write!(formatter, "{}:{}:{}", self.file(), self.line, self.col) } } + +#[stable(feature = "panic_hooks", since = "1.10.0")] +unsafe impl Send for Location<'_> {} +#[stable(feature = "panic_hooks", since = "1.10.0")] +unsafe impl Sync for Location<'_> {} From 09aab29ebf93fbb546d409d1e127392abf36cd8a Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 17 Jun 2025 13:24:38 +0000 Subject: [PATCH 03/21] Only compute recursive callees once. --- compiler/rustc_middle/src/query/mod.rs | 11 +- compiler/rustc_mir_transform/src/inline.rs | 2 +- .../rustc_mir_transform/src/inline/cycle.rs | 283 ++++++++++-------- compiler/rustc_mir_transform/src/lib.rs | 2 +- 4 files changed, 158 insertions(+), 140 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 3668f4e12f5d..de3b15c5a6f6 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1283,14 +1283,13 @@ rustc_queries! { return_result_from_ensure_ok } - /// Check whether the function has any recursion that could cause the inliner to trigger - /// a cycle. - query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool { + /// Return the set of (transitive) callees that may result in a recursive call to `key`. + query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet> { fatal_cycle + arena_cache desc { |tcx| - "computing if `{}` (transitively) calls `{}`", - key.0, - tcx.def_path_str(key.1), + "computing (transitive) callees of `{}` that may recurse", + tcx.def_path_str(key), } } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index f48dba9663a5..0972e95a238b 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -777,7 +777,7 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>( { // If we know for sure that the function we're calling will itself try to // call us, then we avoid inlining that function. - if inliner.tcx().mir_callgraph_reachable((callee, caller_def_id.expect_local())) { + if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee) { debug!("query cycle avoidance"); return Err("caller might be reachable from callee"); } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index a944960ce4ab..4ef41b9ba447 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -1,5 +1,6 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::mir::TerminatorKind; use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt}; @@ -7,137 +8,139 @@ use rustc_session::Limit; use rustc_span::sym; use tracing::{instrument, trace}; -// FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking -// this query ridiculously often. -#[instrument(level = "debug", skip(tcx, root, target))] -pub(crate) fn mir_callgraph_reachable<'tcx>( - tcx: TyCtxt<'tcx>, - (root, target): (ty::Instance<'tcx>, LocalDefId), -) -> bool { - trace!(%root, target = %tcx.def_path_str(target)); - assert_ne!( - root.def_id().expect_local(), - target, - "you should not call `mir_callgraph_reachable` on immediate self recursion" - ); - assert!( - matches!(root.def, InstanceKind::Item(_)), - "you should not call `mir_callgraph_reachable` on shims" - ); - assert!( - !tcx.is_constructor(root.def_id()), - "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" - ); - #[instrument( - level = "debug", - skip(tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit) - )] - fn process<'tcx>( - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - caller: ty::Instance<'tcx>, - target: LocalDefId, - stack: &mut Vec>, - seen: &mut FxHashSet>, - recursion_limiter: &mut FxHashMap, - recursion_limit: Limit, - ) -> bool { - trace!(%caller); - for &(callee, args) in tcx.mir_inliner_callees(caller.def) { - let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions( - tcx, - typing_env, - ty::EarlyBinder::bind(args), - ) else { - trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping"); - continue; - }; - let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else { - trace!(?callee, "cannot resolve, skipping"); - continue; - }; - - // Found a path. - if callee.def_id() == target.to_def_id() { - return true; - } - - if tcx.is_constructor(callee.def_id()) { - trace!("constructors always have MIR"); - // Constructor functions cannot cause a query cycle. - continue; - } - - match callee.def { - InstanceKind::Item(_) => { - // If there is no MIR available (either because it was not in metadata or - // because it has no MIR because it's an extern function), then the inliner - // won't cause cycles on this. - if !tcx.is_mir_available(callee.def_id()) { - trace!(?callee, "no mir available, skipping"); - continue; - } - } - // These have no own callable MIR. - InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => continue, - // These have MIR and if that MIR is inlined, instantiated and then inlining is run - // again, a function item can end up getting inlined. Thus we'll be able to cause - // a cycle that way - InstanceKind::VTableShim(_) - | InstanceKind::ReifyShim(..) - | InstanceKind::FnPtrShim(..) - | InstanceKind::ClosureOnceShim { .. } - | InstanceKind::ConstructCoroutineInClosureShim { .. } - | InstanceKind::ThreadLocalShim { .. } - | InstanceKind::CloneShim(..) => {} - - // This shim does not call any other functions, thus there can be no recursion. - InstanceKind::FnPtrAddrShim(..) => { - continue; - } - InstanceKind::DropGlue(..) - | InstanceKind::FutureDropPollShim(..) - | InstanceKind::AsyncDropGlue(..) - | InstanceKind::AsyncDropGlueCtorShim(..) => { - // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to - // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this - // needs some more analysis. - if callee.has_param() { - continue; - } - } - } - - if seen.insert(callee) { - let recursion = recursion_limiter.entry(callee.def_id()).or_default(); - trace!(?callee, recursion = *recursion); - if recursion_limit.value_within_limit(*recursion) { - *recursion += 1; - stack.push(callee); - let found_recursion = ensure_sufficient_stack(|| { - process( - tcx, - typing_env, - callee, - target, - stack, - seen, - recursion_limiter, - recursion_limit, - ) - }); - if found_recursion { - return true; - } - stack.pop(); - } else { - // Pessimistically assume that there could be recursion. - return true; - } +fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool { + match callee.def { + // If there is no MIR available (either because it was not in metadata or + // because it has no MIR because it's an extern function), then the inliner + // won't cause cycles on this. + InstanceKind::Item(_) => { + if !tcx.is_mir_available(callee.def_id()) { + return false; + } + } + + // These have no own callable MIR. + InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => return false, + + // These have MIR and if that MIR is inlined, instantiated and then inlining is run + // again, a function item can end up getting inlined. Thus we'll be able to cause + // a cycle that way + InstanceKind::VTableShim(_) + | InstanceKind::ReifyShim(..) + | InstanceKind::FnPtrShim(..) + | InstanceKind::ClosureOnceShim { .. } + | InstanceKind::ConstructCoroutineInClosureShim { .. } + | InstanceKind::ThreadLocalShim { .. } + | InstanceKind::CloneShim(..) => {} + + // This shim does not call any other functions, thus there can be no recursion. + InstanceKind::FnPtrAddrShim(..) => return false, + + // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to + // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this + // needs some more analysis. + InstanceKind::DropGlue(..) + | InstanceKind::FutureDropPollShim(..) + | InstanceKind::AsyncDropGlue(..) + | InstanceKind::AsyncDropGlueCtorShim(..) => { + if callee.has_param() { + return false; } } - false } + + crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed) + || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id()) +} + +#[instrument( + level = "debug", + skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit), + ret +)] +fn process<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + caller: ty::Instance<'tcx>, + target: LocalDefId, + seen: &mut FxHashSet>, + involved: &mut FxHashSet>, + recursion_limiter: &mut FxHashMap, + recursion_limit: Limit, +) -> bool { + trace!(%caller); + let mut cycle_found = false; + + for &(callee, args) in tcx.mir_inliner_callees(caller.def) { + let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions( + tcx, + typing_env, + ty::EarlyBinder::bind(args), + ) else { + trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping"); + continue; + }; + let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else { + trace!(?callee, "cannot resolve, skipping"); + continue; + }; + + // Found a path. + if callee.def_id() == target.to_def_id() { + cycle_found = true; + } + + if tcx.is_constructor(callee.def_id()) { + trace!("constructors always have MIR"); + // Constructor functions cannot cause a query cycle. + continue; + } + + if !should_recurse(tcx, callee) { + continue; + } + + if seen.insert(callee) { + let recursion = recursion_limiter.entry(callee.def_id()).or_default(); + trace!(?callee, recursion = *recursion); + let found_recursion = if recursion_limit.value_within_limit(*recursion) { + *recursion += 1; + ensure_sufficient_stack(|| { + process( + tcx, + typing_env, + callee, + target, + seen, + involved, + recursion_limiter, + recursion_limit, + ) + }) + } else { + // Pessimistically assume that there could be recursion. + true + }; + if found_recursion { + involved.insert(callee); + cycle_found = true; + } + } + } + + cycle_found +} + +#[instrument(level = "debug", skip(tcx), ret)] +pub(crate) fn mir_callgraph_cyclic<'tcx>( + tcx: TyCtxt<'tcx>, + root: LocalDefId, +) -> UnordSet> { + assert!( + !tcx.is_constructor(root.to_def_id()), + "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" + ); + // FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error. // In code like that pointed out in #128887, the type complexity we ask the solver to deal with // grows as we recurse into the call graph. If we use the same recursion limit here and in the @@ -146,16 +149,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>( // the default recursion limits are quite generous for us. If we need to recurse 64 times // into the call graph, we're probably not going to find any useful MIR inlining. let recursion_limit = tcx.recursion_limit() / 2; + let mut involved = FxHashSet::default(); + let typing_env = ty::TypingEnv::post_analysis(tcx, root); + let Ok(Some(root_instance)) = ty::Instance::try_resolve( + tcx, + typing_env, + root.to_def_id(), + ty::GenericArgs::identity_for_item(tcx, root.to_def_id()), + ) else { + trace!("cannot resolve, skipping"); + return involved.into(); + }; + if !should_recurse(tcx, root_instance) { + trace!("cannot walk, skipping"); + return involved.into(); + } process( tcx, - ty::TypingEnv::post_analysis(tcx, target), + typing_env, + root_instance, root, - target, - &mut Vec::new(), &mut FxHashSet::default(), + &mut involved, &mut FxHashMap::default(), recursion_limit, - ) + ); + involved.into() } pub(crate) fn mir_inliner_callees<'tcx>( diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 572ad585c8c8..48a4fed3993a 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -215,7 +215,7 @@ pub fn provide(providers: &mut Providers) { optimized_mir, is_mir_available, is_ctfe_mir_available: is_mir_available, - mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable, + mir_callgraph_cyclic: inline::cycle::mir_callgraph_cyclic, mir_inliner_callees: inline::cycle::mir_inliner_callees, promoted_mir, deduced_param_attrs: deduce_param_attrs::deduced_param_attrs, From 8a35c7ac5b9b3d311253f2935a4b68ab293dc960 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Wed, 18 Jun 2025 11:38:58 +0000 Subject: [PATCH 04/21] Cache queries. --- compiler/rustc_middle/src/query/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index de3b15c5a6f6..cfd15c990436 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1291,6 +1291,7 @@ rustc_queries! { "computing (transitive) callees of `{}` that may recurse", tcx.def_path_str(key), } + cache_on_disk_if { true } } /// Obtain all the calls into other local functions From 2074013f44e4b12a0949182d050d3470bd76e4de Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Thu, 19 Jun 2025 08:47:39 +0000 Subject: [PATCH 05/21] Only store the LocalDefId instead of the whole instance. --- compiler/rustc_middle/src/query/mod.rs | 2 +- compiler/rustc_mir_transform/src/inline.rs | 5 +++-- compiler/rustc_mir_transform/src/inline/cycle.rs | 10 +++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index cfd15c990436..9fdc20961dea 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1284,7 +1284,7 @@ rustc_queries! { } /// Return the set of (transitive) callees that may result in a recursive call to `key`. - query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet> { + query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet { fatal_cycle arena_cache desc { |tcx| diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 0972e95a238b..c27087fea11f 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -770,14 +770,15 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>( return Ok(()); } - if callee_def_id.is_local() + if let Some(callee_def_id) = callee_def_id.as_local() && !inliner .tcx() .is_lang_item(inliner.tcx().parent(caller_def_id), rustc_hir::LangItem::FnOnce) { // If we know for sure that the function we're calling will itself try to // call us, then we avoid inlining that function. - if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee) { + if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id) + { debug!("query cycle avoidance"); return Err("caller might be reachable from callee"); } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index 4ef41b9ba447..08f3ce5fd67e 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -8,6 +8,7 @@ use rustc_session::Limit; use rustc_span::sym; use tracing::{instrument, trace}; +#[instrument(level = "debug", skip(tcx), ret)] fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool { match callee.def { // If there is no MIR available (either because it was not in metadata or @@ -64,7 +65,7 @@ fn process<'tcx>( caller: ty::Instance<'tcx>, target: LocalDefId, seen: &mut FxHashSet>, - involved: &mut FxHashSet>, + involved: &mut FxHashSet, recursion_limiter: &mut FxHashMap, recursion_limit: Limit, ) -> bool { @@ -122,7 +123,10 @@ fn process<'tcx>( true }; if found_recursion { - involved.insert(callee); + if let Some(callee) = callee.def_id().as_local() { + // Calling `optimized_mir` of a non-local definition cannot cycle. + involved.insert(callee); + } cycle_found = true; } } @@ -135,7 +139,7 @@ fn process<'tcx>( pub(crate) fn mir_callgraph_cyclic<'tcx>( tcx: TyCtxt<'tcx>, root: LocalDefId, -) -> UnordSet> { +) -> UnordSet { assert!( !tcx.is_constructor(root.to_def_id()), "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" From 6a1b7df4854b0023e34b18e2196d3069906da796 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 23 Jun 2025 13:45:56 +0000 Subject: [PATCH 06/21] Use a NonNull pointer --- library/core/src/panic/location.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs index 1e950d6e4988..972270208852 100644 --- a/library/core/src/panic/location.rs +++ b/library/core/src/panic/location.rs @@ -1,6 +1,7 @@ use crate::ffi::CStr; use crate::fmt; use crate::marker::PhantomData; +use crate::ptr::NonNull; /// A struct containing information about the location of a panic. /// @@ -37,7 +38,7 @@ pub struct Location<'a> { // A raw pointer is used rather than a reference because the pointer is valid for one more byte // than the length stored in this pointer; the additional byte is the NUL-terminator used by // `Location::file_with_nul`. - filename: *const str, + filename: NonNull, line: u32, col: u32, _filename: PhantomData<&'a str>, @@ -144,7 +145,7 @@ impl<'a> Location<'a> { #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")] pub const fn file(&self) -> &str { // SAFETY: The filename is valid. - unsafe { &*self.filename } + unsafe { self.filename.as_ref() } } /// Returns the name of the source file as a nul-terminated `CStr`. @@ -155,12 +156,14 @@ impl<'a> Location<'a> { #[unstable(feature = "file_with_nul", issue = "141727")] #[inline] pub const fn file_with_nul(&self) -> &CStr { + let filename = self.filename.as_ptr(); + // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't // overflow. - let cstr_len = unsafe { crate::mem::size_of_val_raw(self.filename).unchecked_add(1) }; + let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) }; // SAFETY: The filename is valid for `filename_len+1` bytes. - let slice = unsafe { crate::slice::from_raw_parts(self.filename as *const _, cstr_len) }; + let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) }; // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes. unsafe { CStr::from_bytes_with_nul_unchecked(slice) } From e0f8e865dbf9f1dc9d1d917efc04320e2eb123f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 24 Jun 2025 15:51:08 +0200 Subject: [PATCH 07/21] Skip unnecessary components in x64 try builds --- src/tools/opt-dist/src/exec.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index 64ce5cc37752..75569eacacde 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -113,7 +113,7 @@ impl Bootstrap { "library/std", ]) .env("RUST_BACKTRACE", "full"); - let cmd = add_shared_x_flags(env, cmd); + let mut cmd = add_shared_x_flags(env, cmd); Self { cmd, metrics_path } } @@ -122,7 +122,12 @@ impl Bootstrap { let metrics_path = env.build_root().join("build").join("metrics.json"); let args = dist_args.iter().map(|arg| arg.as_str()).collect::>(); let cmd = cmd(&args).env("RUST_BACKTRACE", "full"); - let cmd = add_shared_x_flags(env, cmd); + let mut cmd = add_shared_x_flags(env, cmd); + if env.is_fast_try_build() { + // We set build.extended=false for fast try builds, but we still need Cargo + cmd = cmd.arg("cargo"); + } + Self { cmd, metrics_path } } @@ -188,6 +193,19 @@ impl Bootstrap { } } -fn add_shared_x_flags(env: &Environment, cmd: CmdBuilder) -> CmdBuilder { - if env.is_fast_try_build() { cmd.arg("--set").arg("rust.deny-warnings=false") } else { cmd } +fn add_shared_x_flags(env: &Environment, mut cmd: CmdBuilder) -> CmdBuilder { + if env.is_fast_try_build() { + // Skip things that cannot be skipped through `x ... --skip` + cmd.arg("--set") + .arg("rust.llvm-bitcode-linker=false") + // Skip wasm-component-ld. This also skips cargo, which we need to re-enable for dist + .arg("--set") + .arg("build.extended=false") + .arg("--set") + .arg("rust.codegen-backends=['llvm']") + .arg("--set") + .arg("rust.deny-warnings=false") + } else { + cmd + } } From 25dee4e56e0ad075044e7e465ae513086c78f45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 24 Jun 2025 15:57:25 +0200 Subject: [PATCH 08/21] Skip more dist components --- src/tools/opt-dist/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs index d2827ec01ca7..9c8a6637a3b1 100644 --- a/src/tools/opt-dist/src/main.rs +++ b/src/tools/opt-dist/src/main.rs @@ -407,13 +407,18 @@ fn main() -> anyhow::Result<()> { for target in [ "rust-docs", "rustc-docs", + "rustc-dev", + "rust-dev", "rust-docs-json", "rust-analyzer", "rustc-src", + "extended", "clippy", "miri", "rustfmt", "gcc", + "generate-copyright", + "bootstrap", ] { build_args.extend(["--skip".to_string(), target.to_string()]); } From 803415478cfc69d9e6c02b313560d6a2e433e0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 24 Jun 2025 16:04:56 +0200 Subject: [PATCH 09/21] Do not build GCC in fast try builds --- src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh index 064ac5b0a5e4..924bdbc76150 100755 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh @@ -10,4 +10,7 @@ python3 ../x.py build --set rust.debug=true opt-dist build-manifest bootstrap # Use GCC for building GCC, as it seems to behave badly when built with Clang -CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc +# Only build GCC on full builds, not try builds +if [ "${DIST_TRY_BUILD:-0}" == "0" ]; then + CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc +fi From 58bc1dcb73b75d8fd0117d76e9a9f61ff297cd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 25 Jun 2025 16:24:30 +0200 Subject: [PATCH 10/21] Remove `mut` --- src/tools/opt-dist/src/exec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index 75569eacacde..0dc6e56b9d5f 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -113,7 +113,7 @@ impl Bootstrap { "library/std", ]) .env("RUST_BACKTRACE", "full"); - let mut cmd = add_shared_x_flags(env, cmd); + let cmd = add_shared_x_flags(env, cmd); Self { cmd, metrics_path } } @@ -193,7 +193,7 @@ impl Bootstrap { } } -fn add_shared_x_flags(env: &Environment, mut cmd: CmdBuilder) -> CmdBuilder { +fn add_shared_x_flags(env: &Environment, cmd: CmdBuilder) -> CmdBuilder { if env.is_fast_try_build() { // Skip things that cannot be skipped through `x ... --skip` cmd.arg("--set") From d0321192532f3b32e187e43a183c92c31fcfe83a Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 10 Jun 2025 21:09:35 +0000 Subject: [PATCH 11/21] Don't fold in Instantiate when there's nothing to fold --- compiler/rustc_type_ir/src/binder.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 927a2ce84ea7..a7b915c48455 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -622,6 +622,17 @@ impl> ty::EarlyBinder { where A: SliceLike, { + // Nothing to fold, so let's avoid visiting things and possibly re-hashing/equating + // them when interning. Perf testing found this to be a modest improvement. + // See: + if args.is_empty() { + assert!( + !self.value.has_param(), + "{:?} has parameters, but no args were provided in instantiate", + self.value, + ); + return self.value; + } let mut folder = ArgFolder { cx, args: args.as_slice(), binders_passed: 0 }; self.value.fold_with(&mut folder) } From 508021aa4320f1feb4c7a783ee16c2feebc9249a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Thu, 26 Jun 2025 01:31:12 +0200 Subject: [PATCH 12/21] Add windows-gnullvm hosts to the manifest --- src/tools/build-manifest/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 4c53ea42793c..0520eff0fa27 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -14,6 +14,7 @@ use crate::versions::{PkgType, Versions}; static HOSTS: &[&str] = &[ "aarch64-apple-darwin", + "aarch64-pc-windows-gnullvm", "aarch64-pc-windows-msvc", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", @@ -44,6 +45,7 @@ static HOSTS: &[&str] = &[ "x86_64-apple-darwin", "x86_64-pc-solaris", "x86_64-pc-windows-gnu", + "x86_64-pc-windows-gnullvm", "x86_64-pc-windows-msvc", "x86_64-unknown-freebsd", "x86_64-unknown-illumos", @@ -470,7 +472,7 @@ impl Builder { } // so is rust-mingw if it's available for the target PkgType::RustMingw => { - if host.contains("pc-windows-gnu") { + if host.ends_with("pc-windows-gnu") { components.push(host_component(pkg)); } } From b2be01c81567e89142577f9474a810e3a8676cfc Mon Sep 17 00:00:00 2001 From: binarycat Date: Tue, 24 Jun 2025 17:47:24 -0500 Subject: [PATCH 13/21] rustdoc: show attributes on enum variants mostly for #[non_exhaustive] --- src/librustdoc/html/render/print_item.rs | 1 + .../rustdoc/enum/enum-variant-non_exhaustive.rs | 17 +++++++++++++++++ ...-variant-non_exhaustive.type-alias-code.html | 4 ++++ .../enum-variant-non_exhaustive.type-code.html | 4 ++++ 4 files changed, 26 insertions(+) create mode 100644 tests/rustdoc/enum/enum-variant-non_exhaustive.rs create mode 100644 tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html create mode 100644 tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 515424cbef19..5456a09dce72 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1702,6 +1702,7 @@ fn render_enum_fields( if v.is_stripped() { continue; } + write!(w, "{}", render_attributes_in_pre(v, TAB, cx))?; w.write_str(TAB)?; match v.kind { clean::VariantItem(ref var) => match var.kind { diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.rs b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs new file mode 100644 index 000000000000..ea0234a49f64 --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs @@ -0,0 +1,17 @@ +// regression test for https://github.com/rust-lang/rust/issues/142599 + +#![crate_name = "foo"] + +//@ snapshot type-code 'foo/enum.Type.html' '//pre[@class="rust item-decl"]/code' +pub enum Type { + #[non_exhaustive] + // attribute that should not be shown + #[warn(unsafe_code)] + Variant, +} + +// we would love to use the `following-sibling::` axis +// (along with an `h2[@id="aliased-type"]` query), +// but unfortunately python doesn't implement that. +//@ snapshot type-alias-code 'foo/type.TypeAlias.html' '//pre[@class="rust item-decl"][2]/code' +pub type TypeAlias = Type; diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html new file mode 100644 index 000000000000..04eea709079d --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html @@ -0,0 +1,4 @@ +pub enum TypeAlias { + #[non_exhaustive] + Variant, +} \ No newline at end of file diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html new file mode 100644 index 000000000000..6c8851ea5df4 --- /dev/null +++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html @@ -0,0 +1,4 @@ +pub enum Type { + #[non_exhaustive] + Variant, +} \ No newline at end of file From 1087042e22ae3d14ff856f621a84eaa3e15cc537 Mon Sep 17 00:00:00 2001 From: Bastian Kersting Date: Thu, 15 May 2025 19:09:13 +0000 Subject: [PATCH 14/21] Insert checks for enum discriminants when debug assertions are enabled Similar to the existing nullpointer and alignment checks, this checks for valid enum discriminants on creation of enums through unsafe transmutes. Essentially this sanitizes patterns like the following: ```rust let val: MyEnum = unsafe { std::mem::transmute(42) }; ``` An extension of this check will be done in a follow-up that explicitly sanitizes for extern enum values that come into Rust from e.g. C/C++. This check is similar to Miri's capabilities of checking for valid construction of enum values. This PR is inspired by saethlin@'s PR https://github.com/rust-lang/rust/pull/104862. Thank you so much for keeping this code up and the detailed comments! I also pair-programmed large parts of this together with vabr-g@. --- compiler/rustc_codegen_cranelift/src/base.rs | 12 + compiler/rustc_codegen_ssa/src/mir/block.rs | 6 + .../src/const_eval/machine.rs | 1 + compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_middle/messages.ftl | 3 + compiler/rustc_middle/src/mir/syntax.rs | 1 + compiler/rustc_middle/src/mir/terminator.rs | 8 + compiler/rustc_middle/src/mir/visit.rs | 2 +- .../rustc_mir_transform/src/check_enums.rs | 501 ++++++++++++++++++ compiler/rustc_mir_transform/src/lib.rs | 2 + compiler/rustc_monomorphize/src/collector.rs | 3 + .../rustc_smir/src/rustc_smir/convert/mir.rs | 3 + .../rustc_smir/src/stable_mir/mir/body.rs | 4 + .../rustc_smir/src/stable_mir/mir/pretty.rs | 4 + .../rustc_smir/src/stable_mir/mir/visit.rs | 3 +- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/panicking.rs | 16 + src/tools/miri/src/lib.rs | 2 +- tests/ui/mir/enum/convert_non_enum_break.rs | 20 + .../mir/enum/convert_non_enum_niche_break.rs | 27 + .../ui/mir/enum/convert_non_enum_niche_ok.rs | 29 + tests/ui/mir/enum/convert_non_enum_ok.rs | 20 + tests/ui/mir/enum/niche_option_tuple_break.rs | 20 + tests/ui/mir/enum/niche_option_tuple_ok.rs | 21 + tests/ui/mir/enum/numbered_variants_break.rs | 13 + tests/ui/mir/enum/numbered_variants_ok.rs | 13 + .../enum/option_with_bigger_niche_break.rs | 14 + .../mir/enum/option_with_bigger_niche_ok.rs | 14 + tests/ui/mir/enum/plain_no_data_break.rs | 14 + tests/ui/mir/enum/plain_no_data_ok.rs | 14 + tests/ui/mir/enum/single_ok.rs | 11 + tests/ui/mir/enum/single_with_repr_break.rs | 13 + tests/ui/mir/enum/single_with_repr_ok.rs | 12 + tests/ui/mir/enum/with_niche_int_break.rs | 21 + tests/ui/mir/enum/with_niche_int_ok.rs | 23 + tests/ui/mir/enum/with_niche_ptr_ok.rs | 14 + tests/ui/mir/enum/wrap_break.rs | 14 + tests/ui/mir/enum/wrap_ok.rs | 12 + 38 files changed, 909 insertions(+), 3 deletions(-) create mode 100644 compiler/rustc_mir_transform/src/check_enums.rs create mode 100644 tests/ui/mir/enum/convert_non_enum_break.rs create mode 100644 tests/ui/mir/enum/convert_non_enum_niche_break.rs create mode 100644 tests/ui/mir/enum/convert_non_enum_niche_ok.rs create mode 100644 tests/ui/mir/enum/convert_non_enum_ok.rs create mode 100644 tests/ui/mir/enum/niche_option_tuple_break.rs create mode 100644 tests/ui/mir/enum/niche_option_tuple_ok.rs create mode 100644 tests/ui/mir/enum/numbered_variants_break.rs create mode 100644 tests/ui/mir/enum/numbered_variants_ok.rs create mode 100644 tests/ui/mir/enum/option_with_bigger_niche_break.rs create mode 100644 tests/ui/mir/enum/option_with_bigger_niche_ok.rs create mode 100644 tests/ui/mir/enum/plain_no_data_break.rs create mode 100644 tests/ui/mir/enum/plain_no_data_ok.rs create mode 100644 tests/ui/mir/enum/single_ok.rs create mode 100644 tests/ui/mir/enum/single_with_repr_break.rs create mode 100644 tests/ui/mir/enum/single_with_repr_ok.rs create mode 100644 tests/ui/mir/enum/with_niche_int_break.rs create mode 100644 tests/ui/mir/enum/with_niche_int_ok.rs create mode 100644 tests/ui/mir/enum/with_niche_ptr_ok.rs create mode 100644 tests/ui/mir/enum/wrap_break.rs create mode 100644 tests/ui/mir/enum/wrap_ok.rs diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 0b641ba64b7a..1b68c6535da5 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -407,6 +407,18 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { source_info.span, ) } + AssertKind::InvalidEnumConstruction(source) => { + let source = codegen_operand(fx, source).load_scalar(fx); + let location = fx.get_caller_location(source_info).load_scalar(fx); + + codegen_panic_inner( + fx, + rustc_hir::LangItem::PanicInvalidEnumConstruction, + &[source, location], + *unwind, + source_info.span, + ) + } _ => { let location = fx.get_caller_location(source_info).load_scalar(fx); diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 43b87171d510..f06410404288 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -777,6 +777,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // `#[track_caller]` adds an implicit argument. (LangItem::PanicNullPointerDereference, vec![location]) } + AssertKind::InvalidEnumConstruction(source) => { + let source = self.codegen_operand(bx, source).immediate(); + // It's `fn panic_invalid_enum_construction(source: u128)`, + // `#[track_caller]` adds an implicit argument. + (LangItem::PanicInvalidEnumConstruction, vec![source, location]) + } _ => { // It's `pub fn panic_...()` and `#[track_caller]` adds an implicit argument. (msg.panic_function(), vec![location]) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index a68dcf299886..a4c5ca09a72a 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -508,6 +508,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { found: eval_to_int(found)?, }, NullPointerDereference => NullPointerDereference, + InvalidEnumConstruction(source) => InvalidEnumConstruction(eval_to_int(source)?), }; Err(ConstEvalErrKind::AssertFailure(err)).into() } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 21d36ed54cdf..b537dceaddd8 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -310,6 +310,7 @@ language_item_table! { PanicAsyncGenFnResumedPanic, sym::panic_const_async_gen_fn_resumed_panic, panic_const_async_gen_fn_resumed_panic, Target::Fn, GenericRequirement::None; PanicGenFnNonePanic, sym::panic_const_gen_fn_none_panic, panic_const_gen_fn_none_panic, Target::Fn, GenericRequirement::None; PanicNullPointerDereference, sym::panic_null_pointer_dereference, panic_null_pointer_dereference, Target::Fn, GenericRequirement::None; + PanicInvalidEnumConstruction, sym::panic_invalid_enum_construction, panic_invalid_enum_construction, Target::Fn, GenericRequirement::None; PanicCoroutineResumedDrop, sym::panic_const_coroutine_resumed_drop, panic_const_coroutine_resumed_drop, Target::Fn, GenericRequirement::None; PanicAsyncFnResumedDrop, sym::panic_const_async_fn_resumed_drop, panic_const_async_fn_resumed_drop, Target::Fn, GenericRequirement::None; PanicAsyncGenFnResumedDrop, sym::panic_const_async_gen_fn_resumed_drop, panic_const_async_gen_fn_resumed_drop, Target::Fn, GenericRequirement::None; diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 3d27e587b6cb..7aa474324120 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -17,6 +17,9 @@ middle_assert_gen_resume_after_drop = `gen` fn or block cannot be further iterat middle_assert_gen_resume_after_panic = `gen` fn or block cannot be further iterated on after it panicked +middle_assert_invalid_enum_construction = + trying to construct an enum from an invalid value `{$source}` + middle_assert_misaligned_ptr_deref = misaligned pointer dereference: address must be a multiple of {$required} but is {$found} diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index bb068f3821db..802583f283da 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1075,6 +1075,7 @@ pub enum AssertKind { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: O, found: O }, NullPointerDereference, + InvalidEnumConstruction(O), } #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 0834fa8844c0..4034a3a06e94 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -208,6 +208,7 @@ impl AssertKind { LangItem::PanicGenFnNonePanic } NullPointerDereference => LangItem::PanicNullPointerDereference, + InvalidEnumConstruction(_) => LangItem::PanicInvalidEnumConstruction, ResumedAfterDrop(CoroutineKind::Coroutine(_)) => LangItem::PanicCoroutineResumedDrop, ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { LangItem::PanicAsyncFnResumedDrop @@ -284,6 +285,9 @@ impl AssertKind { ) } NullPointerDereference => write!(f, "\"null pointer dereference occurred\""), + InvalidEnumConstruction(source) => { + write!(f, "\"trying to construct an enum from an invalid value {{}}\", {source:?}") + } ResumedAfterReturn(CoroutineKind::Coroutine(_)) => { write!(f, "\"coroutine resumed after completion\"") } @@ -367,6 +371,7 @@ impl AssertKind { middle_assert_coroutine_resume_after_panic } NullPointerDereference => middle_assert_null_ptr_deref, + InvalidEnumConstruction(_) => middle_assert_invalid_enum_construction, ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { middle_assert_async_resume_after_drop } @@ -420,6 +425,9 @@ impl AssertKind { add!("required", format!("{required:#?}")); add!("found", format!("{found:#?}")); } + InvalidEnumConstruction(source) => { + add!("source", format!("{source:#?}")); + } } } } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 1777756174bf..929ebe1aee18 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -642,7 +642,7 @@ macro_rules! make_mir_visitor { self.visit_operand(l, location); self.visit_operand(r, location); } - OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => { + OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) | InvalidEnumConstruction(op) => { self.visit_operand(op, location); } ResumedAfterReturn(_) | ResumedAfterPanic(_) | NullPointerDereference | ResumedAfterDrop(_) => { diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs new file mode 100644 index 000000000000..e06e0c6122e8 --- /dev/null +++ b/compiler/rustc_mir_transform/src/check_enums.rs @@ -0,0 +1,501 @@ +use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange}; +use rustc_hir::LangItem; +use rustc_index::IndexVec; +use rustc_middle::bug; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::*; +use rustc_middle::ty::layout::PrimitiveExt; +use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv}; +use rustc_session::Session; +use tracing::debug; + +/// This pass inserts checks for a valid enum discriminant where they are most +/// likely to find UB, because checking everywhere like Miri would generate too +/// much MIR. +pub(super) struct CheckEnums; + +impl<'tcx> crate::MirPass<'tcx> for CheckEnums { + fn is_enabled(&self, sess: &Session) -> bool { + sess.ub_checks() + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + // This pass emits new panics. If for whatever reason we do not have a panic + // implementation, running this pass may cause otherwise-valid code to not compile. + if tcx.lang_items().get(LangItem::PanicImpl).is_none() { + return; + } + + let typing_env = body.typing_env(tcx); + let basic_blocks = body.basic_blocks.as_mut(); + let local_decls = &mut body.local_decls; + + // This operation inserts new blocks. Each insertion changes the Location for all + // statements/blocks after. Iterating or visiting the MIR in order would require updating + // our current location after every insertion. By iterating backwards, we dodge this issue: + // The only Locations that an insertion changes have already been handled. + for block in basic_blocks.indices().rev() { + for statement_index in (0..basic_blocks[block].statements.len()).rev() { + let location = Location { block, statement_index }; + let statement = &basic_blocks[block].statements[statement_index]; + let source_info = statement.source_info; + + let mut finder = EnumFinder::new(tcx, local_decls, typing_env); + finder.visit_statement(statement, location); + + for check in finder.into_found_enums() { + debug!("Inserting enum check"); + let new_block = split_block(basic_blocks, location); + + match check { + EnumCheckType::Direct { source_op, discr, op_size, valid_discrs } => { + insert_direct_enum_check( + tcx, + local_decls, + basic_blocks, + block, + source_op, + discr, + op_size, + valid_discrs, + source_info, + new_block, + ) + } + EnumCheckType::Uninhabited => insert_uninhabited_enum_check( + tcx, + local_decls, + &mut basic_blocks[block], + source_info, + new_block, + ), + EnumCheckType::WithNiche { + source_op, + discr, + op_size, + offset, + valid_range, + } => insert_niche_check( + tcx, + local_decls, + &mut basic_blocks[block], + source_op, + valid_range, + discr, + op_size, + offset, + source_info, + new_block, + ), + } + } + } + } + } + + fn is_required(&self) -> bool { + true + } +} + +/// Represent the different kind of enum checks we can insert. +enum EnumCheckType<'tcx> { + /// We know we try to create an uninhabited enum from an inhabited variant. + Uninhabited, + /// We know the enum does no niche optimizations and can thus easily compute + /// the valid discriminants. + Direct { + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + valid_discrs: Vec, + }, + /// We try to construct an enum that has a niche. + WithNiche { + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Size, + valid_range: WrappingRange, + }, +} + +struct TyAndSize<'tcx> { + pub ty: Ty<'tcx>, + pub size: Size, +} + +/// A [Visitor] that finds the construction of enums and evaluates which checks +/// we should apply. +struct EnumFinder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + local_decls: &'a mut LocalDecls<'tcx>, + typing_env: TypingEnv<'tcx>, + enums: Vec>, +} + +impl<'a, 'tcx> EnumFinder<'a, 'tcx> { + fn new( + tcx: TyCtxt<'tcx>, + local_decls: &'a mut LocalDecls<'tcx>, + typing_env: TypingEnv<'tcx>, + ) -> Self { + EnumFinder { tcx, local_decls, typing_env, enums: Vec::new() } + } + + /// Returns the found enum creations and which checks should be inserted. + fn into_found_enums(self) -> Vec> { + self.enums + } +} + +impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue { + let ty::Adt(adt_def, _) = ty.kind() else { + return; + }; + if !adt_def.is_enum() { + return; + } + + let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else { + return; + }; + let Ok(op_layout) = self + .tcx + .layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx))) + else { + return; + }; + + match enum_layout.variants { + Variants::Empty if op_layout.is_uninhabited() => return, + // An empty enum that tries to be constructed from an inhabited value, this + // is never correct. + Variants::Empty => { + // The enum layout is uninhabited but we construct it from sth inhabited. + // This is always UB. + self.enums.push(EnumCheckType::Uninhabited); + } + // Construction of Single value enums is always fine. + Variants::Single { .. } => {} + // Construction of an enum with multiple variants but no niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Direct, + tag: Scalar::Initialized { value, .. }, + .. + } => { + let valid_discrs = + adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect(); + + let discr = + TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::Direct { + source_op: op.to_copy(), + discr, + op_size: op_layout.size, + valid_discrs, + }); + } + // Construction of an enum with multiple variants and niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + tag: Scalar::Initialized { value, valid_range, .. }, + tag_field, + .. + } => { + let discr = + TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::WithNiche { + source_op: op.to_copy(), + discr, + op_size: op_layout.size, + offset: enum_layout.fields.offset(tag_field.as_usize()), + valid_range, + }); + } + _ => return, + } + + self.super_rvalue(rvalue, location); + } + } +} + +fn split_block( + basic_blocks: &mut IndexVec>, + location: Location, +) -> BasicBlock { + let block_data = &mut basic_blocks[location.block]; + + // Drain every statement after this one and move the current terminator to a new basic block. + let new_block = BasicBlockData { + statements: block_data.statements.split_off(location.statement_index), + terminator: block_data.terminator.take(), + is_cleanup: block_data.is_cleanup, + }; + + basic_blocks.push(new_block) +} + +/// Inserts the cast of an operand (any type) to a u128 value that holds the discriminant value. +fn insert_discr_cast_to_u128<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Option, + source_info: SourceInfo, +) -> Place<'tcx> { + let get_ty_for_size = |tcx: TyCtxt<'tcx>, size: Size| -> Ty<'tcx> { + match size.bytes() { + 1 => tcx.types.u8, + 2 => tcx.types.u16, + 4 => tcx.types.u32, + 8 => tcx.types.u64, + 16 => tcx.types.u128, + invalid => bug!("Found discriminant with invalid size, has {} bytes", invalid), + } + }; + + let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() { + // The discriminant is less wide than the operand, cast the operand into + // [MaybeUninit; N] and then index into it. + let mu = Ty::new_maybe_uninit(tcx, tcx.types.u8); + let array_len = op_size.bytes(); + let mu_array_ty = Ty::new_array(tcx, mu, array_len); + let mu_array = + local_decls.push(LocalDecl::with_source_info(mu_array_ty, source_info)).into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_array_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((mu_array, rvalue))), + }); + + // Index into the array of MaybeUninit to get something that is actually + // as wide as the discriminant. + let offset = offset.unwrap_or(Size::ZERO); + let smaller_mu_array = mu_array.project_deeper( + &[ProjectionElem::Subslice { + from: offset.bytes(), + to: offset.bytes() + discr.size.bytes(), + from_end: false, + }], + tcx, + ); + + (CastKind::Transmute, Operand::Copy(smaller_mu_array)) + } else { + let operand_int_ty = get_ty_for_size(tcx, op_size); + + let op_as_int = + local_decls.push(LocalDecl::with_source_info(operand_int_ty, source_info)).into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, operand_int_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((op_as_int, rvalue))), + }); + + (CastKind::IntToInt, Operand::Copy(op_as_int)) + }; + + // Cast the resulting value to the actual discriminant integer type. + let rvalue = Rvalue::Cast(cast_kind, discr_ty_bits, discr.ty); + let discr_in_discr_ty = + local_decls.push(LocalDecl::with_source_info(discr.ty, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((discr_in_discr_ty, rvalue))), + }); + + // Cast the discriminant to a u128 (base for comparisions of enum discriminants). + let const_u128 = Ty::new_uint(tcx, ty::UintTy::U128); + let rvalue = Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_in_discr_ty), const_u128); + let discr = local_decls.push(LocalDecl::with_source_info(const_u128, source_info)).into(); + block_data + .statements + .push(Statement { source_info, kind: StatementKind::Assign(Box::new((discr, rvalue))) }); + + discr +} + +fn insert_direct_enum_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + basic_blocks: &mut IndexVec>, + current_block: BasicBlock, + source_op: Operand<'tcx>, + discr: TyAndSize<'tcx>, + op_size: Size, + discriminants: Vec, + source_info: SourceInfo, + new_block: BasicBlock, +) { + // Insert a new target block that is branched to in case of an invalid discriminant. + let invalid_discr_block_data = BasicBlockData::new(None, false); + let invalid_discr_block = basic_blocks.push(invalid_discr_block_data); + let block_data = &mut basic_blocks[current_block]; + let discr = insert_discr_cast_to_u128( + tcx, + local_decls, + block_data, + source_op, + discr, + op_size, + None, + source_info, + ); + + // Branch based on the discriminant value. + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::SwitchInt { + discr: Operand::Copy(discr), + targets: SwitchTargets::new( + discriminants.into_iter().map(|discr| (discr, new_block)), + invalid_discr_block, + ), + }, + }); + + // Abort in case of an invalid enum discriminant. + basic_blocks[invalid_discr_block].terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool), + })), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} + +fn insert_uninhabited_enum_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_info: SourceInfo, + new_block: BasicBlock, +) { + let is_ok: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + is_ok, + Rvalue::Use(Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool), + }))), + ))), + }); + + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Copy(is_ok), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new( + ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128), + }, + )))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} + +fn insert_niche_check<'tcx>( + tcx: TyCtxt<'tcx>, + local_decls: &mut IndexVec>, + block_data: &mut BasicBlockData<'tcx>, + source_op: Operand<'tcx>, + valid_range: WrappingRange, + discr: TyAndSize<'tcx>, + op_size: Size, + offset: Size, + source_info: SourceInfo, + new_block: BasicBlock, +) { + let discr = insert_discr_cast_to_u128( + tcx, + local_decls, + block_data, + source_op, + discr, + op_size, + Some(offset), + source_info, + ); + + // Compare the discriminant agains the valid_range. + let start_const = Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val(ConstValue::from_u128(valid_range.start), tcx.types.u128), + })); + let end_start_diff_const = Operand::Constant(Box::new(ConstOperand { + span: source_info.span, + user_ty: None, + const_: Const::Val( + ConstValue::from_u128(u128::wrapping_sub(valid_range.end, valid_range.start)), + tcx.types.u128, + ), + })); + + let discr_diff: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + discr_diff, + Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(discr), start_const))), + ))), + }); + + let is_ok: Place<'_> = + local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new(( + is_ok, + Rvalue::BinaryOp( + // This is a `WrappingRange`, so make sure to get the wrapping right. + BinOp::Le, + Box::new((Operand::Copy(discr_diff), end_start_diff_const)), + ), + ))), + }); + + block_data.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Assert { + cond: Operand::Copy(is_ok), + expected: true, + target: new_block, + msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))), + // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, + }, + }); +} diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 572ad585c8c8..6b32254b0514 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -117,6 +117,7 @@ declare_passes! { mod check_inline : CheckForceInline; mod check_call_recursion : CheckCallRecursion, CheckDropRecursion; mod check_alignment : CheckAlignment; + mod check_enums : CheckEnums; mod check_const_item_mutation : CheckConstItemMutation; mod check_null : CheckNull; mod check_packed_ref : CheckPackedRef; @@ -666,6 +667,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' // Add some UB checks before any UB gets optimized away. &check_alignment::CheckAlignment, &check_null::CheckNull, + &check_enums::CheckEnums, // Before inlining: trim down MIR with passes to reduce inlining work. // Has to be done before inlining, otherwise actual call will be almost always inlined. diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 173030e0326e..ce61d3794735 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -834,6 +834,9 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { mir::AssertKind::NullPointerDereference => { push_mono_lang_item(self, LangItem::PanicNullPointerDereference); } + mir::AssertKind::InvalidEnumConstruction(_) => { + push_mono_lang_item(self, LangItem::PanicInvalidEnumConstruction); + } _ => { push_mono_lang_item(self, msg.panic_function()); } diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index 42b3e59b73ab..85e71ed2c255 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -506,6 +506,9 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> { AssertKind::NullPointerDereference => { stable_mir::mir::AssertMessage::NullPointerDereference } + AssertKind::InvalidEnumConstruction(source) => { + stable_mir::mir::AssertMessage::InvalidEnumConstruction(source.stable(tables)) + } } } } diff --git a/compiler/rustc_smir/src/stable_mir/mir/body.rs b/compiler/rustc_smir/src/stable_mir/mir/body.rs index 660cd7db0800..e4b7659ce7f9 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/body.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/body.rs @@ -270,6 +270,7 @@ pub enum AssertMessage { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: Operand, found: Operand }, NullPointerDereference, + InvalidEnumConstruction(Operand), } impl AssertMessage { @@ -342,6 +343,9 @@ impl AssertMessage { Ok("misaligned pointer dereference") } AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"), + AssertMessage::InvalidEnumConstruction(_) => { + Ok("trying to construct an enum from an invalid value") + } } } } diff --git a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs index ba20651f993d..b068a9a1081f 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs @@ -313,6 +313,10 @@ fn pretty_assert_message(writer: &mut W, msg: &AssertMessage) -> io::R AssertMessage::NullPointerDereference => { write!(writer, "\"null pointer dereference occurred\"") } + AssertMessage::InvalidEnumConstruction(op) => { + let pretty_op = pretty_operand(op); + write!(writer, "\"trying to construct an enum from an invalid value {{}}\",{pretty_op}") + } AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) | AssertMessage::ResumedAfterDrop(_) => { diff --git a/compiler/rustc_smir/src/stable_mir/mir/visit.rs b/compiler/rustc_smir/src/stable_mir/mir/visit.rs index e21dc11eea9c..b7dd433eb093 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/visit.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/visit.rs @@ -367,7 +367,8 @@ macro_rules! make_mir_visitor { } AssertMessage::OverflowNeg(op) | AssertMessage::DivisionByZero(op) - | AssertMessage::RemainderByZero(op) => { + | AssertMessage::RemainderByZero(op) + | AssertMessage::InvalidEnumConstruction(op) => { self.visit_operand(op, location); } AssertMessage::ResumedAfterReturn(_) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d66f98871b97..95366d3bd929 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1575,6 +1575,7 @@ symbols! { panic_implementation, panic_in_cleanup, panic_info, + panic_invalid_enum_construction, panic_location, panic_misaligned_pointer_dereference, panic_nounwind, diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index d87f4814f021..812bc5e61457 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -314,6 +314,22 @@ fn panic_null_pointer_dereference() -> ! { ) } +#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold, optimize(size))] +#[cfg_attr(feature = "panic_immediate_abort", inline)] +#[track_caller] +#[lang = "panic_invalid_enum_construction"] // needed by codegen for panic on invalid enum construction. +#[rustc_nounwind] // `CheckEnums` MIR pass requires this function to never unwind +fn panic_invalid_enum_construction(source: u128) -> ! { + if cfg!(feature = "panic_immediate_abort") { + super::intrinsics::abort() + } + + panic_nounwind_fmt( + format_args!("trying to construct an enum from an invalid value {source:#x}"), + /* force_no_backtrace */ false, + ) +} + /// Panics because we cannot unwind out of a function. /// /// This is a separate function to avoid the codesize impact of each crate containing the string to diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 51ec19af52a0..12a9a3317503 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -165,7 +165,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[ "-Zmir-emit-retag", "-Zmir-preserve-ub", "-Zmir-opt-level=0", - "-Zmir-enable-passes=-CheckAlignment,-CheckNull", + "-Zmir-enable-passes=-CheckAlignment,-CheckNull,-CheckEnums", // Deduplicating diagnostics means we miss events when tracking what happens during an // execution. Let's not do that. "-Zdeduplicate-diagnostics=no", diff --git a/tests/ui/mir/enum/convert_non_enum_break.rs b/tests/ui/mir/enum/convert_non_enum_break.rs new file mode 100644 index 000000000000..de062c39907a --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_break.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x10000 + +#[allow(dead_code)] +#[repr(u32)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 1 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_niche_break.rs b/tests/ui/mir/enum/convert_non_enum_niche_break.rs new file mode 100644 index 000000000000..9ff4849c5b1f --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_niche_break.rs @@ -0,0 +1,27 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x5 + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 5, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_niche_ok.rs b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs new file mode 100644 index 000000000000..24027da54589 --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs @@ -0,0 +1,29 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 0, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 1, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 2, b: 0 }) }; + let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 3, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/convert_non_enum_ok.rs b/tests/ui/mir/enum/convert_non_enum_ok.rs new file mode 100644 index 000000000000..37fc64342ca9 --- /dev/null +++ b/tests/ui/mir/enum/convert_non_enum_ok.rs @@ -0,0 +1,20 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u32)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: u16, + b: u16, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 0 }) }; + let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 1, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/niche_option_tuple_break.rs b/tests/ui/mir/enum/niche_option_tuple_break.rs new file mode 100644 index 000000000000..43eef3a4cc5f --- /dev/null +++ b/tests/ui/mir/enum/niche_option_tuple_break.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x3 + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: usize, + b: usize, +} + +fn main() { + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 3, b: 3 }) }; +} diff --git a/tests/ui/mir/enum/niche_option_tuple_ok.rs b/tests/ui/mir/enum/niche_option_tuple_ok.rs new file mode 100644 index 000000000000..71c885c7edbf --- /dev/null +++ b/tests/ui/mir/enum/niche_option_tuple_ok.rs @@ -0,0 +1,21 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +#[allow(dead_code)] +struct Bar { + a: usize, + b: usize, +} + +fn main() { + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 0, b: 0 }) }; + let _val: Option<(usize, Foo)> = + unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 1, b: 0 }) }; +} diff --git a/tests/ui/mir/enum/numbered_variants_break.rs b/tests/ui/mir/enum/numbered_variants_break.rs new file mode 100644 index 000000000000..e3e71dc8aec4 --- /dev/null +++ b/tests/ui/mir/enum/numbered_variants_break.rs @@ -0,0 +1,13 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x3 + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/numbered_variants_ok.rs b/tests/ui/mir/enum/numbered_variants_ok.rs new file mode 100644 index 000000000000..995a2f6511b1 --- /dev/null +++ b/tests/ui/mir/enum/numbered_variants_ok.rs @@ -0,0 +1,13 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Foo { + A, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(0) }; + let _val: Foo = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/option_with_bigger_niche_break.rs b/tests/ui/mir/enum/option_with_bigger_niche_break.rs new file mode 100644 index 000000000000..c66614b845b5 --- /dev/null +++ b/tests/ui/mir/enum/option_with_bigger_niche_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x0 + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Option = unsafe { std::mem::transmute::>(0) }; +} diff --git a/tests/ui/mir/enum/option_with_bigger_niche_ok.rs b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs new file mode 100644 index 000000000000..1d44ffd28fce --- /dev/null +++ b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Option = unsafe { std::mem::transmute::>(2) }; + let _val: Option = unsafe { std::mem::transmute::>(3) }; +} diff --git a/tests/ui/mir/enum/plain_no_data_break.rs b/tests/ui/mir/enum/plain_no_data_break.rs new file mode 100644 index 000000000000..db68e752479d --- /dev/null +++ b/tests/ui/mir/enum/plain_no_data_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/plain_no_data_ok.rs b/tests/ui/mir/enum/plain_no_data_ok.rs new file mode 100644 index 000000000000..bbdc18f96dc4 --- /dev/null +++ b/tests/ui/mir/enum/plain_no_data_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[repr(u32)] +#[allow(dead_code)] +enum Foo { + A = 2, + B, +} + +fn main() { + let _val: Foo = unsafe { std::mem::transmute::(2) }; + let _val: Foo = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/single_ok.rs b/tests/ui/mir/enum/single_ok.rs new file mode 100644 index 000000000000..06b5a237c687 --- /dev/null +++ b/tests/ui/mir/enum/single_ok.rs @@ -0,0 +1,11 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::<(), Single>(()) }; +} diff --git a/tests/ui/mir/enum/single_with_repr_break.rs b/tests/ui/mir/enum/single_with_repr_break.rs new file mode 100644 index 000000000000..5a4ec85a9b55 --- /dev/null +++ b/tests/ui/mir/enum/single_with_repr_break.rs @@ -0,0 +1,13 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::(1) }; +} diff --git a/tests/ui/mir/enum/single_with_repr_ok.rs b/tests/ui/mir/enum/single_with_repr_ok.rs new file mode 100644 index 000000000000..b0ed2bad6608 --- /dev/null +++ b/tests/ui/mir/enum/single_with_repr_ok.rs @@ -0,0 +1,12 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A +} + +fn main() { + let _val: Single = unsafe { std::mem::transmute::(0) }; +} diff --git a/tests/ui/mir/enum/with_niche_int_break.rs b/tests/ui/mir/enum/with_niche_int_break.rs new file mode 100644 index 000000000000..0ec60a33564a --- /dev/null +++ b/tests/ui/mir/enum/with_niche_int_break.rs @@ -0,0 +1,21 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x4 + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::(4) }; +} diff --git a/tests/ui/mir/enum/with_niche_int_ok.rs b/tests/ui/mir/enum/with_niche_int_ok.rs new file mode 100644 index 000000000000..9a3ff3a73beb --- /dev/null +++ b/tests/ui/mir/enum/with_niche_int_ok.rs @@ -0,0 +1,23 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Mix { + A, + B(u16), +} + +#[allow(dead_code)] +enum Nested { + C(Mix), + D, + E, +} + +fn main() { + let _val: Nested = unsafe { std::mem::transmute::(0) }; + let _val: Nested = unsafe { std::mem::transmute::(1) }; + let _val: Nested = unsafe { std::mem::transmute::(2) }; + let _val: Nested = unsafe { std::mem::transmute::(3) }; +} diff --git a/tests/ui/mir/enum/with_niche_ptr_ok.rs b/tests/ui/mir/enum/with_niche_ptr_ok.rs new file mode 100644 index 000000000000..969d955f7a4b --- /dev/null +++ b/tests/ui/mir/enum/with_niche_ptr_ok.rs @@ -0,0 +1,14 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +fn main() { + let _val = unsafe { + std::mem::transmute::<*const usize, Option>(std::ptr::null()) + }; + let _val = unsafe { + std::mem::transmute::<*const usize, Option>(usize::MAX as *const _) + }; + let _val = unsafe { std::mem::transmute::>(0) }; + let _val = unsafe { std::mem::transmute::>(1) }; + let _val = unsafe { std::mem::transmute::>(usize::MAX) }; +} diff --git a/tests/ui/mir/enum/wrap_break.rs b/tests/ui/mir/enum/wrap_break.rs new file mode 100644 index 000000000000..4491394ca5a3 --- /dev/null +++ b/tests/ui/mir/enum/wrap_break.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x0 +#![feature(never_type)] +#![allow(invalid_value)] + +#[allow(dead_code)] +enum Wrap { + A(!), +} + +fn main() { + let _val: Wrap = unsafe { std::mem::transmute::<(), Wrap>(()) }; +} diff --git a/tests/ui/mir/enum/wrap_ok.rs b/tests/ui/mir/enum/wrap_ok.rs new file mode 100644 index 000000000000..2881675c9ce6 --- /dev/null +++ b/tests/ui/mir/enum/wrap_ok.rs @@ -0,0 +1,12 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +enum Wrap { + A(u32), +} + +fn main() { + let _val: Wrap = unsafe { std::mem::transmute::(2) }; + let _val: Wrap = unsafe { std::mem::transmute::(u32::MAX) }; +} From 7a79454de465774f8445a88640ffdc5792fc8a13 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 27 Jun 2025 18:24:42 +0800 Subject: [PATCH 15/21] update internal `send_signal` comment --- library/std/src/sys/process/unix/unix.rs | 4 ++-- library/std/src/sys/process/unix/vxworks.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 1fe80c13b81c..bbd03e2b0c4e 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -968,8 +968,8 @@ impl Process { } pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be signaling + // If we've already waited on this process then the pid can be recycled and + // used for another process, and we probably shouldn't be sending signals to // random processes, so return Ok because the process has exited already. if self.status.is_some() { return Ok(()); diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index 51ae8c56bdb9..2275cbb946a9 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -151,8 +151,8 @@ impl Process { } pub fn send_signal(&self, signal: i32) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be killing + // If we've already waited on this process then the pid can be recycled and + // used for another process, and we probably shouldn't be sending signals to // random processes, so return Ok because the process has exited already. if self.status.is_some() { Ok(()) From cd1713ebba83a87e80a7366fdfe6fc28cabe6053 Mon Sep 17 00:00:00 2001 From: Cheng Xu Date: Fri, 27 Jun 2025 11:01:59 -0700 Subject: [PATCH 16/21] BTreeSet: remove duplicated code by reusing `from_sorted_iter` The method `BTreeSet::from_sorted_iter` was introduced in 49ccb7519f55bd117d2ab50b7a030637f380aec6, but it was not consistently used throughout the codebase. As a result, some code redundantly reimplemented its logic. This commit fixes the problem. --- library/alloc/src/collections/btree/set.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs index aa9e5fce1d4c..d50ce02bda74 100644 --- a/library/alloc/src/collections/btree/set.rs +++ b/library/alloc/src/collections/btree/set.rs @@ -1517,9 +1517,7 @@ impl From<[T; N]> for BTreeSet { // use stable sort to preserve the insertion order. arr.sort(); - let iter = IntoIterator::into_iter(arr).map(|k| (k, SetValZST::default())); - let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global); - BTreeSet { map } + BTreeSet::from_sorted_iter(IntoIterator::into_iter(arr), Global) } } From 9090199f49bf681aaf68322b3beedc37acea7b5f Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:38:52 +0300 Subject: [PATCH 17/21] Update ui.rs --- src/tools/test-float-parse/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/test-float-parse/src/ui.rs b/src/tools/test-float-parse/src/ui.rs index 73473eef0bfd..1a9ba0dc1d99 100644 --- a/src/tools/test-float-parse/src/ui.rs +++ b/src/tools/test-float-parse/src/ui.rs @@ -118,7 +118,7 @@ pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> match result { Ok(FinishedAll) => (), Err(EarlyExit::Timeout) => { - println!(" exited early; exceded {:?} timeout", cfg.timeout) + println!(" exited early; exceeded {:?} timeout", cfg.timeout) } Err(EarlyExit::MaxFailures) => { println!(" exited early; exceeded {:?} max failures", cfg.max_failures) From 3a34dac6c36f4e725d7e16022d12c9f58ebc6c2a Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:39:12 +0300 Subject: [PATCH 18/21] Update README.md --- src/tools/miri/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 5554c7975ff3..3031d2c744a3 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -592,7 +592,7 @@ Definite bugs found: * [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084)) * [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281) * [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo) -* [Mockall reading unintialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite) +* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite) * [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): From 39092cc10b1a9c9395b5876ad40999e1a1eda585 Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:39:40 +0300 Subject: [PATCH 19/21] Update dangling_pointer_to_raw_pointer.rs --- .../fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs index c63e926376d4..9646cdb20659 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs @@ -6,7 +6,7 @@ fn direct_raw(x: *const (i32, i32)) -> *const i32 { // Ensure that if a raw pointer is created via an intermediate // reference, we catch that. (Just in case someone decides to -// desugar this differenly or so.) +// desugar this differently or so.) fn via_ref(x: *const (i32, i32)) -> *const i32 { unsafe { &(*x).0 as *const i32 } //~ERROR: dangling pointer } From 00b64f8851e9f56239a48e4306efd597d7be972c Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Fri, 27 Jun 2025 17:58:36 +0000 Subject: [PATCH 20/21] Use tidy to sort `sym::*` items --- compiler/rustc_macros/src/symbols.rs | 12 -- compiler/rustc_macros/src/symbols/tests.rs | 15 --- compiler/rustc_span/src/symbol.rs | 131 ++++++++++----------- 3 files changed, 64 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs index 2b00b7dd27aa..78a4d47ca334 100644 --- a/compiler/rustc_macros/src/symbols.rs +++ b/compiler/rustc_macros/src/symbols.rs @@ -190,17 +190,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { let mut symbols_stream = quote! {}; let mut prefill_stream = quote! {}; let mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10); - let mut prev_key: Option<(Span, String)> = None; - - let mut check_order = |span: Span, s: &str, errors: &mut Errors| { - if let Some((prev_span, ref prev_str)) = prev_key { - if s < prev_str { - errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`")); - errors.error(prev_span, format!("location of previous symbol `{prev_str}`")); - } - } - prev_key = Some((span, s.to_string())); - }; // Generate the listed keywords. for keyword in input.keywords.iter() { @@ -219,7 +208,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { // Generate the listed symbols. for symbol in input.symbols.iter() { let name = &symbol.name; - check_order(symbol.name.span(), &name.to_string(), &mut errors); let value = match &symbol.value { Value::SameAsName => name.to_string(), diff --git a/compiler/rustc_macros/src/symbols/tests.rs b/compiler/rustc_macros/src/symbols/tests.rs index 9c53453df5b5..f0a7a2106be4 100644 --- a/compiler/rustc_macros/src/symbols/tests.rs +++ b/compiler/rustc_macros/src/symbols/tests.rs @@ -84,18 +84,3 @@ fn check_dup_symbol_and_keyword() { }; test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]); } - -#[test] -fn check_symbol_order() { - let input = quote! { - Keywords {} - Symbols { - zebra, - aardvark, - } - }; - test_symbols_macro( - input, - &["Symbol `aardvark` must precede `zebra`", "location of previous symbol `zebra`"], - ); -} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4be7c7ba5109..c5bc8e85009b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -150,14 +150,10 @@ symbols! { // As well as the symbols listed, there are symbols for the strings // "0", "1", ..., "9", which are accessible via `sym::integer`. // - // The proc macro will abort if symbols are not in alphabetical order (as - // defined by `impl Ord for str`) or if any symbols are duplicated. Vim - // users can sort the list by selecting it and executing the command - // `:'<,'>!LC_ALL=C sort`. - // // There is currently no checking that all symbols are used; that would be // nice to have. Symbols { + // tidy-alphabetical-start Abi, AcqRel, Acquire, @@ -175,18 +171,18 @@ symbols! { AsyncGenPending, AsyncGenReady, AtomicBool, - AtomicI128, + AtomicI8, AtomicI16, AtomicI32, AtomicI64, - AtomicI8, + AtomicI128, AtomicIsize, AtomicPtr, - AtomicU128, + AtomicU8, AtomicU16, AtomicU32, AtomicU64, - AtomicU8, + AtomicU128, AtomicUsize, BTreeEntry, BTreeMap, @@ -607,10 +603,10 @@ symbols! { catch_unwind, cause, cdylib, - ceilf128, ceilf16, ceilf32, ceilf64, + ceilf128, cfg, cfg_accessible, cfg_attr, @@ -747,10 +743,10 @@ symbols! { copy, copy_closures, copy_nonoverlapping, - copysignf128, copysignf16, copysignf32, copysignf64, + copysignf128, core, core_panic, core_panic_2015_macro, @@ -763,10 +759,10 @@ symbols! { coroutine_state, coroutine_yield, coroutines, - cosf128, cosf16, cosf32, cosf64, + cosf128, count, coverage, coverage_attribute, @@ -874,8 +870,8 @@ symbols! { dotdot_in_tuple_patterns, dotdoteq_in_patterns, dreg, - dreg_low16, dreg_low8, + dreg_low16, drop, drop_in_place, drop_types_in_const, @@ -928,16 +924,16 @@ symbols! { exhaustive_integer_patterns, exhaustive_patterns, existential_type, - exp2f128, exp2f16, exp2f32, exp2f64, + exp2f128, expect, expected, - expf128, expf16, expf32, expf64, + expf128, explicit_extern_abis, explicit_generic_args_with_impl_trait, explicit_tail_calls, @@ -958,9 +954,6 @@ symbols! { external, external_doc, f, - f128, - f128_epsilon, - f128_nan, f16, f16_epsilon, f16_nan, @@ -999,10 +992,13 @@ symbols! { f64_legacy_const_neg_infinity, f64_legacy_const_radix, f64_nan, - fabsf128, + f128, + f128_epsilon, + f128_nan, fabsf16, fabsf32, fabsf64, + fabsf128, fadd_algebraic, fadd_fast, fake_variadic, @@ -1024,22 +1020,22 @@ symbols! { flags, float, float_to_int_unchecked, - floorf128, floorf16, floorf32, floorf64, - fmaf128, + floorf128, fmaf16, fmaf32, fmaf64, + fmaf128, fmt, fmt_debug, fmul_algebraic, fmul_fast, - fmuladdf128, fmuladdf16, fmuladdf32, fmuladdf64, + fmuladdf128, fn_align, fn_body, fn_delegation, @@ -1140,13 +1136,12 @@ symbols! { html_root_url, hwaddress, i, - i128, - i128_legacy_const_max, - i128_legacy_const_min, - i128_legacy_fn_max_value, - i128_legacy_fn_min_value, - i128_legacy_mod, - i128_type, + i8, + i8_legacy_const_max, + i8_legacy_const_min, + i8_legacy_fn_max_value, + i8_legacy_fn_min_value, + i8_legacy_mod, i16, i16_legacy_const_max, i16_legacy_const_min, @@ -1165,12 +1160,13 @@ symbols! { i64_legacy_fn_max_value, i64_legacy_fn_min_value, i64_legacy_mod, - i8, - i8_legacy_const_max, - i8_legacy_const_min, - i8_legacy_fn_max_value, - i8_legacy_fn_min_value, - i8_legacy_mod, + i128, + i128_legacy_const_max, + i128_legacy_const_min, + i128_legacy_fn_max_value, + i128_legacy_fn_min_value, + i128_legacy_mod, + i128_type, ident, if_let, if_let_guard, @@ -1292,19 +1288,19 @@ symbols! { loaded_from_disk, local, local_inner_macros, - log10f128, - log10f16, - log10f32, - log10f64, - log2f128, log2f16, log2f32, log2f64, + log2f128, + log10f16, + log10f32, + log10f64, + log10f128, log_syntax, - logf128, logf16, logf32, logf64, + logf128, loongarch_target_feature, loop_break_value, loop_match, @@ -1334,14 +1330,14 @@ symbols! { match_beginning_vert, match_default_bindings, matches_macro, - maximumf128, maximumf16, maximumf32, maximumf64, - maxnumf128, + maximumf128, maxnumf16, maxnumf32, maxnumf64, + maxnumf128, may_dangle, may_unwind, maybe_uninit, @@ -1372,14 +1368,14 @@ symbols! { min_generic_const_args, min_specialization, min_type_alias_impl_trait, - minimumf128, minimumf16, minimumf32, minimumf64, - minnumf128, + minimumf128, minnumf16, minnumf32, minnumf64, + minnumf128, mips_target_feature, mir_assume, mir_basic_block, @@ -1633,14 +1629,14 @@ symbols! { post_dash_lto: "post-lto", postfix_match, powerpc_target_feature, - powf128, powf16, powf32, powf64, - powif128, + powf128, powif16, powif32, powif64, + powif128, pre_dash_lto: "pre-lto", precise_capturing, precise_capturing_in_traits, @@ -1785,14 +1781,14 @@ symbols! { ropi_rwpi: "ropi-rwpi", rotate_left, rotate_right, - round_ties_even_f128, round_ties_even_f16, round_ties_even_f32, round_ties_even_f64, - roundf128, + round_ties_even_f128, roundf16, roundf32, roundf64, + roundf128, rt, rtm_target_feature, rust, @@ -1972,8 +1968,8 @@ symbols! { simd_fexp2, simd_ffi, simd_flog, - simd_flog10, simd_flog2, + simd_flog10, simd_floor, simd_fma, simd_fmax, @@ -2021,10 +2017,10 @@ symbols! { simd_with_exposed_provenance, simd_xor, since, - sinf128, sinf16, sinf32, sinf64, + sinf128, size, size_of, size_of_val, @@ -2046,10 +2042,10 @@ symbols! { specialization, speed, spotlight, - sqrtf128, sqrtf16, sqrtf32, sqrtf64, + sqrtf128, sreg, sreg_low16, sse, @@ -2127,10 +2123,10 @@ symbols! { target_has_atomic, target_has_atomic_equal_alignment, target_has_atomic_load_store, - target_has_reliable_f128, - target_has_reliable_f128_math, target_has_reliable_f16, target_has_reliable_f16_math, + target_has_reliable_f128, + target_has_reliable_f128_math, target_os, target_pointer_width, target_thread_local, @@ -2173,10 +2169,10 @@ symbols! { transparent_enums, transparent_unions, trivial_bounds, - truncf128, truncf16, truncf32, truncf64, + truncf128, try_blocks, try_capture, try_from, @@ -2205,12 +2201,12 @@ symbols! { type_name, type_privacy_lints, typed_swap_nonoverlapping, - u128, - u128_legacy_const_max, - u128_legacy_const_min, - u128_legacy_fn_max_value, - u128_legacy_fn_min_value, - u128_legacy_mod, + u8, + u8_legacy_const_max, + u8_legacy_const_min, + u8_legacy_fn_max_value, + u8_legacy_fn_min_value, + u8_legacy_mod, u16, u16_legacy_const_max, u16_legacy_const_min, @@ -2229,12 +2225,12 @@ symbols! { u64_legacy_fn_max_value, u64_legacy_fn_min_value, u64_legacy_mod, - u8, - u8_legacy_const_max, - u8_legacy_const_min, - u8_legacy_fn_max_value, - u8_legacy_fn_min_value, - u8_legacy_mod, + u128, + u128_legacy_const_max, + u128_legacy_const_min, + u128_legacy_fn_max_value, + u128_legacy_fn_min_value, + u128_legacy_mod, ub_checks, unaligned_volatile_load, unaligned_volatile_store, @@ -2387,6 +2383,7 @@ symbols! { zfh, zfhmin, zmm_reg, + // tidy-alphabetical-end } } From e0c8c3fbb049b278c286d7efd9ca90fe14763f32 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 28 Jun 2025 14:28:46 -0700 Subject: [PATCH 21/21] Update cargo --- src/tools/cargo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/cargo b/src/tools/cargo index 409fed7dc155..930b4f62cfcd 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 409fed7dc1553d49cb9a8c0637d12d65571346ce +Subproject commit 930b4f62cfcd1f0eabdb30a56d91bf6844b739bf