From 840ff78d1680e26e11444de09dc4f4ba03234956 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:46:25 +0000 Subject: [PATCH 01/80] library: optimize truncate method --- library/alloc/src/collections/vec_deque/mod.rs | 4 ++-- library/alloc/src/string.rs | 11 ++++++----- library/alloc/src/vec/mod.rs | 17 ++++++++--------- library/alloc/src/wtf8/mod.rs | 11 ++++++----- library/std/src/ffi/os_str.rs | 13 +++++++------ 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index d91b35c1f6077..8f5928d3a0a28 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1449,8 +1449,8 @@ impl VecDeque { // Safe because: // // * Any slice passed to `drop_in_place` is valid; the second case has - // `len <= front.len()` and returning on `len > self.len()` ensures - // `begin <= back.len()` in the first case + // `len <= front.len()` and returning on `len >= self.len()` ensures + // `begin < back.len()` in the first case // * The head of the VecDeque is moved before calling `drop_in_place`, // so no value is dropped twice if `drop_in_place` panics unsafe { diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 1d9a4347a9616..37315d26fdf19 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -1457,8 +1457,8 @@ impl String { /// Shortens this `String` to the specified length. /// - /// If `new_len` is greater than or equal to the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// Note that this method has no effect on the allocated capacity /// of the string @@ -1480,10 +1480,11 @@ impl String { #[stable(feature = "rust1", since = "1.0.0")] #[track_caller] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_char_boundary(new_len)); - self.vec.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_char_boundary(new_len)); + self.vec.truncate(new_len) } /// Removes the last character from the string buffer and returns it. diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index d2b7837d98fa6..3375b63f01510 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -1790,8 +1790,8 @@ impl Vec { /// assert_eq!(vec, [1, 2]); /// ``` /// - /// No truncation occurs when `len` is greater than the vector's current - /// length: + /// No truncation occurs when `len` is greater than or equal to the + /// vector's current length: /// /// ``` /// let mut vec = vec![1, 2, 3]; @@ -1817,16 +1817,13 @@ impl Vec { // This is safe because: // - // * the slice passed to `drop_in_place` is valid; the `len > self.len` + // * the slice passed to `drop_in_place` is valid; the `len >= self.len` // case avoids creating an invalid slice, and // * the `len` of the vector is shrunk before calling `drop_in_place`, // such that no value will be dropped twice in case `drop_in_place` // were to panic once (if it panics twice, the program aborts). unsafe { - // Note: It's intentional that this is `>` and not `>=`. - // Changing it to `>=` has negative performance - // implications in some cases. See #78884 for more. - if len > self.len { + if len >= self.len { return; } let remaining_len = self.len - len; @@ -3151,7 +3148,8 @@ impl Vec { /// calling the closure `f`. The return values from `f` will end up /// in the `Vec` in the order they have been generated. /// - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method uses a closure to create new values on every push. If /// you'd rather [`Clone`] a given value, use [`Vec::resize`]. If you @@ -3501,7 +3499,8 @@ impl Vec { /// /// If `new_len` is greater than `len`, the `Vec` is extended by the /// difference, with each additional slot filled with `value`. - /// If `new_len` is less than `len`, the `Vec` is simply truncated. + /// If `new_len` is less than or equal to `len`, the `Vec` is simply + /// truncated. /// /// This method requires `T` to implement [`Clone`], /// in order to be able to clone the passed value. diff --git a/library/alloc/src/wtf8/mod.rs b/library/alloc/src/wtf8/mod.rs index 394c41bf36727..4138770a5e9f1 100644 --- a/library/alloc/src/wtf8/mod.rs +++ b/library/alloc/src/wtf8/mod.rs @@ -342,18 +342,19 @@ impl Wtf8Buf { /// Shortens a string to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// /// Panics if `new_len` does not lie on a code point boundary. #[inline] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_code_point_boundary(new_len)); - self.bytes.truncate(new_len) + if new_len >= self.len() { + return; } + assert!(self.is_code_point_boundary(new_len)); + self.bytes.truncate(new_len) } /// Consumes the WTF-8 string and tries to convert it to a vec of bytes. diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index be606ae69d6d9..c3ae2b9e2f522 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -576,8 +576,8 @@ impl OsString { /// Truncate the `OsString` to the specified length. /// - /// If `new_len` is greater than the string's current length, this has no - /// effect. + /// If `new_len` is greater than or equal to the string's current length, + /// this has no effect. /// /// # Panics /// @@ -586,11 +586,12 @@ impl OsString { #[inline] #[unstable(feature = "os_string_truncate", issue = "133262")] pub fn truncate(&mut self, len: usize) { - if len <= self.len() { - self.as_os_str().inner.check_public_boundary(len); - // SAFETY: The length was just checked to be at a valid boundary. - unsafe { self.inner.truncate_unchecked(len) }; + if len >= self.len() { + return; } + self.as_os_str().inner.check_public_boundary(len); + // SAFETY: The length was just checked to be at a valid boundary. + unsafe { self.inner.truncate_unchecked(len) }; } /// Provides plumbing to `Vec::extend_from_slice` without giving full From dfa6584d17920bc1b66a7c0fbb86ae922b74c903 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 18 Oct 2026 11:48:03 +0200 Subject: [PATCH 02/80] ping me for layout invariant changes --- triagebot.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index fe827ebdb653e..b53bbdc323de8 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1103,6 +1103,10 @@ cc = ["@BoxyUwU"] message = "Some changes occurred in `rustc_ty_utils::consts.rs`" cc = ["@BoxyUwU"] +[mentions."compiler/rustc_ty_utils/src/layout/invariant.rs"] +message = "Layout invariants are being changed" +cc = ["@RalfJung"] + [mentions."compiler/rustc_trait_selection/src/solve/"] message = "Some changes occurred to the core trait solver" cc = ["@rust-lang/initiative-trait-system-refactor"] From bbcf01ee3495d0e4f94935707fca180ec46f835b Mon Sep 17 00:00:00 2001 From: Michalis Kokologiannakis Date: Sat, 17 Oct 2026 19:25:39 +0200 Subject: [PATCH 03/80] genmc/build,cpp: Update GenMC version to 0.19.0 GenMC now supports weak CASes and the failure ordering of CASes. Hence, the CAS API is adjusted accordingly (no change in behavior for now). --- src/tools/miri/genmc-sys/build.rs | 2 +- src/tools/miri/genmc-sys/cpp/src/Exploration.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index bde9ce7feaf5b..75906632ba6c0 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -28,7 +28,7 @@ mod downloading { /// The GenMC repository the we get our commit from. pub(crate) const GENMC_GITHUB_URL: &str = "https://github.com/MPI-SWS/genmc.git"; /// The GenMC commit we depend on. It must be available on the specified GenMC repository. - pub(crate) const GENMC_COMMIT: &str = "29b03a66402c4453fc77901ef3be90bb55707cd4"; + pub(crate) const GENMC_COMMIT: &str = "9f6c4c0772d0c42b325681581acc6a0f3eb9b5f7"; /// Ensure that a local GenMC repo is present and set to the correct commit. /// Return the path of the GenMC repo clone. diff --git a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp index 54220c17b67d7..62d1902b3f83f 100644 --- a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp @@ -312,11 +312,13 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { curr_pos(thread_id), GenmcScalarExt::try_to_sval(old_val), success_ordering, + /* fail_ordering: */ success_ordering, SAddr(address), ASize(size), expectedVal, new_val, WriteAttr(), + /* weak: */ false, nullptr, std::nullopt, EventDeps() @@ -327,11 +329,11 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { if (std::holds_alternative(load_ret.result)) return CompareExchangeResultExt::from_invalid(); - const auto* ret_val = std::get_if(&load_ret.result); + const auto* ret_val = std::get_if(&load_ret.result); // FIXME(genmc): handle `HandleResult::Reset` return values. ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result."); - const auto read_old_val = *ret_val; - if (read_old_val != expectedVal) + const auto read_old_val = ret_val->val; + if (!ret_val->success) return CompareExchangeResultExt::failure(read_old_val); // FIXME(GenMC): Add support for modelling spurious failures. From cd093925a4a18e3f4a42d5a31dd40bdd9a76446a Mon Sep 17 00:00:00 2001 From: Michalis Kokologiannakis Date: Sat, 17 Oct 2026 19:27:57 +0200 Subject: [PATCH 04/80] diagnostics,genmc*: Support the failure ordering of CASes --- src/tools/miri/doc/genmc.md | 2 - .../miri/genmc-sys/cpp/src/Exploration.cpp | 9 ++-- .../miri/src/concurrency/genmc/helper.rs | 43 ------------------- src/tools/miri/src/concurrency/genmc/mod.rs | 28 ++---------- src/tools/miri/src/diagnostics.rs | 22 +--------- .../genmc/fail/data_race/cas_failord_mp.rs | 34 +++++++++++++++ .../fail/data_race/cas_failord_mp.stderr | 32 ++++++++++++++ .../atomics/cas_failure_ord_racy_key_init.rs | 4 -- .../cas_failure_ord_racy_key_init.stderr | 29 ------------- .../tests/genmc/pass/atomics/cas_simple.rs | 1 - .../genmc/pass/atomics/cas_simple.stderr | 14 +----- 11 files changed, 75 insertions(+), 143 deletions(-) create mode 100644 src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs create mode 100644 src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index e9d4849bc5960..c9433b755f3ad 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -62,8 +62,6 @@ There are several limitations which can make GenMC miss bugs: - GenMC does not support re-using freed memory for new allocations, so any bugs related to that will be missed. - GenMC does not support `compare_exchange_weak`, so the consequences of spurious failures are not explored. A warning will be emitted if this affects code you wrote (but not if it happens inside your dependencies). -- GenMC does not support the separate failure ordering of `compare_exchange`. Miri will take the maximum of the success and failure ordering and use that for the access; outcomes that rely on the real ordering being weaker will not be explored. - A warning will be emitted if this affects code you wrote (but not if it happens inside your dependencies). - GenMC is incompatible with borrow tracking (Stacked/Tree Borrows). You need to set `-Zmiri-disable-stacked-borrows` to use GenMC. - Like all C++ memory model verification tools, GenMC has to solve the [out-of-thin-air problem](https://www.cl.cam.ac.uk/~pes20/cpp/notes42.html). It takes the [usual approach](https://plv.mpi-sws.org/scfix/paper.pdf) of requiring the union of "program-order" and "reads-from" to be acyclic. diff --git a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp index 62d1902b3f83f..22f7332d16200 100644 --- a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp @@ -298,11 +298,10 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { MemOrdering fail_load_ordering, bool can_fail_spuriously ) -> CompareExchangeResult { - // NOTE: Both the store and load events should get the same `ordering`, it should not be split + // NOTE: On success, both the load and the store get `success_ordering`, it should not be split // into a load and a store component. This means we can have for example `AcqRel` loads and - // stores, but this is intended for CAS operations. - - // FIXME(GenMC): properly handle failure memory ordering. + // stores, but this is intended for CAS operations. On failure, the load gets + // `fail_load_ordering`. auto expectedVal = GenmcScalarExt::to_sval(expected_value); auto new_val = GenmcScalarExt::to_sval(new_value); @@ -312,7 +311,7 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { curr_pos(thread_id), GenmcScalarExt::try_to_sval(old_val), success_ordering, - /* fail_ordering: */ success_ordering, + fail_load_ordering, SAddr(address), ASize(size), expectedVal, diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index 87daa05bd224c..d66c163220646 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -114,50 +114,7 @@ impl AtomicFenceOrd { } } -/// Since GenMC ignores the failure memory ordering and Miri should not detect bugs that don't actually exist, we upgrade the success ordering if required. -/// This means that Miri running in GenMC mode will not explore all possible executions allowed under the RC11 memory model. -/// FIXME(genmc): remove this once GenMC properly supports the failure memory ordering. -pub(super) fn maybe_upgrade_compare_exchange_success_orderings( - success: AtomicRwOrd, - failure: AtomicReadOrd, -) -> AtomicRwOrd { - use AtomicReadOrd::*; - let (success_read, success_write) = success.split_memory_orderings(); - let upgraded_success_read = match (success_read, failure) { - (_, SeqCst) | (SeqCst, _) => SeqCst, - (Acquire, _) | (_, Acquire) => Acquire, - (Relaxed, Relaxed) => Relaxed, - }; - AtomicRwOrd::from_split_memory_orderings(upgraded_success_read, success_write) -} - impl AtomicRwOrd { - /// Split up an atomic read-write memory ordering into a separate read and write ordering. - pub(super) fn split_memory_orderings(self) -> (AtomicReadOrd, AtomicWriteOrd) { - match self { - AtomicRwOrd::Relaxed => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed), - AtomicRwOrd::Acquire => (AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed), - AtomicRwOrd::Release => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Release), - AtomicRwOrd::AcqRel => (AtomicReadOrd::Acquire, AtomicWriteOrd::Release), - AtomicRwOrd::SeqCst => (AtomicReadOrd::SeqCst, AtomicWriteOrd::SeqCst), - } - } - - /// Split up an atomic read-write memory ordering into a separate read and write ordering. - fn from_split_memory_orderings( - read_ordering: AtomicReadOrd, - write_ordering: AtomicWriteOrd, - ) -> Self { - match (read_ordering, write_ordering) { - (AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Relaxed, - (AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Acquire, - (AtomicReadOrd::Relaxed, AtomicWriteOrd::Release) => AtomicRwOrd::Release, - (AtomicReadOrd::Acquire, AtomicWriteOrd::Release) => AtomicRwOrd::AcqRel, - (AtomicReadOrd::SeqCst, _) => AtomicRwOrd::SeqCst, - (_, AtomicWriteOrd::SeqCst) => AtomicRwOrd::SeqCst, - } - } - pub(super) fn to_genmc(self) -> MemOrdering { match self { AtomicRwOrd::Relaxed => MemOrdering::Relaxed, diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index a4eab95fbef5d..c5275ea66cb82 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -14,8 +14,7 @@ use tracing::debug; use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler}; use self::helper::{ - MAX_ACCESS_SIZE, genmc_scalar_to_scalar, get_outcome, - maybe_upgrade_compare_exchange_success_orderings, scalar_to_genmc_scalar, to_genmc_rmw_op, + MAX_ACCESS_SIZE, genmc_scalar_to_scalar, get_outcome, scalar_to_genmc_scalar, to_genmc_rmw_op, }; use self::run::GenmcMode; use self::thread_id_map::ThreadIdMap; @@ -375,14 +374,6 @@ impl GenmcCtx { size.bytes() ); - // Upgrade the success memory ordering to equal the failure ordering, since GenMC currently ignores the failure ordering. - // FIXME(genmc): remove this once GenMC properly supports the failure memory ordering. - let upgraded_success_ordering = - maybe_upgrade_compare_exchange_success_orderings(success, fail); - - // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. - let (effective_failure_ordering, _) = upgraded_success_ordering.split_memory_orderings(); - // Return a warning if we cannot explore all behaviors of this operation. // Only emit this if the operation is "in user code": walk up across `#[track_caller]` // frames, then check if the next frame is local. @@ -393,19 +384,6 @@ impl GenmcCtx { .find(|f| !f.instance().def.requires_caller_location(*ecx.tcx)) .is_none_or(|f| ecx.machine.is_local(f.instance())) }; - if (success != upgraded_success_ordering || effective_failure_ordering != fail) - && show_warning() - { - static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); - ecx.dedup_diagnostic(&DEDUP, |_first| { - NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { - success_ordering: success, - upgraded_success_ordering, - failure_ordering: fail, - effective_failure_ordering, - } - }); - } // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. if can_fail_spuriously && show_warning() { static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); @@ -413,7 +391,7 @@ impl GenmcCtx { } debug!( - "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, {success:?}, orderings: {fail:?}), can fail spuriously: {can_fail_spuriously}" + "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, orderings: {success:?}/{fail:?}), can fail spuriously: {can_fail_spuriously}" ); let cas_result = self.genmc.borrow_mut().pin_mut().handle_compare_exchange( self.active_thread_genmc_tid(&ecx.machine), @@ -422,7 +400,7 @@ impl GenmcCtx { scalar_to_genmc_scalar(ecx, self, expected_old_value)?, scalar_to_genmc_scalar(ecx, self, new_value)?, scalar_to_genmc_scalar(ecx, self, old_value)?, - upgraded_success_ordering.to_genmc(), + success.to_genmc(), fail.to_genmc(), can_fail_spuriously, ); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 7c5b38e9a4891..ae4e81b8ebfe3 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -148,12 +148,6 @@ pub enum NonHaltingDiagnostic { }, ExternTypeReborrow, GenmcCompareExchangeWeak, - GenmcCompareExchangeOrderingMismatch { - success_ordering: AtomicRwOrd, - upgraded_success_ordering: AtomicRwOrd, - failure_ordering: AtomicReadOrd, - effective_failure_ordering: AtomicReadOrd, - }, FileInProcOpened, ConnectingSocketGetsockname, SocketAddressResolution { @@ -651,7 +645,7 @@ impl<'tcx> MiriMachine<'tcx> { ("sharing memory with a native function".to_string(), DiagLevel::Warning), ExternTypeReborrow => ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning), - GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } => + GenmcCompareExchangeWeak => ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning), CreatedPointerTag(..) | PoppedPointerTag(..) @@ -699,20 +693,6 @@ impl<'tcx> MiriMachine<'tcx> { GenmcCompareExchangeWeak => "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures." .to_string(), - GenmcCompareExchangeOrderingMismatch { - success_ordering, - upgraded_success_ordering, - failure_ordering, - effective_failure_ordering, - } => { - let was_upgraded_msg = if success_ordering != upgraded_success_ordering { - format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'") - } else { - assert_ne!(failure_ordering, effective_failure_ordering); - format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'") - }; - format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") - } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts"), SocketAddressResolution { error } => format!("address resolution failed: {error}"), diff --git a/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs new file mode 100644 index 0000000000000..5907fb547dbe3 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.rs @@ -0,0 +1,34 @@ +// Translated from GenMC's test `wrong/racy/cas-failord-mp`. +// The race on `DATA` only manifests if the CAS fails, and its failure ordering is properly modeled. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicI32 = AtomicI32::new(0); +static mut DATA: i32 = 0; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let ids = [ + spawn_pthread_closure(|| { + DATA = 42; + X.store(1, Release); + }), + spawn_pthread_closure(|| { + if X.compare_exchange(2, 3, Acquire, Relaxed) == Err(1) { + assert!(DATA == 42); //~ ERROR: Undefined Behavior: Non-atomic race + } + }), + ]; + join_pthreads(ids); + } + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr new file mode 100644 index 0000000000000..353ab85f17325 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/cas_failord_mp.stderr @@ -0,0 +1,32 @@ +Running GenMC Verification... +error: Undefined Behavior: Non-atomic race + --> tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC + | +LL | assert!(DATA == 42); + | ^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` + = note: stack backtrace: + 0: miri_start::{closure#1} + at tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC + 1: as std::ops::FnOnce<()>>::call_once + at RUSTLIB/alloc/src/boxed.rs:LL:CC + 2: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/cas_failord_mp.rs:LL:CC}> + at tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC +note: the last function in that backtrace got called indirectly due to this code + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | / libc::pthread_create( +LL | | &raw mut thread_id, +LL | | attr, +LL | | thread_func::, +LL | | Box::into_raw(f) as *mut c_void, +LL | | ) + | |_________^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs index cf20482092ede..073a85d560379 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs @@ -4,10 +4,6 @@ // Two threads race to initialize a key, which is just an index into an array in this test. // The (Acquire, Release) orderings on the compare_exchange prevent any data races for reading from `VALUES[key]`. -// FIXME(genmc): GenMC does not model the failure ordering of compare_exchange currently. -// Miri thus upgrades the success ordering to prevent showing any false data races in cases like this one. -// Once GenMC supports the failure ordering, this test should work without the upgrading. - #![no_main] #[path = "../../../utils/genmc.rs"] diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr index f0c3377623058..bde951866d013 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.stderr @@ -1,31 +1,2 @@ Running GenMC Verification... -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Success ordering 'Release' was upgraded to 'AcqRel' to match failure ordering 'Acquire'. Miri with GenMC might miss bugs related to this memory access. - --> tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - | -LL | match KEY.compare_exchange(KEY_SENTVAL, key, Release, Acquire) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - | - = note: this is on thread `unnamed-ID` - = note: stack backtrace: - 0: get_or_init - at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - 1: miri_start::{closure#0} - at tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC - 2: as std::ops::FnOnce<()>>::call_once - at RUSTLIB/alloc/src/boxed.rs:LL:CC - 3: genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs:LL:CC}> - at tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC -note: the last function in that backtrace got called indirectly due to this code - --> tests/genmc/pass/atomics/../../../utils/genmc.rs:LL:CC - | -LL | / libc::pthread_create( -LL | | &raw mut thread_id, -LL | | attr, -LL | | thread_func::, -LL | | Box::into_raw(f) as *mut c_void, -LL | | ) - | |_________^ - Verification complete with 2 executions. No errors found. -warning: 1 warning emitted - diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs index 5ef1dd2352d68..33222ed6679c5 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.rs @@ -30,7 +30,6 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { } // Check that we emit warnings for cases that are not fully supported. - let _ = VALUE.compare_exchange(99, 99, SeqCst, Relaxed); let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); 0 diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr index 59785a2f19d7b..dd11c2ad3c2c1 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr @@ -1,16 +1,4 @@ Running GenMC Verification... -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'SeqCst', the failure ordering 'Relaxed' is treated like 'SeqCst'. Miri with GenMC might miss bugs related to this memory access. - --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC - | -LL | let _ = VALUE.compare_exchange(99, 99, SeqCst, Relaxed); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - -warning: GenMC currently does not model the failure ordering for `compare_exchange`. Success ordering 'Relaxed' was upgraded to 'SeqCst' to match failure ordering 'SeqCst'. Miri with GenMC might miss bugs related to this memory access. - --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC - | -LL | let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC | @@ -18,5 +6,5 @@ LL | let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code Verification complete with 1 executions. No errors found. -warning: 3 warnings emitted +warning: 1 warning emitted From c26810bbe95cfb94c1dc6d1fd1605db6860629af Mon Sep 17 00:00:00 2001 From: Michalis Kokologiannakis Date: Sat, 17 Oct 2026 19:56:56 +0200 Subject: [PATCH 05/80] diagnostics,genmc*: Support weak CASes Modeling spurious failures is opt-in (see `-Zmiri-genmc-weak-cas-spurious-failures`), since Miri-GenMC cannot bound all retry loops involving weak CASes (e.g., the ones in the standard library). --- src/tools/miri/doc/genmc.md | 3 ++- .../miri/genmc-sys/cpp/src/Exploration.cpp | 4 +--- .../miri/src/concurrency/genmc/config.rs | 4 ++++ src/tools/miri/src/concurrency/genmc/mod.rs | 15 +++++++++---- src/tools/miri/src/diagnostics.rs | 6 ++++- .../genmc/pass/atomics/cas_simple.stderr | 4 +++- .../genmc/pass/atomics/cas_weak_spurious.rs | 22 +++++++++++++++++++ .../pass/atomics/cas_weak_spurious.stderr | 2 ++ 8 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs create mode 100644 src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index c9433b755f3ad..b39bc57362c56 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -52,6 +52,7 @@ Note that `cargo miri test` in GenMC mode is currently not supported. - `debug2`: Print the execution graph after every memory access. - `debug3`: Print reads-from values considered by GenMC. - `-Zmiri-genmc-verbose`: Show more information, such as estimated number of executions, and time taken for verification. +- `-Zmiri-genmc-weak-cas-spurious-failures`: Model spurious failures of `compare_exchange_weak`. This is off by default, since all retry loops around `compare_exchange_weak` then need to be bounded (see [below](#eliminating-unbounded-loops)), which is not possible for loops inside the standard library (e.g., in the runtime around `fn main`). - `-Zmiri-disable-weak-memory-emulation`: Disable any weak memory effects (effectively upgrading all atomic orderings in the program to `SeqCst`). This option may reduce the number of explored program executions, but any bugs related to weak memory effects will be missed. This option can help determine if an error is caused by weak memory effects (i.e., if it disappears with this option enabled). @@ -60,7 +61,7 @@ Note that `cargo miri test` in GenMC mode is currently not supported. There are several limitations which can make GenMC miss bugs: - GenMC does not support re-using freed memory for new allocations, so any bugs related to that will be missed. -- GenMC does not support `compare_exchange_weak`, so the consequences of spurious failures are not explored. +- By default, spurious failures of `compare_exchange_weak` are not explored (see `-Zmiri-genmc-weak-cas-spurious-failures`). A warning will be emitted if this affects code you wrote (but not if it happens inside your dependencies). - GenMC is incompatible with borrow tracking (Stacked/Tree Borrows). You need to set `-Zmiri-disable-stacked-borrows` to use GenMC. - Like all C++ memory model verification tools, GenMC has to solve the [out-of-thin-air problem](https://www.cl.cam.ac.uk/~pes20/cpp/notes42.html). diff --git a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp index 22f7332d16200..eb29035525e6c 100644 --- a/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/Exploration.cpp @@ -317,7 +317,7 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { expectedVal, new_val, WriteAttr(), - /* weak: */ false, + can_fail_spuriously, nullptr, std::nullopt, EventDeps() @@ -335,8 +335,6 @@ void MiriGenmcInterface::handle_fence(ThreadId thread_id, MemOrdering ord) { if (!ret_val->success) return CompareExchangeResultExt::failure(read_old_val); - // FIXME(GenMC): Add support for modelling spurious failures. - const auto store_ret = GenMCDriver::handleCasWrite( nullptr, curr_pos(thread_id), diff --git a/src/tools/miri/src/concurrency/genmc/config.rs b/src/tools/miri/src/concurrency/genmc/config.rs index ea5760f7acde8..6749f8acdc52f 100644 --- a/src/tools/miri/src/concurrency/genmc/config.rs +++ b/src/tools/miri/src/concurrency/genmc/config.rs @@ -19,6 +19,8 @@ pub struct GenmcConfig { /// Enable more verbose output, such as number of executions estimate /// and time to completion of verification step. pub(super) verbose_output: bool, + /// Model spurious failures of `compare_exchange_weak`. + pub(super) weak_cas_spurious_failures: bool, } impl GenmcConfig { @@ -74,6 +76,8 @@ impl GenmcConfig { genmc_config.print_genmc_output = true; } else if trimmed_arg == "verbose" { genmc_config.verbose_output = true; + } else if trimmed_arg == "weak-cas-spurious-failures" { + genmc_config.weak_cas_spurious_failures = true; } else { return Err(format!("Invalid GenMC argument: \"-Zmiri-genmc-{trimmed_arg}\"")); } diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index c5275ea66cb82..4f23203b956a4 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -122,6 +122,9 @@ pub struct GenmcCtx { /// State that persists across executions. /// All `GenmcCtx` in one verification step share this state. global_state: Arc, + + /// Whether to model spurious failures of `compare_exchange_weak`. + weak_cas_spurious_failures: bool, } /// GenMC Context creation and administrative / query actions @@ -134,7 +137,12 @@ impl GenmcCtx { genmc_config.log_level, /* do_estimation: */ mode == GenmcMode::Estimation, )); - Self { genmc, exec_state: Default::default(), global_state } + Self { + genmc, + exec_state: Default::default(), + global_state, + weak_cas_spurious_failures: genmc_config.weak_cas_spurious_failures, + } } fn get_estimation_results(&self) -> EstimationResult { @@ -384,8 +392,7 @@ impl GenmcCtx { .find(|f| !f.instance().def.requires_caller_location(*ecx.tcx)) .is_none_or(|f| ecx.machine.is_local(f.instance())) }; - // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. - if can_fail_spuriously && show_warning() { + if can_fail_spuriously && !self.weak_cas_spurious_failures && show_warning() { static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); ecx.dedup_diagnostic(&DEDUP, |_first| NonHaltingDiagnostic::GenmcCompareExchangeWeak); } @@ -402,7 +409,7 @@ impl GenmcCtx { scalar_to_genmc_scalar(ecx, self, old_value)?, success.to_genmc(), fail.to_genmc(), - can_fail_spuriously, + can_fail_spuriously && self.weak_cas_spurious_failures, ); let (old_value, is_success, is_coherence_order_maximal_write) = diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index ae4e81b8ebfe3..da4679dcef3a1 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -691,7 +691,7 @@ impl<'tcx> MiriMachine<'tcx> { ExternTypeReborrow => format!("reborrow of a reference to `extern type` is not properly supported"), GenmcCompareExchangeWeak => - "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures." + "GenMC does not model spurious failures of `compare_exchange_weak` by default. Miri with GenMC might miss bugs related to spurious failures." .to_string(), FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts"), @@ -801,6 +801,10 @@ impl<'tcx> MiriMachine<'tcx> { ), ] } + GenmcCompareExchangeWeak => + vec![note!( + "add `-Zmiri-genmc-weak-cas-spurious-failures` to MIRIFLAGS to explore spurious failures; all retry loops around `compare_exchange_weak` then need to be bounded" + )], _ => vec![], }; diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr index dd11c2ad3c2c1..0203748beab7c 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_simple.stderr @@ -1,9 +1,11 @@ Running GenMC Verification... -warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. +warning: GenMC does not model spurious failures of `compare_exchange_weak` by default. Miri with GenMC might miss bugs related to spurious failures. --> tests/genmc/pass/atomics/cas_simple.rs:LL:CC | LL | let _ = VALUE.compare_exchange_weak(99, 99, Relaxed, SeqCst); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = help: add `-Zmiri-genmc-weak-cas-spurious-failures` to MIRIFLAGS to explore spurious failures; all retry loops around `compare_exchange_weak` then need to be bounded Verification complete with 1 executions. No errors found. warning: 1 warning emitted diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs new file mode 100644 index 0000000000000..010236dbfb2b4 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-genmc-weak-cas-spurious-failures + +// Translated from GenMC's test `correct/synthetic/weak-cas-spurious`. +// Observable spurious failure of a weak CAS (not used in a loop). + +#![no_main] + +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::*; + +static X: AtomicI32 = AtomicI32::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // Either the CAS succeeds, or it fails spuriously and leaves `X` unchanged. + match X.compare_exchange_weak(0, 1, Relaxed, Relaxed) { + Ok(0) => assert!(X.load(Relaxed) == 1), + Err(0) => assert!(X.load(Relaxed) == 0), + _ => std::process::abort(), + } + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr new file mode 100644 index 0000000000000..bde951866d013 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_weak_spurious.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. From 0fc7ab9a42ef759205f5ea55f1eb760753e18d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 14:27:45 +0200 Subject: [PATCH 06/80] Update subtree sync docs --- compiler/rustc_codegen_gcc/doc/subtree.md | 55 +++-------------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index fcac399e46542..00b5d377816fd 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -1,54 +1,9 @@ -# git subtree sync +# Subtree sync procedure -`rustc_codegen_gcc` is a subtree of the rust compiler. As such, it needs to be -synced from time to time to ensure changes that happened on their side are also -included on our side. +`rustc_codegen_gcc` is using [Josh] to perform subtree synces with the `rust-lang/rust` repository. -### How to install a forked git-subtree +[Josh]: https://josh-project.dev/docs/intro.html -Using git-subtree with `rustc` requires a patched git to make it work. -The PR that is needed is [here](https://github.com/gitgitgadget/git/pull/493). -Use the following instructions to install it: +The procedure for performing subtree synchronizations is documented [here][rdg-josh]. -```bash -git clone git@github.com:tqc/git.git -cd git -git checkout tqc/subtree -make -make install -cd contrib/subtree -make -cp git-subtree ~/bin -``` - -### Syncing with rust compiler - -Do a sync with this command: - -```bash -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -cd ../rustc_codegen_gcc -git checkout master -git pull -git checkout sync_branch_name -git merge master -``` - -To send the changes to the rust repo: - -```bash -cd ../rust -git pull origin master -git checkout -b subtree-update_cg_gcc_YYYY-MM-DD -PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master -# Don't forget to update the `gcc` submodule to the same version as the -# one in `rustc_codegen_gcc/libgccjit.version`. -git push - -# Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later. -PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name -``` - -FIXME: write a script that does the above. - -https://rust-lang.zulipchat.com/#narrow/stream/301329-t-devtools/topic/subtree.20madness/near/258877725 +[rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree From 6034ed9cbaf47f4029a4758a87ba072541c9d594 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Mon, 19 Oct 2026 01:14:36 -0700 Subject: [PATCH 07/80] Use `eval_context_ref` and `&self` where mutable access is not needed --- .../miri/src/borrow_tracker/stacked_borrows/mod.rs | 4 ++-- .../miri/src/borrow_tracker/tree_borrows/mod.rs | 8 ++++---- src/tools/miri/src/concurrency/data_race.rs | 8 ++++---- src/tools/miri/src/concurrency/init_once.rs | 4 ++-- src/tools/miri/src/concurrency/sync.rs | 12 ++++++------ src/tools/miri/src/helpers.rs | 4 ++-- src/tools/miri/src/shims/backtrace.rs | 4 ++-- src/tools/miri/src/shims/files.rs | 4 ++-- src/tools/miri/src/shims/io_error.rs | 4 ++-- src/tools/miri/src/shims/time.rs | 4 ++-- src/tools/miri/src/shims/tls.rs | 4 ++-- src/tools/miri/src/shims/unix/env.rs | 8 ++++---- src/tools/miri/src/shims/unix/foreign_items.rs | 4 ++-- src/tools/miri/src/shims/unix/freebsd/sync.rs | 4 ++-- src/tools/miri/src/shims/unix/fs.rs | 6 +++--- src/tools/miri/src/shims/unix/thread.rs | 4 ++-- src/tools/miri/src/shims/windows/env.rs | 4 ++-- src/tools/miri/src/shims/windows/handle.rs | 4 ++-- 18 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index d6ac9390dc0eb..9a49b6ca1db14 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -929,8 +929,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn print_stacks(&mut self, alloc_id: AllocId) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn print_stacks(&self, alloc_id: AllocId) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let alloc_extra = this.get_alloc_extra(alloc_id)?; let stacks = alloc_extra.borrow_tracker_sb().borrow(); for (range, stack) in stacks.stacks.iter_all() { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index f2875548d08d5..bf38ca356df45 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -560,8 +560,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Display the tree. - fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn print_tree(&self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let alloc_extra = this.get_alloc_extra(alloc_id)?; let tree_borrows = alloc_extra.borrow_tracker_tb().borrow(); let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow(); @@ -572,12 +572,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// The name given is `name` and the pointer that receives it is the `nth_parent` /// of `ptr` (with 0 representing `ptr` itself) fn tb_give_pointer_debug_name( - &mut self, + &self, ptr: Pointer, nth_parent: u8, name: &str, ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let (tag, alloc_id) = match ptr.provenance { Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id), Some(Provenance::Wildcard) => { diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index f9b2a0eece9b8..acf45c84bcc6d 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1508,11 +1508,11 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { /// Update the data-race detector for an atomic write occurring at the /// associated memory-place and on the current thread. fn validate_atomic_store( - &mut self, + &self, place: &MPlaceTy<'tcx>, atomic: AtomicWriteOrd, ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); this.validate_atomic_op( place, atomic, @@ -1530,14 +1530,14 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { /// Update the data-race detector for an atomic read-modify-write occurring /// at the associated memory place and on the current thread. fn validate_atomic_rmw( - &mut self, + &self, place: &MPlaceTy<'tcx>, atomic: AtomicRwOrd, ) -> InterpResult<'tcx> { use AtomicRwOrd::*; let acquire = matches!(atomic, Acquire | AcqRel | SeqCst); let release = matches!(atomic, Release | AcqRel | SeqCst); - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); this.validate_atomic_op( place, atomic, diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs index 5c3541ffbe4c1..be010bb0458fb 100644 --- a/src/tools/miri/src/concurrency/init_once.rs +++ b/src/tools/miri/src/concurrency/init_once.rs @@ -140,8 +140,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Synchronize with the previous completion of an InitOnce. /// Must only be called after checking that it is complete. #[inline] - fn init_once_observe_completed(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn init_once_observe_completed(&self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let init_once = init_once_ref.0.borrow(); assert_eq!( diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index 0175e5573d686..14bdcb37c32f9 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -445,8 +445,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Lock by setting the mutex owner and increasing the lock count. - fn mutex_lock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn mutex_lock(&self, mutex_ref: &MutexRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); let mut mutex = mutex_ref.0.borrow_mut(); if let Some(current_owner) = mutex.owner { @@ -541,8 +541,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Read-lock the lock by adding the `reader` the list of threads that own /// this lock. - fn rwlock_reader_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn rwlock_reader_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); trace!("rwlock_reader_lock: now also held (one more time) by {:?}", thread); let mut rwlock = rwlock_ref.0.borrow_mut(); @@ -628,8 +628,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lock by setting the writer that owns the lock. #[inline] - fn rwlock_writer_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); + fn rwlock_writer_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> { + let this = self.eval_context_ref(); let thread = this.active_thread(); trace!("rwlock_writer_lock: now held by {:?}", thread); diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 26f2a8a459f79..7db3f4aad641d 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -954,10 +954,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lookup an array of immediates from any linker sections matching the provided predicate, /// with the spans of where they were found. fn lookup_link_section( - &mut self, + &self, include_name: impl Fn(&str) -> bool, ) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let tcx = this.tcx.tcx; let mut array = vec![]; diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 61194a1bba98f..774f7f1e8f953 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -84,10 +84,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } fn resolve_frame_pointer( - &mut self, + &self, ptr: &OpTy<'tcx>, ) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let ptr = this.read_pointer(ptr)?; // Take apart the pointer, we need its pieces. The offset encodes the span. diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d25744a40e242..29d0d28f804f7 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -687,12 +687,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Write data to a host `Write` type, with the bytes taken from machine memory. fn write_to_host( - &mut self, + &self, mut file: impl io::Write, len: usize, ptr: Pointer, ) -> InterpResult<'tcx, Result> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; let result = file.write(bytes); diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 22a176c803819..4d1ffb1d9b15f 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -284,8 +284,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - fn io_error_to_errnum(&mut self, err: impl Into) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn io_error_to_errnum(&self, err: impl Into) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); interp_ok(match err.into() { HostError(err) => this.host_error_to_errnum(err)?, LibcError(name) => this.eval_libc(name), diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 18267a3730c34..f86780eb87b7a 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -490,8 +490,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Parse a `timeval` struct and return it as a [`Duration`]. It returns [`None`] /// if the value in the `timeval` struct is invalid. Some libc functions will return /// EINVAL in this case. - fn read_timeval(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { - let this = self.eval_context_mut(); + fn read_timeval(&self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); let sec_field = this.project_field_named(tp, "tv_sec")?; let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?; diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 0444efd0ac62a..5554038442dc6 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -336,8 +336,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Lookup all the FLS keys (which are stored as TLS keys) that have a destructor. /// See also: `schedule_next_windows_fls_dtor`. - fn lookup_windows_fls_keys_with_dtors(&mut self) -> InterpResult<'tcx, VecDeque> { - let this = self.eval_context_mut(); + fn lookup_windows_fls_keys_with_dtors(&self) -> InterpResult<'tcx, VecDeque> { + let this = self.eval_context_ref(); interp_ok( this.machine diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs index d8352d9c06553..1d8e5e95c40d4 100644 --- a/src/tools/miri/src/shims/unix/env.rs +++ b/src/tools/miri/src/shims/unix/env.rs @@ -126,8 +126,8 @@ fn alloc_environ_block<'tcx>( impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn getenv(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> { - let this = self.eval_context_mut(); + fn getenv(&self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> { + let this = self.eval_context_ref(); this.assert_target_os_is_unix("getenv"); let name_ptr = this.read_pointer(name_op)?; @@ -326,8 +326,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn getpid(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn getpid(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); this.assert_target_os_is_unix("getpid"); // The reason we need to do this wacky of a conversion is because diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 9491698265c88..f2c16b13f4f6c 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -52,8 +52,8 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Querying system information - fn sysconf(&mut self, val: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn sysconf(&self, val: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let name = this.read_scalar(val)?.to_i32()?; // FIXME: Which of these are POSIX, and which are GNU/Linux? diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs index 28f912ab973fe..f1a9572129c99 100644 --- a/src/tools/miri/src/shims/unix/freebsd/sync.rs +++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs @@ -213,8 +213,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Parses a `_umtx_time` struct. /// Returns `None` if the underlying `timespec` struct is invalid. - fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { - let this = self.eval_context_mut(); + fn read_umtx_time(&self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); // Only flag allowed is UMTX_ABSTIME. let abs_time = this.eval_libc_u32("UMTX_ABSTIME"); diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 97de7a505f512..9aae844729fae 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1677,12 +1677,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Shared logic between `posix_fallocate` and `linux_fallocate`. fn fallocate_impl( - &mut self, + &self, fd_num: i32, offset: i64, len: i64, ) -> InterpResult<'tcx, Result<(), IoError>> { - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); // EINVAL is returned/set when: "offset was less than 0, or len was less than or equal to 0". if offset < 0 || len <= 0 { @@ -1735,7 +1735,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd_op)?.to_i32()?; - self.ffullsync_fd(fd) + this.ffullsync_fd(fd) } fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> { diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs index c2322e38633e3..6564849bb7675 100644 --- a/src/tools/miri/src/shims/unix/thread.rs +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -80,8 +80,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_u32(0)) } - fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn pthread_self(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let thread_id = this.active_thread(); interp_ok(Scalar::from_uint(thread_id.to_u32(), this.libc_ty_layout("pthread_t").size)) diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs index f203a6833a424..f8c9bed395e7f 100644 --- a/src/tools/miri/src/shims/windows/env.rs +++ b/src/tools/miri/src/shims/windows/env.rs @@ -198,8 +198,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } #[allow(non_snake_case)] - fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn GetCurrentProcessId(&self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); this.assert_target_os(Os::Windows, "GetCurrentProcessId"); interp_ok(Scalar::from_u32(this.get_pid())) diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index da9247c844c4b..4655b2dac2bf3 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -231,8 +231,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ))) } - fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); + fn GetStdHandle(&self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let which = this.read_scalar(which)?.to_i32()?; let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?; From a80cbc8812dd6b86c743d1526d95db911a76c77e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 10:28:01 +0200 Subject: [PATCH 08/80] do not call pthread dtors on main thread return --- src/tools/miri/src/eval.rs | 3 +- src/tools/miri/src/shims/tls.rs | 35 +++++++---- .../miri/src/shims/unix/foreign_items.rs | 2 - ...g_abi.rs => pthread_key_dtor_wrong_abi.rs} | 8 ++- ...derr => pthread_key_dtor_wrong_abi.stderr} | 3 +- .../fail/panic/tls_macro_const_drop_panic.rs | 4 +- .../tests/fail/panic/tls_macro_drop_panic.rs | 4 +- .../tests/pass-dep/concurrency/pthread_key.rs | 63 +++++++++++++++++++ .../pthread_key_drop_main_return.rs | 22 +++++++ ...rop_order.rs => pthread_key_drop_order.rs} | 5 +- .../miri/tests/pass/threadleak_ignored.rs | 4 +- .../pass/threadleak_ignored.stack.stderr | 1 - .../tests/pass/threadleak_ignored.tree.stderr | 1 - ...adleak_ignored.tree_implicit_writes.stderr | 1 - ...hread.rs => tls_macro_drop_main_return.rs} | 3 +- ...dout => tls_macro_drop_main_return.stdout} | 0 ...n_tls_callback.rs => win_thread_atexit.rs} | 16 +++-- ...llback.stderr => win_thread_atexit.stderr} | 1 + 18 files changed, 139 insertions(+), 37 deletions(-) rename src/tools/miri/tests/fail-dep/concurrency/{tls_pthread_dtor_wrong_abi.rs => pthread_key_dtor_wrong_abi.rs} (64%) rename src/tools/miri/tests/fail-dep/concurrency/{tls_pthread_dtor_wrong_abi.stderr => pthread_key_dtor_wrong_abi.stderr} (88%) create mode 100644 src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs create mode 100644 src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs rename src/tools/miri/tests/pass-dep/concurrency/{tls_pthread_drop_order.rs => pthread_key_drop_order.rs} (89%) delete mode 100644 src/tools/miri/tests/pass/threadleak_ignored.stack.stderr delete mode 100644 src/tools/miri/tests/pass/threadleak_ignored.tree.stderr delete mode 100644 src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr rename src/tools/miri/tests/pass/tls/{tls_macro_drop_single_thread.rs => tls_macro_drop_main_return.rs} (94%) rename src/tools/miri/tests/pass/tls/{tls_macro_drop_single_thread.stdout => tls_macro_drop_main_return.stdout} (100%) rename src/tools/miri/tests/pass/tls/{win_tls_callback.rs => win_thread_atexit.rs} (57%) rename src/tools/miri/tests/pass/tls/{win_tls_callback.stderr => win_thread_atexit.stderr} (50%) diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 6e51ff42367dc..44b0290efc382 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -256,7 +256,8 @@ impl<'tcx> MainThreadState<'tcx> { } } Running => { - *self = TlsDtors(Default::default()); + // Main code is done, the program is exiting. + *self = TlsDtors(tls::TlsDtorsState::new_for_exit()); } TlsDtors(state) => match state.on_stack_empty(this)? { diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 0444efd0ac62a..2ad3082582358 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -228,8 +228,9 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>); enum TlsDtorsStatePriv<'tcx> { #[default] Init, - MacOsDtors, PthreadDtors(RunningPthreadDtorState), + AfterPthreadDtors, + MacOsDtors, /// For Windows, we support two different ways dtors can be registered. /// 1. Functions that are registered via the `FlsAlloc` function, which are invoked one by one. /// 2. Functions from the magic `.CRT$XLB` linker section. @@ -239,6 +240,12 @@ enum TlsDtorsStatePriv<'tcx> { } impl<'tcx> TlsDtorsState<'tcx> { + /// Returns the state we should start running when the program is exiting. + pub fn new_for_exit() -> Self { + // Pthread destructors get skipped on `exit`. + Self(TlsDtorsStatePriv::AfterPthreadDtors) + } + pub fn on_stack_empty( &mut self, this: &mut MiriInterpCx<'tcx>, @@ -247,6 +254,21 @@ impl<'tcx> TlsDtorsState<'tcx> { let new_state = 'new_state: { match &mut self.0 { Init => { + if this.target_os_is_unix() { + // We run the pthread destructors first, since they get skipped on `exit`. + break 'new_state PthreadDtors(Default::default()); + } else { + break 'new_state AfterPthreadDtors; + } + } + PthreadDtors(state) => { + match this.schedule_next_pthread_tls_dtor(state)? { + Poll::Pending => return interp_ok(Poll::Pending), // just keep going + Poll::Ready(()) => break 'new_state AfterPthreadDtors, + } + } + AfterPthreadDtors => { + // Next, we run the platform-specific destructors match this.tcx.sess.target.os { Os::MacOs => { // macOS has a _tlv_atexit function that allows @@ -254,10 +276,6 @@ impl<'tcx> TlsDtorsState<'tcx> { // These are run first. break 'new_state MacOsDtors; } - _ if this.target_os_is_unix() => { - // All other Unixes directly jump to running the pthread dtors. - break 'new_state PthreadDtors(Default::default()); - } Os::Windows => { // Determine which destructors to run. let dtors = this.lookup_windows_tls_dtors()?; @@ -276,7 +294,6 @@ impl<'tcx> TlsDtorsState<'tcx> { ); } _ => { - // No TLS dtor support. break 'new_state Done; } } @@ -286,12 +303,6 @@ impl<'tcx> TlsDtorsState<'tcx> { Poll::Pending => return interp_ok(Poll::Pending), // After all macOS destructors are run, the system switches // to destroying the pthread destructors. - Poll::Ready(()) => break 'new_state PthreadDtors(Default::default()), - } - } - PthreadDtors(state) => { - match this.schedule_next_pthread_tls_dtor(state)? { - Poll::Pending => return interp_ok(Poll::Pending), // just keep going Poll::Ready(()) => break 'new_state Done, } } diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 9491698265c88..e8548572904b5 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -902,7 +902,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_key_delete" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "C" fn(libc::pthread_key_t) -> i32), (link_name, abi, args), @@ -913,7 +912,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_getspecific" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "C" fn(libc::pthread_key_t) -> *_), (link_name, abi, args), diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs similarity index 64% rename from src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs rename to src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs index b5eec2db201cb..2b258c2970a59 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs @@ -1,6 +1,6 @@ //@ignore-target: windows # No pthreads on Windows -use std::{mem, ptr}; +use std::{mem, ptr, thread}; pub type Key = libc::pthread_key_t; @@ -16,6 +16,10 @@ unsafe fn dtor(_ptr: *mut u8) {} fn main() { unsafe { let key = create(dtor); - libc::pthread_setspecific(key, ptr::without_provenance(1)); + + // Main thread never calls these dtors, so set the key in another thread. + thread::spawn(move || { + libc::pthread_setspecific(key, ptr::without_provenance(1)); + }); } } diff --git a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr similarity index 88% rename from src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr rename to src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr index 32e92319cdf35..08ec4a6b67b31 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.stderr +++ b/src/tools/miri/tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.stderr @@ -1,11 +1,12 @@ error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C" - --> tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs:LL:CC + --> tests/fail-dep/concurrency/pthread_key_dtor_wrong_abi.rs:LL:CC | LL | assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` = note: this error occurred while pushing a call frame onto an empty stack = note: the span indicates which code caused the function to be called, but may not be the literal call site diff --git a/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs index c25684af50902..12ce5538055b3 100644 --- a/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs +++ b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs @@ -19,5 +19,7 @@ thread_local! { } fn main() { - NOISY.with(|_| ()); + std::thread::spawn(|| { + NOISY.with(|_| ()); + }); } diff --git a/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs index 32fd8318f7963..b6a0526987802 100644 --- a/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs +++ b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs @@ -17,5 +17,7 @@ thread_local! { } fn main() { - NOISY.with(|_| ()); + std::thread::spawn(|| { + NOISY.with(|_| ()); + }); } diff --git a/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs new file mode 100644 index 0000000000000..0fa42b4f26926 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key.rs @@ -0,0 +1,63 @@ +//@ignore-target: windows # No pthreads on Windows +//@run-native + +use std::{process, ptr, thread}; + +static mut STATE: i32 = 0; + +unsafe extern "C" fn dtor(ptr: *mut libc::c_void) { + assert_eq!(ptr.addr(), 43); + + // We expect to be called exactly once. + if STATE == 0 { + STATE = 1; + } else { + process::abort(); + } +} + +fn main() { + unsafe { + let dtor = dtor as unsafe extern "C" fn(*mut libc::c_void); + + let mut key: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key, Some(dtor)), 0); + let mut key2: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key2, Some(dtor)), 0); + + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(42)); + assert_eq!(r, 0); + let ptr = libc::pthread_getspecific(key); + assert_eq!(ptr.addr(), 42); + + let t = thread::spawn(move || { + // Set the value the dtor will see. + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(43)); + assert_eq!(r, 0); + }); + t.join().unwrap(); + // This called the dtor. + assert_eq!({ STATE }, 1); + + // A thread that does not set the key, does not call the dtor. + thread::spawn(move || {}).join().unwrap(); + + // Destroying the key means the dtor does not get called. + let t = thread::spawn(move || { + let r = libc::pthread_setspecific(key2, ptr::without_provenance_mut(44)); + assert_eq!(r, 0); + let r = libc::pthread_key_delete(key2); + assert_eq!(r, 0); + }); + t.join().unwrap(); + + // And when we exit, that also does not call the dtor. + let t = thread::spawn(move || { + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(45)); + assert_eq!(r, 0); + + std::process::exit(0); + }); + t.join().unwrap(); + } +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs new file mode 100644 index 0000000000000..07becf6e28855 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_main_return.rs @@ -0,0 +1,22 @@ +//@ignore-target: windows # No pthreads on Windows +//@run-native + +use std::{process, ptr}; + +unsafe extern "C" fn dtor(_ptr: *mut libc::c_void) { + process::abort(); +} + +// Returning from `main` does not call the destructor (consistent with the behavior on `exit`, +// which is tested in `pthread_key.rs`). +fn main() { + unsafe { + let dtor = dtor as unsafe extern "C" fn(*mut libc::c_void); + + let mut key: libc::pthread_key_t = 0; + assert_eq!(libc::pthread_key_create(&mut key, Some(dtor)), 0); + + let r = libc::pthread_setspecific(key, ptr::without_provenance_mut(42)); + assert_eq!(r, 0); + } +} diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs similarity index 89% rename from src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs rename to src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs index 6ec4b6e48bc3f..f4090f22654a0 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/pthread_key_drop_order.rs @@ -1,9 +1,6 @@ //@ignore-target: windows # No pthreads on Windows +//@run-native //! Test that pthread_key destructors are run in the right order. -//! Note that these are *not* used by actual `thread_local!` on Linux! Those use -//! `destructors::register` from the stdlib instead. In Miri this ends up hitting -//! the fallback path in `guard::key::enable`, which uses a *single* pthread_key -//! to manage a thread-local list of dtors to call. // FIXME(static_mut_refs): use raw pointers instead of references #![allow(static_mut_refs)] diff --git a/src/tools/miri/tests/pass/threadleak_ignored.rs b/src/tools/miri/tests/pass/threadleak_ignored.rs index e43b58cdbd158..96547625b8bdb 100644 --- a/src/tools/miri/tests/pass/threadleak_ignored.rs +++ b/src/tools/miri/tests/pass/threadleak_ignored.rs @@ -10,7 +10,7 @@ use std::cell::RefCell; struct LoudDrop(i32); impl Drop for LoudDrop { fn drop(&mut self) { - eprintln!("Dropping {}", self.0); + panic!("Dropping {}", self.0); } } @@ -19,8 +19,6 @@ thread_local! { } fn main() { - X.with(|x| *x.borrow_mut() = Some(LoudDrop(0))); - // Set up a channel so that we can learn when the other thread initialized `X` // (so that we are sure there is something to drop). let (send, recv) = std::sync::mpsc::channel::<()>(); diff --git a/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr b/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.stack.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr b/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.tree.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr b/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr deleted file mode 100644 index 7557f49c7584b..0000000000000 --- a/src/tools/miri/tests/pass/threadleak_ignored.tree_implicit_writes.stderr +++ /dev/null @@ -1 +0,0 @@ -Dropping 0 diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs similarity index 94% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs index 082a6f17838d5..e60ad970749db 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs @@ -1,4 +1,5 @@ -//! Check that destructors of main thread thread locals are executed. +//! Check that destructors of main thread thread locals are executed when main returns. +//@run-native struct Bar; diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stdout rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.stdout diff --git a/src/tools/miri/tests/pass/tls/win_tls_callback.rs b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs similarity index 57% rename from src/tools/miri/tests/pass/tls/win_tls_callback.rs rename to src/tools/miri/tests/pass/tls/win_thread_atexit.rs index a91556829234f..ddba46f962c5c 100644 --- a/src/tools/miri/tests/pass/tls/win_tls_callback.rs +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs @@ -1,16 +1,20 @@ //! Ensure that we call Windows TLS callbacks in the local crate. //@only-target: windows -// Calling eprintln in the callback seems to (re-)initialize some thread-local storage -// and then leak the memory allocated for that. Let's just ignore these leaks, -// that's not what this test is about. -//@compile-flags: -Zmiri-ignore-leaks + +use core::fmt::Write; + +#[path = "../../utils/mod.rs"] +mod utils; #[link_section = ".CRT$XLB"] #[used] // Miri only considers explicitly `#[used]` statics for `lookup_link_section` pub static CALLBACK: unsafe extern "system" fn(*const (), u32, *const ()) = tls_callback; unsafe extern "system" fn tls_callback(_h: *const (), _dw_reason: u32, _pv: *const ()) { - eprintln!("in tls_callback"); + // Do not call into std as it has already been shut down. That means we cannot run natively. + writeln!(utils::MiriStderr, "in tls_callback").unwrap(); } -fn main() {} +fn main() { + std::thread::spawn(|| {}); +} diff --git a/src/tools/miri/tests/pass/tls/win_tls_callback.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.stderr similarity index 50% rename from src/tools/miri/tests/pass/tls/win_tls_callback.stderr rename to src/tools/miri/tests/pass/tls/win_thread_atexit.stderr index 8479558954456..b415132c9c854 100644 --- a/src/tools/miri/tests/pass/tls/win_tls_callback.stderr +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.stderr @@ -1 +1,2 @@ in tls_callback +in tls_callback From 6807931feb6d0c335cd6a33ec63881b4a941cadc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 11:02:48 +0200 Subject: [PATCH 09/80] test suite: also check output of native runs --- .../miri/tests/pass/shims/env/current_exe.rs | 2 +- .../pass/shims/x86/intrinsics-x86-aes-vaes.rs | 2 +- .../pass/shims/x86/intrinsics-x86-avx512.rs | 2 +- .../pass/shims/x86/intrinsics-x86-gfni.rs | 2 +- .../shims/x86/intrinsics-x86-vpclmulqdq.rs | 2 +- src/tools/miri/tests/ui.rs | 49 ++++++++++++++++--- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 49e382cc29567..cab0e4c00ba1e 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,6 +1,6 @@ //@ignore-target: freebsd # we don't have the shims this needs here //@compile-flags: -Zmiri-disable-isolation -//@run-native +//@run-native: ignore-output use std::env; fn main() { diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs index 11fa75962a954..9462a7abca89b 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+aes,+vaes,+avx512f -//@run-native +//@run-native: ignore-output use core::mem::transmute; #[cfg(target_arch = "x86")] diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index e96845ff09163..89f882a44167e 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512bitalg,+avx512vpopcntdq,+avx512vnni,+avx512vbmi -//@run-native +//@run-native: ignore-output #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs index ef0fd62da5f3f..e259814ff582d 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs @@ -1,7 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+gfni,+avx512f -//@run-native +//@run-native: ignore-output // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs index 22bd697cfd0a1..596a8f5a45d54 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs @@ -3,7 +3,7 @@ //@only-target: x86_64 i686 //@[avx512]compile-flags: -C target-feature=+vpclmulqdq,+avx512f //@[avx]compile-flags: -C target-feature=+vpclmulqdq,+avx2 -//@run-native +//@run-native: ignore-output // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 33aa92b51af40..2933ac78708f6 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -138,10 +138,24 @@ fn miri_config( }; // Register custom comments. - config.custom_comments.insert("run-native", |parser, _args, span| { - // Just remember that this is present. - parser.set_custom_once("run-native", (), span); - }); + if matches!(mode, Mode::Pass { .. }) { + config.custom_comments.insert("run-native", |parser, args, span| { + let mut ignore_output = false; + for arg in args.split_whitespace() { + match arg { + "ignore-output" => ignore_output = true, + _ => parser.error(span.clone(), format!("invalid flag on `run-native`: {arg}")), + } + } + // Remember this data. We use the presence/absence of keys in the map to communicate + // with later passes, since there is apparently no way to get custom data out of + // that map again. + parser.set_custom_once("run-native", (), span.clone()); + if ignore_output { + parser.set_custom_once("run-native-ignore-output", (), span); + } + }); + } // Adjust comment defaults. config.comment_defaults.base().exit_status = match mode { @@ -225,9 +239,8 @@ fn miri_config( comments: &ui_test::Comments, revision: &str, ) -> bool { - let should_run = comments - .for_revision(revision) - .any(|r| r.custom.iter().any(|(k, _v)| *k == "run-native")); + let should_run = + comments.for_revision(revision).any(|r| r.custom.contains_key("run-native")); // We return `true` when the test should be ignored. let ignore = !should_run; ignore @@ -242,7 +255,27 @@ fn miri_config( // Delegate to the native run support. use ui_test::custom_flags::run::Run; Run::post_test_action( - &Run { exit_code: 0, output_conflict_handling: None }, + &Run { + exit_code: 0, + output_conflict_handling: Some(|path, output, errors, config| { + let ignore_output = config + .comments() + .any(|r| r.custom.contains_key("run-native-ignore-output")); + if ignore_output { + return; + } + + // We have to patch the filename. Something is adding a `.run`. + let extension = path.extension().unwrap(); + // Remove that extension from the path. + let path = path.with_extension(""); + // Now the extension is `.run`. Replace it with the final extension. + assert_eq!(path.extension().unwrap().to_str(), Some("run")); + let path = path.with_extension(extension); + + ui_test::error_on_output_conflict(&path, output, errors, config) + }), + }, config, output, build_manager, From 1bc78f2a30c6bd6d7a81bbb9bfd028f4111e28c8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 14:48:18 +0200 Subject: [PATCH 10/80] add support for __cxa_thread_atexit_impl --- src/tools/miri/src/shims/extern_static.rs | 8 ++- src/tools/miri/src/shims/foreign_items.rs | 3 +- src/tools/miri/src/shims/tls.rs | 34 ++++------ .../miri/src/shims/unix/foreign_items.rs | 12 ++-- .../src/shims/unix/linux/foreign_items.rs | 37 +++++++++- .../src/shims/unix/macos/foreign_items.rs | 5 +- .../miri/tests/pass/tls/macos_tlv_atexit.rs | 43 ------------ .../miri/tests/pass/tls/thread_atexit.rs | 68 +++++++++++++++++++ ...tlv_atexit.stdout => thread_atexit.stdout} | 0 .../miri/tests/pass/tls/tls_macro_drop.rs | 6 +- 10 files changed, 138 insertions(+), 78 deletions(-) delete mode 100644 src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs create mode 100644 src/tools/miri/tests/pass/tls/thread_atexit.rs rename src/tools/miri/tests/pass/tls/{macos_tlv_atexit.stdout => thread_atexit.stdout} (100%) diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index 45fe60599fafb..fb0f2e0b0d461 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -1,7 +1,7 @@ //! Provides the `extern static` that this platform expects. use rustc_span::Symbol; -use rustc_target::spec::Os; +use rustc_target::spec::{Env, Os}; use crate::*; @@ -61,6 +61,12 @@ impl<'tcx> MiriMachine<'tcx> { match &ecx.tcx.sess.target.os { Os::Linux => { Self::weak_fn_symbols(ecx, &["getrandom", "gettid", "statx", "strlen"])?; + if ecx.tcx.sess.target.env == Env::Gnu { + Self::weak_fn_symbols(ecx, &["__cxa_thread_atexit_impl"])?; + // The __dso_handle symbol just marks a unique global address. + let val = ImmTy::from_int(0, ecx.machine.layouts.u8); + Self::alloc_extern_static(ecx, "__dso_handle", val)?; + } } Os::Android => { Self::weak_fn_symbols( diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index f5aa85bbbd8e1..5fd5d5110591b 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -89,7 +89,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn is_dyn_sym(&self, name: &str) -> bool { let this = self.eval_context_ref(); match &this.tcx.sess.target.os { - os if this.target_os_is_unix() => shims::unix::foreign_items::is_dyn_sym(name, os), + _ if this.target_os_is_unix() => + shims::unix::foreign_items::is_dyn_sym(name, &this.tcx.sess.target), Os::Windows => shims::windows::foreign_items::is_dyn_sym(name), _ => false, } diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 2ad3082582358..29ef0fe1f555b 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -46,9 +46,8 @@ pub struct TlsData<'tcx> { /// pthreads-style thread-local storage. keys: BTreeMap>, - /// On macOS, each thread holds a list of destructor functions with their - /// respective data arguments. - macos_thread_dtors: BTreeMap, Scalar, Span)>>, + /// Linux and macOS allow registering callbacks that will be executed each time a thread exits. + thread_atexit: BTreeMap, Scalar, Span)>>, } impl<'tcx> Default for TlsData<'tcx> { @@ -56,7 +55,7 @@ impl<'tcx> Default for TlsData<'tcx> { TlsData { next_key: 1, // start with 1 as we must not use 0 on Windows keys: Default::default(), - macos_thread_dtors: Default::default(), + thread_atexit: Default::default(), } } } @@ -131,14 +130,14 @@ impl<'tcx> TlsData<'tcx> { /// Add a thread local storage destructor for the given thread. This function /// is used to implement the `_tlv_atexit` shim on MacOS. - pub fn add_macos_thread_dtor( + pub fn add_thread_atexit( &mut self, thread: ThreadId, dtor: ty::Instance<'tcx>, data: Scalar, span: Span, ) -> InterpResult<'tcx> { - self.macos_thread_dtors.entry(thread).or_default().push((dtor, data, span)); + self.thread_atexit.entry(thread).or_default().push((dtor, data, span)); interp_ok(()) } @@ -202,7 +201,7 @@ impl<'tcx> TlsData<'tcx> { data.remove(&thread_id); } - if let Some(dtors) = self.macos_thread_dtors.remove(&thread_id) { + if let Some(dtors) = self.thread_atexit.remove(&thread_id) { assert!(dtors.is_empty(), "the destructors should have already been run"); } } @@ -210,7 +209,7 @@ impl<'tcx> TlsData<'tcx> { impl VisitProvenance for TlsData<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let TlsData { keys, macos_thread_dtors, next_key: _ } = self; + let TlsData { keys, thread_atexit: macos_thread_dtors, next_key: _ } = self; for scalar in keys.values().flat_map(|v| v.data.values()) { scalar.visit_provenance(visit); @@ -230,7 +229,7 @@ enum TlsDtorsStatePriv<'tcx> { Init, PthreadDtors(RunningPthreadDtorState), AfterPthreadDtors, - MacOsDtors, + ThreadAtexit, /// For Windows, we support two different ways dtors can be registered. /// 1. Functions that are registered via the `FlsAlloc` function, which are invoked one by one. /// 2. Functions from the magic `.CRT$XLB` linker section. @@ -270,11 +269,8 @@ impl<'tcx> TlsDtorsState<'tcx> { AfterPthreadDtors => { // Next, we run the platform-specific destructors match this.tcx.sess.target.os { - Os::MacOs => { - // macOS has a _tlv_atexit function that allows - // registering destructors without associated keys. - // These are run first. - break 'new_state MacOsDtors; + Os::MacOs | Os::Linux => { + break 'new_state ThreadAtexit; } Os::Windows => { // Determine which destructors to run. @@ -298,8 +294,8 @@ impl<'tcx> TlsDtorsState<'tcx> { } } } - MacOsDtors => { - match this.schedule_macos_tls_dtor()? { + ThreadAtexit => { + match this.schedule_thread_atexit()? { Poll::Pending => return interp_ok(Poll::Pending), // After all macOS destructors are run, the system switches // to destroying the pthread destructors. @@ -387,15 +383,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Schedule the macOS thread local storage destructors to be executed. - fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> { + fn schedule_thread_atexit(&mut self) -> InterpResult<'tcx, Poll<()>> { let this = self.eval_context_mut(); let thread_id = this.active_thread(); // macOS keeps track of TLS destructors in a stack. If a destructor // registers another destructor, it will be run next. // See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277 - let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop); + let dtor = this.machine.tls.thread_atexit.get_mut(&thread_id).and_then(Vec::pop); if let Some((instance, data, span)) = dtor { - trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id); + trace!("Running thread_atexit hook {:?} on {:?} at {:?}", instance, data, thread_id); this.call_thread_root_function( instance, diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index e8548572904b5..2c350705fa053 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -6,7 +6,7 @@ use rustc_abi::{CanonAbi, Size}; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; -use rustc_target::spec::{Env, Os}; +use rustc_target::spec::{Env, Os, Target}; use self::shims::unix::android::foreign_items as android; use self::shims::unix::freebsd::foreign_items as freebsd; @@ -19,7 +19,7 @@ use crate::shims::cpu_affinity::EvalContextExt as _; use crate::shims::unix::*; use crate::{shim_sig, *}; -pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { +pub fn is_dyn_sym(name: &str, target: &Target) -> bool { match name { // Used for (std and Miri) tests. "strlen" => true, @@ -34,13 +34,13 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { // `preadv`/`pwritev` are set up as weak symbols in `init_extern_statics` (on Android, // where std uses them via `weak!` since bionic only gained them in API level 24), so // we allow them here too. They do not exist on Solaris. - "preadv" | "pwritev" if !matches!(*target_os, Os::Solaris) => true, + "preadv" | "pwritev" if !matches!(target.os, Os::Solaris) => true, // Give specific OSes a chance to allow their symbols. _ => - match *target_os { + match target.os { Os::Android => android::is_dyn_sym(name), Os::FreeBsd => freebsd::is_dyn_sym(name), - Os::Linux => linux::is_dyn_sym(name), + Os::Linux => linux::is_dyn_sym(name, target), Os::MacOs => macos::is_dyn_sym(name), Os::Solaris | Os::Illumos => solarish::is_dyn_sym(name), Os::NetBsd => netbsd::is_dyn_sym(name), @@ -865,7 +865,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Ok(name) = str::from_utf8(name) else { throw_unsup_format!("dlsym: non UTF-8 symbol name not supported") }; - if is_dyn_sym(name, &this.tcx.sess.target.os) { + if this.is_dyn_sym(name) { let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); this.write_pointer(ptr, dest)?; } else if let Some(&ptr) = this.machine.extern_statics.get(&Symbol::intern(name)) { diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 62ec9d2bc7ed0..d77d8f2a873d1 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -2,6 +2,7 @@ use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; +use rustc_target::spec::Target; use self::shims::unix::linux::mem::EvalContextExt as _; use self::shims::unix::linux_like::eventfd::EvalContextExt as _; @@ -18,8 +19,10 @@ use crate::*; // long including a null terminator. const TASK_COMM_LEN: u64 = 16; -pub fn is_dyn_sym(name: &str) -> bool { +pub fn is_dyn_sym(name: &str, target: &Target) -> bool { matches!(name, "gettid" | "statx") + || (target.env == rustc_target::spec::Env::Gnu + && matches!(name, "__cxa_thread_atexit_impl")) } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -238,6 +241,38 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "prctl" => prctl(this, link_name, abi, args, dest)?, + "__cxa_thread_atexit_impl" + if this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => + { + let [dtor, arg, dso] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; + let dtor = this.read_pointer(dtor)?; + let arg = this.read_scalar(arg)?; + let dso = this.read_pointer(dso)?; + + let dso_handle = Symbol::intern("__dso_handle"); + // FIXME: we skip this check in std because std passes the wrong address, + // see + if dso.addr() != Pointer::from(this.machine.extern_statics[&dso_handle]).addr() + && !this.frame_in_std() + { + throw_unsup_format!( + "__cxa_thread_atexit_impl: a DSO value other than the address of `__dso_handle` is not supported" + ); + } + + let dtor = this.get_ptr_fn(dtor)?.as_instance()?; + this.machine.tls.add_thread_atexit( + this.active_thread(), + dtor, + arg, + this.machine.current_user_relevant_span(), + )?; + + this.write_null(dest)?; + } // Dynamically invoked syscalls "syscall" => { diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 2b5428e36f298..369c914ecbb4b 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -178,9 +178,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dtor = this.read_pointer(dtor)?; let dtor = this.get_ptr_fn(dtor)?.as_instance()?; let data = this.read_scalar(data)?; - let active_thread = this.active_thread(); - this.machine.tls.add_macos_thread_dtor( - active_thread, + this.machine.tls.add_thread_atexit( + this.active_thread(), dtor, data, this.machine.current_user_relevant_span(), diff --git a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs b/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs deleted file mode 100644 index 4fc1e2c7b678b..0000000000000 --- a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.rs +++ /dev/null @@ -1,43 +0,0 @@ -//@only-target: darwin - -use std::thread; - -extern "C" { - fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8); -} - -fn register(f: F) -where - F: FnOnce() + 'static, -{ - // This will receive the pointer passed into `_tlv_atexit`, which is the - // original `f` but boxed up. - unsafe extern "C" fn run(ptr: *mut u8) - where - F: FnOnce() + 'static, - { - let f = unsafe { Box::from_raw(ptr as *mut F) }; - f() - } - - unsafe { - _tlv_atexit(run::, Box::into_raw(Box::new(f)) as *mut u8); - } -} - -fn main() { - thread::spawn(|| { - register(|| println!("dtor 2")); - register(|| println!("dtor 1")); - println!("exiting thread"); - }) - .join() - .unwrap(); - - println!("exiting main"); - register(|| println!("dtor 5")); - register(|| { - println!("registering dtor in dtor 3"); - register(|| println!("dtor 4")); - }); -} diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.rs b/src/tools/miri/tests/pass/tls/thread_atexit.rs new file mode 100644 index 0000000000000..5fdea36cfafcb --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.rs @@ -0,0 +1,68 @@ +//! Test the Linux and macOS APIs for thread exit callbacks. +//@only-target: linux darwin +//@run-native + +use std::{ffi, thread}; + +fn register(f: F) +where + F: FnOnce() + 'static, +{ + // This will receive the pointer passed into `_tlv_atexit`, which is the + // original `f` but boxed up. + unsafe extern "C" fn run(ptr: *mut ffi::c_void) + where + F: FnOnce() + 'static, + { + let f = unsafe { Box::from_raw(ptr as *mut F) }; + f() + } + + cfg_select! { + target_os = "linux" => { + extern "C" { + #[allow(improper_ctypes)] + static __dso_handle: (); + + fn __cxa_thread_atexit_impl( + dtor: unsafe extern "C" fn(*mut ffi::c_void), + arg: *mut ffi::c_void, + dso: *const ffi::c_void, + ) -> ffi::c_int; + } + + let ret = unsafe { + __cxa_thread_atexit_impl( + run::, + Box::into_raw(Box::new(f)) as *mut ffi::c_void, + &raw const __dso_handle as *const ffi::c_void, + ) + }; + assert_eq!(ret, 0); + } + target_vendor = "apple" => { + extern "C" { + fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut ffi::c_void), arg: *mut ffi::c_void); + } + unsafe { _tlv_atexit(run::, Box::into_raw(Box::new(f)) as *mut ffi::c_void) }; + } + } +} + +fn main() { + thread::spawn(|| { + register(|| println!("dtor 2")); + register(|| println!("dtor 1")); + println!("exiting thread"); + }) + .join() + .unwrap(); + + register(|| println!("dtor 5")); + register(|| { + println!("registering dtor in dtor 3"); + register(|| println!("dtor 4")); + }); + + println!("exiting main"); +} diff --git a/src/tools/miri/tests/pass/tls/macos_tlv_atexit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/macos_tlv_atexit.stdout rename to src/tools/miri/tests/pass/tls/thread_atexit.stdout diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs index 0c0b9b7efee50..057935f5ec168 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs @@ -1,6 +1,7 @@ //@revisions: stack tree tree_implicit_writes //@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes //@[tree]compile-flags: -Zmiri-tree-borrows +//@[stack]run-native use std::cell::RefCell; use std::thread; @@ -27,10 +28,7 @@ fn check_destructors() { static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } }; } - // We use the same value for both of them, since destructor order differs between Miri on Linux - // (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a - // thread-local linked list of dtors to call), real Linux rustc (which uses - // `__cxa_thread_atexit_impl`), and Miri on Windows. + // We use the same value for both of them, since destructor order differs between platforms. thread::spawn(|| { A.with(|f| { assert_eq!(*f.value.borrow(), 0); From 4f3cf9712cf98c35509fad730eb87477a9de83cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 16:54:01 +0200 Subject: [PATCH 11/80] Document updating the `gcc` submodule --- compiler/rustc_codegen_gcc/doc/subtree.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index 00b5d377816fd..dbdc09499e30d 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -7,3 +7,7 @@ The procedure for performing subtree synchronizations is documented [here][rdg-josh]. [rdg-josh]: https://rustc-dev-guide.rust-lang.org/external-repos.html#synchronizing-a-josh-subtree + +## Pushing changes to `rust-lang/rust` + +If the used GCC commit (recorded in `libgccjit.version`) changed in `rustc_codegen_gcc`, then you will have to update the `gcc` submodule in the `rust-lang/rust` repository in the push sync PR. The CI in `rust-lang/rust` should ensure that these two commit references match. You can simply update the `gcc` submodule commit and push it to your push sync PR once it has been created. From 3d8756a30c97d1fb83141501fc66a3409c464c23 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 16:36:46 +0200 Subject: [PATCH 12/80] run the thread exit hooks also when exit() is called --- src/tools/miri/priroda/src/debugger.rs | 4 +- src/tools/miri/priroda/src/frontend/dap.rs | 5 +- src/tools/miri/src/concurrency/genmc/dummy.rs | 7 +- src/tools/miri/src/concurrency/genmc/mod.rs | 7 +- .../miri/src/concurrency/genmc/scheduling.rs | 2 +- src/tools/miri/src/concurrency/mod.rs | 2 +- src/tools/miri/src/concurrency/scheduler.rs | 6 +- src/tools/miri/src/concurrency/thread.rs | 57 ++++--- src/tools/miri/src/diagnostics.rs | 7 +- src/tools/miri/src/eval.rs | 158 +++++++++++------- src/tools/miri/src/lib.rs | 2 +- src/tools/miri/src/shims/foreign_items.rs | 23 ++- .../miri/src/shims/windows/foreign_items.rs | 12 +- ....stdout => thread_atexit.in_thread.stdout} | 0 .../miri/tests/pass/tls/thread_atexit.rs | 18 +- .../pass/tls/thread_atexit.with_exit.stdout | 7 + .../tls/thread_atexit.without_exit.stdout | 7 + ...eturn.rs => tls_macro_drop_main_thread.rs} | 4 + ...s_macro_drop_main_thread.with_exit.stdout} | 0 ...macro_drop_main_thread.without_exit.stdout | 2 + .../miri/tests/pass/tls/win_thread_atexit.rs | 4 + ...err => win_thread_atexit.with_exit.stderr} | 0 .../tls/win_thread_atexit.without_exit.stderr | 2 + 23 files changed, 212 insertions(+), 124 deletions(-) rename src/tools/miri/tests/pass/tls/{thread_atexit.stdout => thread_atexit.in_thread.stdout} (100%) create mode 100644 src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout create mode 100644 src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout rename src/tools/miri/tests/pass/tls/{tls_macro_drop_main_return.rs => tls_macro_drop_main_thread.rs} (86%) rename src/tools/miri/tests/pass/tls/{tls_macro_drop_main_return.stdout => tls_macro_drop_main_thread.with_exit.stdout} (100%) create mode 100644 src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout rename src/tools/miri/tests/pass/tls/{win_thread_atexit.stderr => win_thread_atexit.with_exit.stderr} (100%) create mode 100644 src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 73fa355e164eb..ee521a9337957 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -300,11 +300,11 @@ impl<'tcx> PrirodaContext<'tcx> { // FIXME: Preserve `TerminationInfo::Exit::leak_check` and run Miri's // leak/thread-leak diagnostics once Priroda grows a proper post-exit // finalization path. For now, program exit only records the debuggee exit code. - let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + let Some(TerminationInfo::Exit { exit_code, .. }) = info.downcast_ref::() else { return None; }; - Some(*code) + Some(*exit_code) } fn stop_at_exception(&mut self, err: InterpErrorInfo<'tcx>) -> StepResult { diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index d550cb85e83ed..eb1d3a2a7486b 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -702,9 +702,10 @@ impl DapSession { fn interp_error_outcome<'tcx>(err: InterpErrorInfo<'tcx>) -> ExecutionOutcome { let kind = err.into_kind(); if let InterpErrorKind::MachineStop(info) = &kind - && let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + && let Some(TerminationInfo::Exit { exit_code, .. }) = + info.downcast_ref::() { - return ExecutionOutcome::Terminated { code: *code }; + return ExecutionOutcome::Terminated { code: *exit_code }; } ExecutionOutcome::Failed(kind.to_string()) diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index 0ab369f25770a..0314da441a37b 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -3,17 +3,12 @@ use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; +use crate::eval::ExitType; use crate::{ AtomicFenceOrd, AtomicReadOrd, AtomicRmwOp, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, OpTy, Scalar, ThreadId, ThreadManager, VisitProvenance, VisitWith, }; -#[derive(Clone, Copy, Debug)] -pub enum ExitType { - MainThreadFinish, - ExitCalled, -} - #[derive(Debug)] pub struct GenmcCtx {} diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index a4eab95fbef5d..7e1dc75a35d74 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -20,6 +20,7 @@ use self::helper::{ use self::run::GenmcMode; use self::thread_id_map::ThreadIdMap; use crate::diagnostics::SpanDedupDiagnostic; +use crate::eval::ExitType; use crate::*; mod config; @@ -46,12 +47,6 @@ pub enum ExecutionEndResult { Stop, } -#[derive(Clone, Copy, Debug)] -pub enum ExitType { - MainThreadFinish, - ExitCalled, -} - /// The exit status of a program. /// GenMC must store this if a thread exits while any others can still run. /// The other threads must also be explored before the program is terminated. diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index e704346ad2ea4..9e802654d4fbb 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -128,7 +128,7 @@ impl GenmcCtx { "If the execution is finished, we should have a return value from the program.", ); throw_machine_stop!(TerminationInfo::Exit { - code: exit_status.exit_code, + exit_code: exit_status.exit_code, leak_check: matches!(exit_status.exit_type, super::ExitType::MainThreadFinish), }); } diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs index d6d006d0dabb6..5c059e36b9731 100644 --- a/src/tools/miri/src/concurrency/mod.rs +++ b/src/tools/miri/src/concurrency/mod.rs @@ -22,5 +22,5 @@ pub mod weak_memory; mod genmc; pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler}; -pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; +pub use self::genmc::{GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; pub use self::vector_clock::VClock; diff --git a/src/tools/miri/src/concurrency/scheduler.rs b/src/tools/miri/src/concurrency/scheduler.rs index cf81db0910723..6364710fb8905 100644 --- a/src/tools/miri/src/concurrency/scheduler.rs +++ b/src/tools/miri/src/concurrency/scheduler.rs @@ -31,7 +31,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> { let this = self.eval_context_mut(); let active_thread = this.active_thread_mut(); - active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applied + active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applies let mut callback = active_thread .on_stack_empty .take() @@ -175,11 +175,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); if !this.step()? { - // See if this thread can do something else. + // The stack is empty. See if this thread can do something else. match this.run_on_stack_empty()? { Poll::Pending => {} // keep going Poll::Ready(()) => { - this.terminate_active_thread(TlsAllocAction::Deallocate)?; + this.terminate_active_thread(/* for_exit */ false)?; } } } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index b2878ae5201d9..3966ea360b516 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -19,16 +19,6 @@ use crate::concurrency::GlobalDataRaceHandler; use crate::shims::tls; use crate::*; -/// What to do with TLS allocations from terminated threads -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TlsAllocAction { - /// Deallocate backing memory of thread-local statics as usual - Deallocate, - /// Skip deallocating backing memory of thread-local statics and consider all memory reachable - /// from them as "allowed to leak" (like global `static`s). - Leak, -} - /// The argument type for the "unblock" callback, indicating why the thread got unblocked. #[derive(Clone, Copy, Debug, PartialEq)] pub enum UnblockKind { @@ -749,45 +739,60 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(new_thread_id) } + /// Replace the active thread with an empty one, and a new on-empty handler. + /// The memory of the old stack is leaked. + /// Used to implement `exit`. + fn replace_active_thread_stack(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) { + let this = self.eval_context_mut(); + let thread = this.active_thread_mut(); + let span = thread.current_user_relevant_span(); + + thread.stack.clear(); + thread.origin_span = span; + thread.top_user_relevant_frame = None; + thread.on_stack_empty = Some(on_stack_empty); + } + /// Handles thread termination of the active thread: wakes up threads joining on this one, - /// and deals with the thread's thread-local statics according to `tls_alloc_action`. + /// and frees the thread's thread-local statics. `for_exit` indicates whether we are + /// about to exit the program; that changes some details. /// /// This is called by the eval loop when a thread's on_stack_empty returns `Ready`. - fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> { + fn terminate_active_thread(&mut self, for_exit: bool) -> InterpResult<'tcx> { let this = self.eval_context_mut(); // Mark thread as terminated. let thread = this.active_thread_mut(); - assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated"); thread.state = ThreadState::Terminated; + assert!( + for_exit || thread.stack.is_empty(), + "only threads with an empty stack can be terminated" + ); // Deallocate TLS. let gone_thread = this.active_thread(); { let mut free_tls_statics = Vec::new(); - this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| { + this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut ptr| { if thread != gone_thread { // A different thread, keep this static around. return true; } // Delete this static from the map and from memory. // We cannot free directly here as we cannot use `?` in this context. - free_tls_statics.push(alloc_id); + free_tls_statics.push(ptr); false }); // Now free the TLS statics. for ptr in free_tls_statics { - match tls_alloc_action { - TlsAllocAction::Deallocate => - this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?, - TlsAllocAction::Leak => - if let Some(alloc) = ptr.provenance.get_alloc_id() { - trace!( - "Thread-local static leaked and stored as static root: {:?}", - alloc - ); - this.machine.static_roots.push(alloc); - }, + if for_exit { + // Leak the thread-local statics rather than freeing them. + if let Some(alloc) = ptr.provenance.get_alloc_id() { + trace!("Thread-local static leaked and stored as static root: {:?}", alloc); + this.machine.static_roots.push(alloc); + } + } else { + this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?; } } } diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 7c5b38e9a4891..aadb2ade8d9ff 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -14,7 +14,7 @@ use crate::*; /// Details of premature program termination. pub enum TerminationInfo { Exit { - code: i32, + exit_code: i32, leak_check: bool, }, Abort(String), @@ -71,7 +71,8 @@ impl fmt::Display for TerminationInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use TerminationInfo::*; match self { - Exit { code, .. } => write!(f, "the evaluated program completed with exit code {code}"), + Exit { exit_code, .. } => + write!(f, "the evaluated program completed with exit code {exit_code}"), Abort(msg) => write!(f, "{msg}"), Interrupted => write!(f, "interpretation was interrupted"), UnsupportedInIsolation(msg) => write!(f, "{msg}"), @@ -251,7 +252,7 @@ pub fn report_result<'tcx>( let info = info.downcast_ref::().expect("invalid MachineStop payload"); use TerminationInfo::*; let title = match info { - &Exit { code, leak_check } => return Some((code, leak_check)), + &Exit { exit_code, leak_check } => return Some((exit_code, leak_check)), Abort(_) => Some("abnormal termination"), Interrupted => None, UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) => diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 44b0290efc382..6eddb382a2984 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -22,7 +22,6 @@ use rustc_session::config::EntryFnType; use rustc_target::spec::Os; use crate::concurrency::GenmcCtx; -use crate::concurrency::thread::TlsAllocAction; use crate::diagnostics::report_leaks; use crate::helpers::is_no_core; use crate::shims::{global_ctor, tls}; @@ -84,10 +83,10 @@ pub fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { } } -/// When the main thread would exit, we will yield to any other thread that is ready to execute. +/// When the program exits, we will yield to any other thread that is ready to execute. /// But we must only do that a finite number of times, or a background thread running `loop {}` /// will hang the program. -const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256; +const THREAD_YIELDS_AT_EXIT: u32 = 256; /// Configuration needed to spawn a Miri instance. #[derive(Clone)] @@ -219,46 +218,41 @@ impl Default for MiriConfig { } } -/// The state of the main thread. Implementation detail of `on_main_stack_empty`. +#[derive(Clone, Copy, Debug)] +pub enum ExitType { + MainThreadFinish, + ExitCalled, +} + +/// A state machine that's run when the program is exiting. #[derive(Debug)] -enum MainThreadState<'tcx> { - GlobalCtors { - ctor_state: global_ctor::GlobalCtorState<'tcx>, - /// The main function to call. - entry_id: DefId, - entry_type: MiriEntryFnType, - /// Arguments passed to `main`. - argc: ImmTy<'tcx>, - argv: ImmTy<'tcx>, - }, - Running, +pub struct ProgramExitHandler<'tcx> { + state: ProgramExitState<'tcx>, + exit_code: i32, + exit_type: ExitType, +} +#[derive(Debug)] +enum ProgramExitState<'tcx> { TlsDtors(tls::TlsDtorsState<'tcx>), - Yield { - remaining: u32, - }, + Yield { remaining: u32 }, Done, + Unreachable, } -impl<'tcx> MainThreadState<'tcx> { - fn on_main_stack_empty( +impl<'tcx> ProgramExitHandler<'tcx> { + pub fn new(exit_code: i32, exit_type: ExitType) -> Self { + Self { + state: ProgramExitState::TlsDtors(tls::TlsDtorsState::new_for_exit()), + exit_code, + exit_type, + } + } + pub fn on_stack_empty( &mut self, this: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, Poll<()>> { - use MainThreadState::*; - match self { - GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => { - match ctor_state.on_stack_empty(this)? { - Poll::Pending => {} // just keep going - Poll::Ready(()) => { - call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?; - *self = Running; - } - } - } - Running => { - // Main code is done, the program is exiting. - *self = TlsDtors(tls::TlsDtorsState::new_for_exit()); - } + use ProgramExitState::*; + match &mut self.state { TlsDtors(state) => match state.on_stack_empty(this)? { Poll::Pending => {} // just keep going @@ -266,61 +260,109 @@ impl<'tcx> MainThreadState<'tcx> { if this.machine.data_race.as_genmc_ref().is_some() { // In GenMC mode, we don't yield at the end of the main thread. // Instead, the `GenmcCtx` will ensure that unfinished threads get a chance to run at this point. - *self = Done; + self.state = Done; } else { // Give background threads a chance to finish by yielding the main thread a // couple of times -- but only if we would also preempt threads randomly. if this.machine.preemption_rate > 0.0 { // There is a non-zero chance they will yield back to us often enough to // make Miri terminate eventually. - *self = Yield { remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN }; + self.state = Yield { remaining: THREAD_YIELDS_AT_EXIT }; } else { // The other threads did not get preempted, so no need to yield back to // them. - *self = Done; + self.state = Done; } } } }, Yield { remaining } => match remaining.checked_sub(1) { - None => *self = Done, + None => self.state = Done, Some(new_remaining) => { *remaining = new_remaining; this.yield_active_thread(); } }, Done => { - // Figure out exit code. - let ret_place = this.machine.main_fn_ret_place.clone().unwrap(); - let exit_code = this.read_target_isize(&ret_place)?; - // Rust uses `isize` but the underlying type of an exit code is `i32`. - // Do a saturating cast. - let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 { - i32::MAX - } else { - i32::MIN - }); - // Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS - // to be like a global `static`, so that all memory reached by it is considered to "not leak". - this.terminate_active_thread(TlsAllocAction::Leak)?; + // Terminate this thread in the scheduler. + this.terminate_active_thread(/* for_exit */ true)?; + + // We cannot come back. + self.state = Unreachable; - // In GenMC mode, we do not immediately stop execution on main thread exit. + // In GenMC mode, we do not immediately stop execution on exit. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { // If there's no error, execution will continue (on another thread). genmc_ctx.handle_exit( - ThreadId::MAIN_THREAD, - exit_code, - crate::concurrency::ExitType::MainThreadFinish, + this.machine.threads.active_thread(), + self.exit_code, + self.exit_type, )?; } else { // Stop interpreter loop. throw_machine_stop!(TerminationInfo::Exit { - code: exit_code, - leak_check: true + exit_code: self.exit_code, + leak_check: matches!(self.exit_type, ExitType::MainThreadFinish), }); } } + Unreachable => unreachable!(), + } + interp_ok(Poll::Pending) + } +} + +/// The state of the main thread. Implementation detail of `on_main_stack_empty`. +#[derive(Debug)] +enum MainThreadState<'tcx> { + GlobalCtors { + ctor_state: global_ctor::GlobalCtorState<'tcx>, + /// The main function to call. + entry_id: DefId, + entry_type: MiriEntryFnType, + /// Arguments passed to `main`. + argc: ImmTy<'tcx>, + argv: ImmTy<'tcx>, + }, + Running, + Exiting(ProgramExitHandler<'tcx>), +} + +impl<'tcx> MainThreadState<'tcx> { + fn on_main_stack_empty( + &mut self, + this: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Poll<()>> { + use MainThreadState::*; + match self { + GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => { + match ctor_state.on_stack_empty(this)? { + Poll::Pending => {} // just keep going + Poll::Ready(()) => { + call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?; + *self = Running; + } + } + } + Running => { + // Main code is done, the program is exiting. + + // Figure out exit code. + let ret_place = this.machine.main_fn_ret_place.clone().unwrap(); + let exit_code = this.read_target_isize(&ret_place)?; + // Rust uses `isize` but the underlying type of an exit code is `i32`. + let Ok(exit_code) = i32::try_from(exit_code) else { + throw_ub_format!( + "lang_start item return an exit code that does not fit into `i32`" + ); + }; + *self = Exiting(ProgramExitHandler::new(exit_code, ExitType::MainThreadFinish)); + } + Exiting(state) => { + // This is a sink state, we stay here forever. + return state.on_stack_empty(this); + } } interp_ok(Poll::Pending) } diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 7543617243881..507f93e144277 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -148,7 +148,7 @@ pub use crate::concurrency::sync::{ }; pub use crate::concurrency::thread::{ BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId, - ThreadManager, TlsAllocAction, UnblockKind, + ThreadManager, UnblockKind, }; pub use crate::concurrency::{GenmcConfig, GenmcCtx, run_genmc_mode}; pub use crate::data_structures::dedup_range_map::DedupRangeMap; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 5fd5d5110591b..95e8e5d04a752 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -19,6 +19,7 @@ use rustc_target::spec::Os; use super::alloc::EvalContextExt as _; use super::backtrace::EvalContextExt as _; use crate::concurrency::GenmcEvalContextExt as _; +use crate::eval::{ExitType, ProgramExitHandler}; use crate::helpers::EvalContextExt as _; use crate::*; @@ -609,19 +610,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Aborting the process. "exit" => { // FIXME: This does not have a direct test (#3179). - let [code] = &this + let [exit_code] = &this .check_shim_sig(shim_sig!(extern "C" fn(i32) -> ()), (link_name, abi, args))?; - let code = this.read_scalar(code)?.to_i32()?; - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - // If there is no error, execution should continue (on a different thread). - genmc_ctx.handle_exit( - this.machine.threads.active_thread(), - code, - crate::concurrency::ExitType::ExitCalled, - )?; - return interp_ok(EmulateItemResult::AlreadyJumped); - } - throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false }); + let exit_code = this.read_scalar(exit_code)?.to_i32()?; + // Initialize the at-exit state machine. + this.replace_active_thread_stack({ + let mut state_machine = + ProgramExitHandler::new(exit_code, ExitType::ExitCalled); + Box::new(move |m| state_machine.on_stack_empty(m)) + }); + // We "jumped" to the exit handling. + return interp_ok(EmulateItemResult::AlreadyJumped); } "abort" => { // FIXME: This does not have a direct test (#3179). diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index df7a6a0cbd6d5..2d3b15bcde74d 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -9,6 +9,7 @@ use rustc_target::callconv::FnAbi; use rustc_target::spec::Env; use self::shims::windows::handle::{Handle, PseudoHandle}; +use crate::eval::{ExitType, ProgramExitHandler}; use crate::shims::os_str::bytes_to_os_str; use crate::shims::windows::*; use crate::*; @@ -966,8 +967,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { (link_name, abi, args), )?; // Windows technically uses u32, but we unify everything to a Unix-style i32. - let code = this.read_scalar(code)?.to_i32()?; - throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false }); + let exit_code = this.read_scalar(code)?.to_i32()?; + // Initialize the at-exit state machine. + this.replace_active_thread_stack({ + let mut state_machine = + ProgramExitHandler::new(exit_code, ExitType::ExitCalled); + Box::new(move |m| state_machine.on_stack_empty(m)) + }); + // We "jumped" to the exit handling. + return interp_ok(EmulateItemResult::AlreadyJumped); } "SystemFunction036" => { // used by getrandom 0.1 diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.in_thread.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/thread_atexit.stdout rename to src/tools/miri/tests/pass/tls/thread_atexit.in_thread.stdout diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.rs b/src/tools/miri/tests/pass/tls/thread_atexit.rs index 5fdea36cfafcb..0bc304107059a 100644 --- a/src/tools/miri/tests/pass/tls/thread_atexit.rs +++ b/src/tools/miri/tests/pass/tls/thread_atexit.rs @@ -1,6 +1,7 @@ //! Test the Linux and macOS APIs for thread exit callbacks. //@only-target: linux darwin //@run-native +//@revisions: in_thread with_exit without_exit use std::{ffi, thread}; @@ -49,7 +50,7 @@ where } } -fn main() { +fn the_test() { thread::spawn(|| { register(|| println!("dtor 2")); register(|| println!("dtor 1")); @@ -65,4 +66,19 @@ fn main() { }); println!("exiting main"); + if cfg!(with_exit) { + std::process::exit(0); + } +} + +fn main() { + if cfg!(in_thread) { + return std::thread::spawn(the_test).join().unwrap(); + } + + the_test(); + + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout new file mode 100644 index 0000000000000..89d6ca2593547 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.with_exit.stdout @@ -0,0 +1,7 @@ +exiting thread +dtor 1 +dtor 2 +exiting main +registering dtor in dtor 3 +dtor 4 +dtor 5 diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout b/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout new file mode 100644 index 0000000000000..89d6ca2593547 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/thread_atexit.without_exit.stdout @@ -0,0 +1,7 @@ +exiting thread +dtor 1 +dtor 2 +exiting main +registering dtor in dtor 3 +dtor 4 +dtor 5 diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs similarity index 86% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs index e60ad970749db..68ec9973e5675 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs @@ -1,5 +1,6 @@ //! Check that destructors of main thread thread locals are executed when main returns. //@run-native +//@revisions: with_exit without_exit struct Bar; @@ -25,4 +26,7 @@ thread_local!(static FOO: Foo = Foo); fn main() { FOO.with(|_| {}); + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.with_exit.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/tls_macro_drop_main_return.stdout rename to src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.with_exit.stdout diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout new file mode 100644 index 0000000000000..6160f2726492d --- /dev/null +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.without_exit.stdout @@ -0,0 +1,2 @@ +Foo dtor +Bar dtor diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs index ddba46f962c5c..0cedb225d0c85 100644 --- a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs @@ -1,5 +1,6 @@ //! Ensure that we call Windows TLS callbacks in the local crate. //@only-target: windows +//@revisions: with_exit without_exit use core::fmt::Write; @@ -17,4 +18,7 @@ unsafe extern "system" fn tls_callback(_h: *const (), _dw_reason: u32, _pv: *con fn main() { std::thread::spawn(|| {}); + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stderr similarity index 100% rename from src/tools/miri/tests/pass/tls/win_thread_atexit.stderr rename to src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stderr diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr new file mode 100644 index 0000000000000..b415132c9c854 --- /dev/null +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr @@ -0,0 +1,2 @@ +in tls_callback +in tls_callback From 9f0b0f6ec5554345606e4e9e8d9ffc61da812077 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 17:09:43 +0200 Subject: [PATCH 13/80] allow leaks of pthread keys in the thread that calls exit --- src/tools/miri/src/shims/tls.rs | 68 ++++++++++++------- .../miri/src/shims/unix/foreign_items.rs | 8 +-- .../miri/src/shims/windows/foreign_items.rs | 16 ++--- .../miri/tests/pass/tls/thread_atexit.rs | 1 + .../pass/tls/tls_macro_drop_main_thread.rs | 2 + 5 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 29ef0fe1f555b..9dac66cc9d97a 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -4,7 +4,7 @@ use std::collections::btree_map::Entry as BTreeEntry; use std::collections::{BTreeMap, VecDeque}; use std::task::Poll; -use rustc_abi::{ExternAbi, HasDataLayout, Size}; +use rustc_abi::{ExternAbi, Size}; use rustc_middle::ty; use rustc_span::Span; use rustc_target::spec::Os; @@ -15,9 +15,9 @@ pub type TlsKey = u128; #[derive(Clone, Debug)] pub struct TlsEntry<'tcx> { - /// The data for this key. None is used to represent NULL. + /// The data for this key. Absence of data for a thread ID is used to represent NULL. /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.) - pub data: BTreeMap, + pub data: BTreeMap, pub dtor: Option<(ty::Instance<'tcx>, Span)>, } @@ -90,17 +90,12 @@ impl<'tcx> TlsData<'tcx> { } } - pub fn load_tls( - &self, - key: TlsKey, - thread_id: ThreadId, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, Scalar> { + pub fn load_tls(&self, key: TlsKey, thread_id: ThreadId) -> InterpResult<'tcx, Pointer> { match self.keys.get(&key) { Some(TlsEntry { data, .. }) => { let value = data.get(&thread_id).copied(); trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value); - interp_ok(value.unwrap_or_else(|| Scalar::null_ptr(cx))) + interp_ok(value.unwrap_or_else(Pointer::null)) } None => throw_ub_format!("loading from a non-existing TLS key: {}", key), } @@ -110,12 +105,11 @@ impl<'tcx> TlsData<'tcx> { &mut self, key: TlsKey, thread_id: ThreadId, - new_data: Scalar, - cx: &impl HasDataLayout, + new_data: Pointer, ) -> InterpResult<'tcx> { match self.keys.get_mut(&key) { Some(TlsEntry { data, .. }) => { - if new_data.to_target_usize(cx)? != 0 { + if new_data.addr() != Size::ZERO { trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data); data.insert(thread_id, new_data); } else { @@ -164,7 +158,7 @@ impl<'tcx> TlsData<'tcx> { &mut self, key: Option, thread_id: ThreadId, - ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey, Span)> { + ) -> Option<(ty::Instance<'tcx>, Pointer, TlsKey, Span)> { use std::ops::Bound::*; let thread_local = &mut self.keys; @@ -184,8 +178,8 @@ impl<'tcx> TlsData<'tcx> { BTreeEntry::Occupied(entry) => { if let Some((dtor, span)) = dtor { // Set TLS data to NULL, and call dtor with old value. - let data_scalar = entry.remove(); - return Some((*dtor, data_scalar, key, *span)); + let data = entry.remove(); + return Some((*dtor, data, key, *span)); } } BTreeEntry::Vacant(_) => {} @@ -205,6 +199,17 @@ impl<'tcx> TlsData<'tcx> { assert!(dtors.is_empty(), "the destructors should have already been run"); } } + + pub fn leak_all_thread_tls(&self, thread_id: ThreadId, static_roots: &mut Vec) { + for TlsEntry { data, .. } in self.keys.values() { + if let Some(ptr) = data.get(&thread_id) { + if let Some(alloc) = ptr.provenance.and_then(|p| p.get_alloc_id()) { + trace!("Thread-local key value leaked and stored as static root: {:?}", alloc); + static_roots.push(alloc); + } + } + } + } } impl VisitProvenance for TlsData<'_> { @@ -227,6 +232,7 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>); enum TlsDtorsStatePriv<'tcx> { #[default] Init, + InitForExit, PthreadDtors(RunningPthreadDtorState), AfterPthreadDtors, ThreadAtexit, @@ -242,7 +248,7 @@ impl<'tcx> TlsDtorsState<'tcx> { /// Returns the state we should start running when the program is exiting. pub fn new_for_exit() -> Self { // Pthread destructors get skipped on `exit`. - Self(TlsDtorsStatePriv::AfterPthreadDtors) + Self(TlsDtorsStatePriv::InitForExit) } pub fn on_stack_empty( @@ -260,6 +266,17 @@ impl<'tcx> TlsDtorsState<'tcx> { break 'new_state AfterPthreadDtors; } } + InitForExit => { + // We skip the pthread dtors, but we mark them as static roots so they do not + // acount as leaked. + if this.target_os_is_unix() { + this.machine.tls.leak_all_thread_tls( + this.active_thread(), + &mut this.machine.static_roots, + ); + } + break 'new_state AfterPthreadDtors; + } PthreadDtors(state) => { match this.schedule_next_pthread_tls_dtor(state)? { Poll::Pending => return interp_ok(Poll::Pending), // just keep going @@ -425,15 +442,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let Some((instance, ptr, key, span)) = dtor { state.last_key = Some(key); trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread); - assert!( - ptr.to_target_usize(this).unwrap() != 0, - "data can't be NULL when dtor is called!" - ); + assert!(ptr.addr() != Size::ZERO, "data can't be NULL when dtor is called!"); this.call_thread_root_function( instance, ExternAbi::C { unwind: false }, - &[ImmTy::from_scalar(ptr, this.machine.layouts.unit_ptr_mut)], + &[ImmTy::from_scalar( + Scalar::from_maybe_pointer(ptr, this), + this.machine.layouts.unit_ptr_mut, + )], None, span, )?; @@ -487,7 +504,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(&ptr) = data.get(&active_thread) else { continue }; assert!( - ptr.to_target_usize(this).unwrap() != 0, + ptr.addr() != Size::ZERO, "TLS key's value can't be null (should be absent instead)" ); @@ -499,7 +516,10 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.call_thread_root_function( instance, ExternAbi::System { unwind: false }, - &[ImmTy::from_scalar(ptr, this.machine.layouts.unit_ptr_mut)], + &[ImmTy::from_scalar( + Scalar::from_maybe_pointer(ptr, this), + this.machine.layouts.unit_ptr_mut, + )], None, span, )?; diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 2c350705fa053..f5fb82ce32d4c 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -918,8 +918,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "pthread_setspecific" => { let [key, new_ptr] = &this.check_shim_sig( @@ -928,8 +928,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`0`). this.write_null(dest)?; diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 2d3b15bcde74d..13eb213802294 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -605,8 +605,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "TlsSetValue" => { // FIXME: This does not have a direct test (#3179). @@ -616,8 +616,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`1`). this.write_int(1, dest)?; @@ -666,8 +666,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let ptr = this.machine.tls.load_tls(key, active_thread, this)?; - this.write_scalar(ptr, dest)?; + let ptr = this.machine.tls.load_tls(key, active_thread)?; + this.write_pointer(ptr, dest)?; } "FlsSetValue" => { // FIXME: This does not have a direct test (#3179). @@ -677,8 +677,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let key = u128::from(this.read_scalar(key)?.to_u32()?); let active_thread = this.active_thread(); - let new_data = this.read_scalar(new_ptr)?; - this.machine.tls.store_tls(key, active_thread, new_data, &*this.tcx)?; + let new_data = this.read_pointer(new_ptr)?; + this.machine.tls.store_tls(key, active_thread, new_data)?; // Return success (`1`). this.write_int(1, dest)?; diff --git a/src/tools/miri/tests/pass/tls/thread_atexit.rs b/src/tools/miri/tests/pass/tls/thread_atexit.rs index 0bc304107059a..942938fcdfd62 100644 --- a/src/tools/miri/tests/pass/tls/thread_atexit.rs +++ b/src/tools/miri/tests/pass/tls/thread_atexit.rs @@ -1,5 +1,6 @@ //! Test the Linux and macOS APIs for thread exit callbacks. //@only-target: linux darwin +//@ignore-target: android //@run-native //@revisions: in_thread with_exit without_exit diff --git a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs index 68ec9973e5675..eb99923508d9f 100644 --- a/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs +++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_main_thread.rs @@ -1,4 +1,6 @@ //! Check that destructors of main thread thread locals are executed when main returns. +//! Only works on tagets that use something other than pthread keys for the destructors. +//@ignore-target: freebsd illumos solaris android //@run-native //@revisions: with_exit without_exit From b4b59b04128e9e71f5918cc9182c2c0063b6503c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 17:21:55 +0200 Subject: [PATCH 14/80] make windows tests cover the exit case as well --- .../miri/tests/pass/tls/win_thread_atexit.rs | 27 ++++++-- ...err => win_thread_atexit.with_exit.stdout} | 0 ... => win_thread_atexit.without_exit.stdout} | 0 ...ls.stdout => windows-tls.in_thread.stdout} | 0 src/tools/miri/tests/pass/tls/windows-tls.rs | 63 ++++++++++++++----- .../pass/tls/windows-tls.with_exit.stdout | 9 +++ .../pass/tls/windows-tls.without_exit.stdout | 9 +++ 7 files changed, 87 insertions(+), 21 deletions(-) rename src/tools/miri/tests/pass/tls/{win_thread_atexit.with_exit.stderr => win_thread_atexit.with_exit.stdout} (100%) rename src/tools/miri/tests/pass/tls/{win_thread_atexit.without_exit.stderr => win_thread_atexit.without_exit.stdout} (100%) rename src/tools/miri/tests/pass/tls/{windows-tls.stdout => windows-tls.in_thread.stdout} (100%) create mode 100644 src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout create mode 100644 src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs index 0cedb225d0c85..901608311b62f 100644 --- a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs @@ -1,11 +1,29 @@ //! Ensure that we call Windows TLS callbacks in the local crate. //@only-target: windows //@revisions: with_exit without_exit +// Somehow there's apparently an extra thread natively in the `without_exit` variant +// so the callback triggers too often. +//@[with_exit] run-native -use core::fmt::Write; +// Printing without touching the Rust runtime. +fn print_safe(msg: &str) { + use std::io::Write; + use std::os::windows::io::FromRawHandle; + use std::{ffi, fs, mem}; -#[path = "../../utils/mod.rs"] -mod utils; + pub const STD_OUTPUT_HANDLE: u32 = 4294967285; + + #[link(name = "kernel32")] + unsafe extern "system" { + fn GetStdHandle(which: u32) -> *mut ffi::c_void; + } + + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + let mut handle = mem::ManuallyDrop::new(fs::File::from_raw_handle(handle)); + handle.write_all(msg.as_bytes()).unwrap(); + } +} #[link_section = ".CRT$XLB"] #[used] // Miri only considers explicitly `#[used]` statics for `lookup_link_section` @@ -13,10 +31,11 @@ pub static CALLBACK: unsafe extern "system" fn(*const (), u32, *const ()) = tls_ unsafe extern "system" fn tls_callback(_h: *const (), _dw_reason: u32, _pv: *const ()) { // Do not call into std as it has already been shut down. That means we cannot run natively. - writeln!(utils::MiriStderr, "in tls_callback").unwrap(); + print_safe("in tls_callback\n"); } fn main() { + // Spawn a thread so that we see the hook called twice. std::thread::spawn(|| {}); if cfg!(with_exit) { std::process::exit(0); diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stderr rename to src/tools/miri/tests/pass/tls/win_thread_atexit.with_exit.stdout diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr b/src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stderr rename to src/tools/miri/tests/pass/tls/win_thread_atexit.without_exit.stdout diff --git a/src/tools/miri/tests/pass/tls/windows-tls.stdout b/src/tools/miri/tests/pass/tls/windows-tls.in_thread.stdout similarity index 100% rename from src/tools/miri/tests/pass/tls/windows-tls.stdout rename to src/tools/miri/tests/pass/tls/windows-tls.in_thread.stdout diff --git a/src/tools/miri/tests/pass/tls/windows-tls.rs b/src/tools/miri/tests/pass/tls/windows-tls.rs index 32dd5399a08d0..5a644496204a1 100644 --- a/src/tools/miri/tests/pass/tls/windows-tls.rs +++ b/src/tools/miri/tests/pass/tls/windows-tls.rs @@ -1,7 +1,8 @@ //@only-target: windows # this directly tests windows-only functions +//@revisions: with_exit without_exit in_thread use std::ffi::c_void; -use std::{ptr, thread}; +use std::ptr; pub type BOOL = i32; pub const FALSE: BOOL = 0i32; @@ -25,6 +26,26 @@ extern "system" fn dtor_unreachable(_val: *mut c_void) { unreachable!() } +// Printing without touching the Rust runtime. +fn print_safe(msg: &str) { + use std::io::Write; + use std::os::windows::io::FromRawHandle; + use std::{ffi, fs, mem}; + + pub const STD_OUTPUT_HANDLE: u32 = 4294967285; + + #[link(name = "kernel32")] + unsafe extern "system" { + fn GetStdHandle(which: u32) -> *mut ffi::c_void; + } + + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + let mut handle = mem::ManuallyDrop::new(fs::File::from_raw_handle(handle)); + handle.write_all(msg.as_bytes()).unwrap(); + } +} + fn fls_0_zero_value_does_not_run() { let key = unsafe { FlsAlloc(Some(dtor_unreachable)) }; @@ -36,7 +57,7 @@ fn fls_0_zero_value_does_not_run() { fn fls_1_dtor_simple() { extern "system" fn dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_1_dtor_simple"); + print_safe("fls_1_dtor_simple\n"); // Keys are freed in-order. Without a dtor, the early key's value is not zeroed out. let early_key = val as u32; @@ -60,11 +81,11 @@ fn fls_1_dtor_simple() { fn fls_2_dtor_update_value_ignored() { extern "system" fn early_dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_2.1_early_dtor"); + print_safe("fls_2.1_early_dtor\n"); } extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_2.2_later_dtor"); + print_safe("fls_2.2_later_dtor\n"); // Updating a different fls slot's value doesn't cause their dtor to run, if it already did. let early_key = val as u32; @@ -90,7 +111,7 @@ fn fls_2_dtor_update_value_ignored() { fn fls_3_dtor_update_value_skipped_ignored() { extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_3_later_dtor"); + print_safe("fls_3_later_dtor\n"); // Updating a different fls slot's value doesn't cause their dtor to run, if it was already skipped. let early_key = val as u32; @@ -108,7 +129,7 @@ fn fls_3_dtor_update_value_skipped_ignored() { fn fls_4_dtor_update_value_used() { extern "system" fn early_dtor(val: *mut c_void) { - println!("fls_4.1_early_dtor"); + print_safe("fls_4.1_early_dtor\n"); // Updating a different fls slot's value affect their dtor, if it hasn't yet run. let later_key = val as u32; @@ -116,7 +137,7 @@ fn fls_4_dtor_update_value_used() { } extern "system" fn later_dtor(_val: *mut c_void) { - println!("fls_4.2_later_dtor"); + print_safe("fls_4.2_later_dtor\n"); } let early_key = unsafe { FlsAlloc(Some(early_dtor)) }; @@ -133,7 +154,7 @@ fn fls_4_dtor_update_value_used() { fn fls_5_dtor_value() { extern "system" fn dtor(val: *mut c_void) { assert!(!val.is_null()); - println!("fls_5_dtor_value"); + print_safe("fls_5_dtor_value\n"); // When the key's dtor run, the key's value equals the destructor argument. let key = val as u32; @@ -147,7 +168,7 @@ fn fls_5_dtor_value() { fn fls_6_no_dtor_does_not_zero_value() { extern "system" fn later_dtor(val: *mut c_void) { - println!("fls_6_later_dtor"); + print_safe("fls_6_later_dtor\n"); // If an earlier fls slot doesn't have a dtor, the key's value is _not_ zeroed out. let early_key = val as u32; @@ -166,7 +187,7 @@ fn fls_6_no_dtor_does_not_zero_value() { fn fls_7_free() { // This dtor is not expected to be called. extern "system" fn dtor_for_freed_key(_val: *mut c_void) { - println!("dtor_for_freed_key"); + print_safe("dtor_for_freed_key\n"); } let key_with_dtor = unsafe { FlsAlloc(Some(dtor_for_freed_key)) }; assert_eq!(unsafe { FlsSetValue(key_with_dtor, ptr::without_provenance_mut(0)) }, TRUE); @@ -190,16 +211,24 @@ fn fls() { fls_7_free(); } -fn main() { +fn the_test() { let key = unsafe { TlsAlloc() }; assert_eq!(unsafe { TlsSetValue(key, ptr::without_provenance_mut(1)) }, TRUE); assert_eq!(unsafe { TlsGetValue(key).addr() }, 1); assert_eq!(unsafe { TlsFree(key) }, TRUE); - thread::spawn(|| { - fls(); - println!("exiting thread"); - }) - .join() - .unwrap(); + fls(); + print_safe("exiting thread\n"); +} + +fn main() { + if cfg!(in_thread) { + return std::thread::spawn(the_test).join().unwrap(); + } + + the_test(); + + if cfg!(with_exit) { + std::process::exit(0); + } } diff --git a/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout b/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout new file mode 100644 index 0000000000000..f3034813f224a --- /dev/null +++ b/src/tools/miri/tests/pass/tls/windows-tls.with_exit.stdout @@ -0,0 +1,9 @@ +exiting thread +fls_1_dtor_simple +fls_2.1_early_dtor +fls_2.2_later_dtor +fls_3_later_dtor +fls_4.1_early_dtor +fls_4.2_later_dtor +fls_5_dtor_value +fls_6_later_dtor diff --git a/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout b/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout new file mode 100644 index 0000000000000..f3034813f224a --- /dev/null +++ b/src/tools/miri/tests/pass/tls/windows-tls.without_exit.stdout @@ -0,0 +1,9 @@ +exiting thread +fls_1_dtor_simple +fls_2.1_early_dtor +fls_2.2_later_dtor +fls_3_later_dtor +fls_4.1_early_dtor +fls_4.2_later_dtor +fls_5_dtor_value +fls_6_later_dtor From e4d6be45e222449f9c6f217d3ef7cdcc4cd3b5e4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 17:56:17 +0200 Subject: [PATCH 15/80] proper test for yield-before-exit --- src/tools/miri/src/eval.rs | 19 ++++++------------- .../fail/concurrency/yield-before-exit.rs | 12 ++++++++++++ .../yield-before-exit.with_exit.stderr | 19 +++++++++++++++++++ .../yield-before-exit.without_exit.stderr | 19 +++++++++++++++++++ .../tests/many-seeds/scoped-thread-leak.rs | 8 -------- 5 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 src/tools/miri/tests/fail/concurrency/yield-before-exit.rs create mode 100644 src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr create mode 100644 src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr delete mode 100644 src/tools/miri/tests/many-seeds/scoped-thread-leak.rs diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 6eddb382a2984..5ba03ff1bad7f 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -258,21 +258,14 @@ impl<'tcx> ProgramExitHandler<'tcx> { Poll::Pending => {} // just keep going Poll::Ready(()) => { if this.machine.data_race.as_genmc_ref().is_some() { - // In GenMC mode, we don't yield at the end of the main thread. - // Instead, the `GenmcCtx` will ensure that unfinished threads get a chance to run at this point. + // In GenMC mode, we don't yield at the end of the main thread. Instead, + // the `GenmcCtx` will ensure that unfinished threads get a chance to + // run at this point. self.state = Done; } else { - // Give background threads a chance to finish by yielding the main thread a - // couple of times -- but only if we would also preempt threads randomly. - if this.machine.preemption_rate > 0.0 { - // There is a non-zero chance they will yield back to us often enough to - // make Miri terminate eventually. - self.state = Yield { remaining: THREAD_YIELDS_AT_EXIT }; - } else { - // The other threads did not get preempted, so no need to yield back to - // them. - self.state = Done; - } + // Give background threads a chance to finish by yielding the main + // thread a couple of times. + self.state = Yield { remaining: THREAD_YIELDS_AT_EXIT }; } } }, diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs b/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs new file mode 100644 index 0000000000000..e293961d50097 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.rs @@ -0,0 +1,12 @@ +//! Regression test for https://github.com/rust-lang/miri/issues/2629: +//! If we fail to yield from the main thread, we miss the UB here. +//@compile-flags: -Zmiri-fixed-schedule +//@revisions: with_exit without_exit + +fn main() { + std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + //~^ERROR: Undefined Behavior + if cfg!(with_exit) { + std::process::exit(0); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr b/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr new file mode 100644 index 0000000000000..4608f64203cf3 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.with_exit.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: entering unreachable code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` +note: the current function got called indirectly due to this code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr b/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr new file mode 100644 index 0000000000000..4608f64203cf3 --- /dev/null +++ b/src/tools/miri/tests/fail/concurrency/yield-before-exit.without_exit.stderr @@ -0,0 +1,19 @@ +error: Undefined Behavior: entering unreachable code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: this is on thread `unnamed-ID` +note: the current function got called indirectly due to this code + --> tests/fail/concurrency/yield-before-exit.rs:LL:CC + | +LL | std::thread::spawn(|| unsafe { std::hint::unreachable_unchecked() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs b/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs deleted file mode 100644 index f28e43696f709..0000000000000 --- a/src/tools/miri/tests/many-seeds/scoped-thread-leak.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Regression test for https://github.com/rust-lang/miri/issues/2629 -use std::thread; - -fn main() { - thread::scope(|s| { - s.spawn(|| {}); - }); -} From faa74c1231b87149add0e9127ec42c02a727e1b5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 19 Oct 2026 20:48:22 +0200 Subject: [PATCH 16/80] work around a genmc problem --- src/tools/miri/src/shims/extern_static.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index fb0f2e0b0d461..1631c160dbfc6 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -62,7 +62,11 @@ impl<'tcx> MiriMachine<'tcx> { Os::Linux => { Self::weak_fn_symbols(ecx, &["getrandom", "gettid", "statx", "strlen"])?; if ecx.tcx.sess.target.env == Env::Gnu { - Self::weak_fn_symbols(ecx, &["__cxa_thread_atexit_impl"])?; + // FIXME: enabling this with genmc causes test failures because of how + // std then uses pthread keys and genmc does not know about their state. + if ecx.machine.data_race.as_genmc_ref().is_none() { + Self::weak_fn_symbols(ecx, &["__cxa_thread_atexit_impl"])?; + } // The __dso_handle symbol just marks a unique global address. let val = ImmTy::from_int(0, ecx.machine.layouts.u8); Self::alloc_extern_static(ecx, "__dso_handle", val)?; From 7a6d062621b0c818dd27625c7bf6e80a449f9af2 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Mon, 19 Oct 2026 21:48:14 -0700 Subject: [PATCH 17/80] macOS shims: migrate to check_shim_sig --- .../src/shims/unix/macos/foreign_items.rs | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 2b5428e36f298..5745ee1b67f73 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -35,27 +34,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // errno "__error" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } // File related shims "close$NOCANCEL" => { - let [fd] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [fd] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let fd = this.read_scalar(fd)?.to_i32()?; let result = this.close(fd)?; this.write_scalar(result, dest)?; } "stat$INODE64" => { - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, buf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *libc::stat) -> i32), + (link_name, abi, args), + )?; let result = this.stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat$INODE64" => { - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, buf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *libc::stat) -> i32), + (link_name, abi, args), + )?; let result = this.lstat(path, buf)?; this.write_scalar(result, dest)?; } @@ -76,17 +81,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "opendir$INODE64" => { - let [name] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [name] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; let result = this.opendir(name)?; this.write_scalar(result, dest)?; } "readdir$INODE64" => { - let [dirp] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirp] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; this.readdir(dirp, dest, "dirent")?; } "realpath$DARWIN_EXTSN" => { - let [path, resolved_path] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [path, resolved_path] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> *_), + (link_name, abi, args), + )?; let result = this.realpath(path, resolved_path)?; this.write_scalar(result, dest)?; } @@ -94,15 +103,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Environment related shims "_NSGetEnviron" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let environ = this.machine.env_vars.unix().environ(); this.write_pointer(environ, dest)?; } // Random data generation "CCRandomGenerateBytes" => { - let [bytes, count] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [bytes, count] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize) -> i32), + (link_name, abi, args), + )?; let bytes = this.read_pointer(bytes)?; let count = this.read_target_usize(count)?; let success = this.eval_libc_i32("kCCSuccess"); @@ -112,22 +124,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Time related shims "mach_absolute_time" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> u64), (link_name, abi, args))?; let result = this.mach_absolute_time()?; this.write_scalar(result, dest)?; } "mach_timebase_info" => { // FIXME: This does not have a direct test (#3179). - let [info] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [info] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; let result = this.mach_timebase_info(info)?; this.write_scalar(result, dest)?; } "mach_wait_until" => { // FIXME: This does not have a direct test (#3179). - let [deadline] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [deadline] = &this + .check_shim_sig(shim_sig!(extern "C" fn(u64) -> i32), (link_name, abi, args))?; let result = this.mach_wait_until(deadline)?; this.write_scalar(result, dest)?; } @@ -135,18 +149,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Access to command-line arguments "_NSGetArgc" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?; } "_NSGetArgv" => { // FIXME: This does not have a direct test (#3179). - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?; } "_NSGetExecutablePath" => { // FIXME: This does not have a direct test (#3179). - let [buf, bufsize] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [buf, bufsize] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; this.check_no_isolation("`_NSGetExecutablePath`")?; let buf_ptr = this.read_pointer(buf)?; @@ -173,8 +191,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "_tlv_atexit" => { - let [dtor, data] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dtor, data] = &this.check_shim_sig( + shim_sig!(extern "C" fn(fn(..) -> _, *_) -> ()), + (link_name, abi, args), + )?; let dtor = this.read_pointer(dtor)?; let dtor = this.get_ptr_fn(dtor)?.as_instance()?; let data = this.read_scalar(data)?; @@ -190,14 +210,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Querying system information "pthread_get_stackaddr_np" => { // FIXME: This does not have a direct test (#3179). - let [thread] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t) -> *_), + (link_name, abi, args), + )?; this.read_target_usize(thread)?; let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size()); this.write_scalar(stack_addr, dest)?; } "pthread_get_stacksize_np" => { // FIXME: This does not have a direct test (#3179). - let [thread] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t) -> usize), + (link_name, abi, args), + )?; this.read_target_usize(thread)?; let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size()); this.write_scalar(stack_size, dest)?; @@ -205,7 +231,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Threading "pthread_setname_np" => { - let [name] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [name] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; // The real implementation has logic in two places: // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200, @@ -234,8 +261,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // The function's behavior isn't portable between platforms. // In case of macOS, a truncated name (due to a too small buffer) @@ -258,16 +287,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_threadid_np" => { - let [thread, tid_ptr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, tid_ptr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let res = this.apple_pthread_threadid_np(thread, tid_ptr)?; this.write_scalar(res, dest)?; } // Synchronization primitives "os_sync_wait_on_address" => { - let [addr_op, value_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -278,8 +311,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wait_on_address_with_deadline" => { - let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this + .check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -290,8 +326,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wait_on_address_with_timeout" => { - let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this + .check_shim_sig( + shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32), + (link_name, abi, args), + )?; this.os_sync_wait_on_address( addr_op, value_op, @@ -302,42 +341,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "os_sync_wake_by_address_any" => { - let [addr_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ false, dest, )?; } "os_sync_wake_by_address_all" => { - let [addr_op, size_op, flags_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr_op, size_op, flags_op] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, u32) -> i32), + (link_name, abi, args), + )?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ true, dest, )?; } "os_unfair_lock_lock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_lock(lock_op)?; } "os_unfair_lock_trylock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> bool), (link_name, abi, args))?; this.os_unfair_lock_trylock(lock_op, dest)?; } "os_unfair_lock_unlock" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_unlock(lock_op)?; } "os_unfair_lock_assert_owner" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_assert_owner(lock_op)?; } "os_unfair_lock_assert_not_owner" => { - let [lock_op] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [lock_op] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?; this.os_unfair_lock_assert_not_owner(lock_op)?; } "dispatch_semaphore_create" => { @@ -375,8 +418,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pthread_cond_timedwait_relative_np" => { - let [cond, mutex, reltime] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [cond, mutex, reltime] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; this.pthread_cond_timedwait( cond, mutex, reltime, dest, /* macos_relative_np */ true, )?; @@ -385,8 +430,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "confstr" => { - let [_key, _buf, _buflen] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_key, _buf, _buflen] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, usize) -> usize), + (link_name, abi, args), + )?; // We just pretend that no configuration key exists, and return EINVAL. this.set_last_error(LibcError("EINVAL"))?; this.write_null(dest)?; From 6776628ab0d501f4bdffba5a65f0975e3f88f9a6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Oct 2026 08:29:37 +0200 Subject: [PATCH 18/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 8144389c895da997dbdecaa635e41d6a50be6ffd. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 7c934ebcea4c1..60438fb681c3b 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -b6eeb93c992027a03e1fd120b6652546396b142c +8144389c895da997dbdecaa635e41d6a50be6ffd From f61ab11a2e5656d7f40be8a9e89609d61d9a55dc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Oct 2026 08:30:59 +0200 Subject: [PATCH 19/80] fix windows tls native tests --- src/tools/miri/tests/pass/tls/win_thread_atexit.rs | 5 ++--- src/tools/miri/tests/pass/tls/windows-tls.rs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs index 901608311b62f..65fd24d65b7b9 100644 --- a/src/tools/miri/tests/pass/tls/win_thread_atexit.rs +++ b/src/tools/miri/tests/pass/tls/win_thread_atexit.rs @@ -1,9 +1,8 @@ //! Ensure that we call Windows TLS callbacks in the local crate. //@only-target: windows //@revisions: with_exit without_exit -// Somehow there's apparently an extra thread natively in the `without_exit` variant -// so the callback triggers too often. -//@[with_exit] run-native +// run-native does not work properly. It randomly sometimes calls the hook 3 times. I guess there's +// sometimes another thread appearing from somewhere. // Printing without touching the Rust runtime. fn print_safe(msg: &str) { diff --git a/src/tools/miri/tests/pass/tls/windows-tls.rs b/src/tools/miri/tests/pass/tls/windows-tls.rs index 5a644496204a1..b29035a7e75cf 100644 --- a/src/tools/miri/tests/pass/tls/windows-tls.rs +++ b/src/tools/miri/tests/pass/tls/windows-tls.rs @@ -1,5 +1,7 @@ //@only-target: windows # this directly tests windows-only functions //@revisions: with_exit without_exit in_thread +// FIXME: run-native explodes for the other revisions. (https://github.com/rust-lang/miri/issues/5444) +//@[without_exit] run-native use std::ffi::c_void; use std::ptr; From 38d78d6c151aa9a0d3e3df34a493d0dd3ce031f8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Oct 2026 08:39:23 +0200 Subject: [PATCH 20/80] also make __dso_handle available as weak symbol and remove std work-around --- src/tools/miri/src/shims/extern_static.rs | 8 +++++--- src/tools/miri/src/shims/unix/linux/foreign_items.rs | 6 +----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index 1631c160dbfc6..5e7a391aa8451 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -16,11 +16,11 @@ impl<'tcx> MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, name: &str, val: ImmTy<'tcx>, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, Pointer> { let place = ecx.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; ecx.write_immediate(*val, &place)?; Self::add_extern_static(ecx, name, place.ptr()); - interp_ok(()) + interp_ok(place.ptr()) } /// Make `ptr` available as a weak symbol with the given name. @@ -69,7 +69,9 @@ impl<'tcx> MiriMachine<'tcx> { } // The __dso_handle symbol just marks a unique global address. let val = ImmTy::from_int(0, ecx.machine.layouts.u8); - Self::alloc_extern_static(ecx, "__dso_handle", val)?; + let ptr = Self::alloc_extern_static(ecx, "__dso_handle", val)?; + // We also need a weak symbol for `__dso_handle`. + Self::add_weak_symbol(ecx, "__dso_handle", ptr)?; } } Os::Android => { diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index d77d8f2a873d1..24c83bf097b8c 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -253,11 +253,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dso = this.read_pointer(dso)?; let dso_handle = Symbol::intern("__dso_handle"); - // FIXME: we skip this check in std because std passes the wrong address, - // see - if dso.addr() != Pointer::from(this.machine.extern_statics[&dso_handle]).addr() - && !this.frame_in_std() - { + if dso.addr() != Pointer::from(this.machine.extern_statics[&dso_handle]).addr() { throw_unsup_format!( "__cxa_thread_atexit_impl: a DSO value other than the address of `__dso_handle` is not supported" ); From e8f4356f71ecc7aec6bdc4d473f849aaf9c0e503 Mon Sep 17 00:00:00 2001 From: Hamdan-Khan Date: Tue, 20 Oct 2026 15:29:44 +0500 Subject: [PATCH 21/80] migrate linux shim checks from using `check_shim_sig_deprecated` to `check_shim_sig` --- .../src/shims/unix/linux/foreign_items.rs | 84 ++++++++++++------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 62ec9d2bc7ed0..dba89c05cb9ac 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -147,14 +146,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "sync_file_range" => { - let [fd, offset, nbytes, flags] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [fd, offset, nbytes, flags] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t, u32) -> i32), + (link_name, abi, args), + )?; let result = this.sync_file_range(fd, offset, nbytes, flags)?; this.write_scalar(result, dest)?; } "statx" => { - let [dirfd, pathname, flags, mask, statxbuf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirfd, pathname, flags, mask, statxbuf] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, u32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } @@ -167,38 +170,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "readdir64" => { - let [dirp] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [dirp] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?; this.readdir(dirp, dest, "dirent64")?; } // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let res = match this.pthread_setname_np( this.read_scalar(thread)?, this.read_scalar(name)?, @@ -211,8 +224,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // The function's behavior isn't portable between platforms. // In case of glibc, the length of the output buffer must // be not shorter than TASK_COMM_LEN. @@ -233,7 +248,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "gettid" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this.check_shim_sig( + shim_sig!(extern "C" fn() -> libc::pid_t), + (link_name, abi, args), + )?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } @@ -246,8 +264,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "mmap64" => { - let [addr, length, prot, flags, fd, offset] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [addr, length, prot, flags, fd, offset] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize, i32, i32, i32, libc::off64_t) -> *_), + (link_name, abi, args), + )?; let offset = this.read_scalar(offset)?.to_i64()?; let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?; this.write_scalar(ptr, dest)?; @@ -261,23 +281,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(ptr, dest)?; } "__xpg_strerror_r" => { - let [errnum, buf, buflen] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [errnum, buf, buflen] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, usize) -> i32), + (link_name, abi, args), + )?; let result = this.strerror_r(errnum, buf, buflen)?; this.write_scalar(result, dest)?; } "__errno_location" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "__libc_current_sigrtmin" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; this.write_int(SIGRTMIN, dest)?; } "__libc_current_sigrtmax" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; this.write_int(SIGRTMAX, dest)?; } @@ -285,15 +310,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_getattr_np" if this.frame_in_std() => { - let [_thread, _attr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_thread, _attr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "gnu_get_libc_version" if this.frame_in_std() && this.tcx.sess.target.env == rustc_target::spec::Env::Gnu => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; // We have to be at least version 2.26 so that std does not call `res_init`. // This returns a C string, so we have to add a null terminator. let version = "2.26\0"; From 750d8d3f103e92118a1e19f2185de2b8a19212e8 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 08:49:34 -0400 Subject: [PATCH 22/80] Remove /tmp workaround now that rustdoc fixed the disk usage issue --- compiler/rustc_codegen_gcc/.github/workflows/ci.yml | 2 -- compiler/rustc_codegen_gcc/.github/workflows/rustup.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml index 0687bc1150907..66362e27ce4da 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml @@ -116,8 +116,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV #- name: Cache rust repository ## We only clone the rust repository for rustc tests diff --git a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml index 57d7a39de049a..0309ccbad5481 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/rustup.yml @@ -77,8 +77,6 @@ jobs: - name: Set env run: | echo "workspace="$GITHUB_WORKSPACE >> $GITHUB_ENV - # /tmp is a small tmpfs, and rustdoc keeps every compiled doctest there until the run ends. - echo "TMPDIR=$RUNNER_TEMP" >> $GITHUB_ENV - name: Prepare if: ${{ matrix.run_cargo_tests }} From 5953e0ad9fec6231963522e2e4fcb197657e7a97 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 20 Oct 2026 07:13:05 -0700 Subject: [PATCH 23/80] math shims: migrate to check_shim_sig --- src/tools/miri/src/shims/math.rs | 38 +++++++++++++++++++++++--------- src/tools/miri/src/shims/sig.rs | 8 ++++++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/src/shims/math.rs b/src/tools/miri/src/shims/math.rs index 1a3228425af9a..81c303307f457 100644 --- a/src/tools/miri/src/shims/math.rs +++ b/src/tools/miri/src/shims/math.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_apfloat::Float; use rustc_middle::ty::Ty; use rustc_span::Symbol; @@ -38,7 +37,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erff" | "erfcf" => { - let [f] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32) -> f32), + (link_name, abi, args), + )?; let f = this.read_scalar(f)?.to_f32()?; let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| { @@ -81,7 +83,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2f" | "fdimf" => { - let [f1, f2] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f1, f2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32, f32) -> f32), + (link_name, abi, args), + )?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; @@ -125,7 +130,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erf" | "erfc" => { - let [f] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64) -> f64), + (link_name, abi, args), + )?; let f = this.read_scalar(f)?.to_f64()?; let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| { @@ -168,7 +176,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2" | "fdim" => { - let [f1, f2] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [f1, f2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, f64) -> f64), + (link_name, abi, args), + )?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; @@ -199,7 +210,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "ldexp" | "scalbn" => { - let [x, exp] = this.check_shim_sig_deprecated(abi, CanonAbi::C , link_name, args)?; + let [x, exp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, i32) -> f64), + (link_name, abi, args), + )?; // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same. let x = this.read_scalar(x)?.to_f64()?; let exp = this.read_scalar(exp)?.to_i32()?; @@ -209,8 +223,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgammaf_r" => { - let [x, signp] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [x, signp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f32, *_) -> f32), + (link_name, abi, args), + )?; let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; @@ -229,8 +245,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgamma_r" => { - let [x, signp] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [x, signp] = &this.check_shim_sig( + shim_sig!(extern "C" fn(f64, *_) -> f64), + (link_name, abi, args), + )?; let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 359f98861ffe9..7d0a995a64797 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -21,7 +21,7 @@ pub struct ShimSig<'tcx, const ARGS: usize> { /// ``` /// /// The following types are supported: -/// - primitive integer types +/// - primitive integer and floating-point types /// - `()` /// - (thin) raw pointers, written `*_` since the mutability and pointee type are irrelevant /// - `$crate::$mod::...::$ty` for a type from the given crate (most commonly that is `libc`) @@ -140,6 +140,12 @@ macro_rules! shim_sig_arg { ($this:ident, usize) => { $this.tcx.types.usize }; + ($this:ident, f32) => { + $this.tcx.types.f32 + }; + ($this:ident, f64) => { + $this.tcx.types.f64 + }; ($this:ident, ()) => { $this.tcx.types.unit }; From 8a301c09dc29846b24b0ee2025bc452296dbd8e8 Mon Sep 17 00:00:00 2001 From: Mehmet <262413094+mehmettoplu-rs@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:01:10 +0300 Subject: [PATCH 24/80] Android shims: migrate to check_shim_sig --- .../src/shims/unix/android/foreign_items.rs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 6af9bcb74692f..7740757ee336a 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -76,37 +75,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Miscellaneous "__errno" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "gettid" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this.check_shim_sig( + shim_sig!(extern "C" fn() -> libc::pid_t), + (link_name, abi, args), + )?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } From 6e8e699e1cfb4797b6039f83a7aac0951ddb54c0 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 18:00:25 -0400 Subject: [PATCH 25/80] Use correct alignment for global variables --- compiler/rustc_codegen_gcc/src/consts.rs | 20 +++++---- compiler/rustc_codegen_gcc/src/context.rs | 4 +- .../tests/asm/const_global_alignment.rs | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 902f8ba52faaa..b5a4dcf87bbd6 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -64,14 +64,18 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let cv = const_alloc_to_gcc(self, alloc); let align = alloc.inner().align; - if let Some(variable) = self.const_globals.borrow().get(&cv) { - if let Some(global_variable) = self.global_lvalues.borrow().get(variable) { - let alignment = align.bits() as i32; - if alignment > global_variable.get_alignment() { - global_variable.set_alignment(alignment); - } + if let Some(&mut (variable, ref mut global_align)) = + self.const_globals.borrow_mut().get_mut(&cv) + { + // `set_alignment` replaces the alignment, which is on the global's type and thus not + // reported by `get_alignment`: only raise it, or a smaller use would lower it. + if align > *global_align + && let Some(global_variable) = self.global_lvalues.borrow().get(&variable) + { + global_variable.set_alignment(align.bytes() as i32); + *global_align = align; } - return *variable; + return variable; } let global_value = self.static_addr_of_mut(cv, align, kind); #[cfg(feature = "master")] @@ -80,7 +84,7 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { .get(&global_value) .expect("`static_addr_of_mut` did not add the global to `self.global_lvalues`") .global_set_readonly(); - self.const_globals.borrow_mut().insert(cv, global_value); + self.const_globals.borrow_mut().insert(cv, (global_value, align)); global_value } diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index b57e0fb2cd23a..8eaa155ab8be1 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -126,8 +126,8 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Local holding the return value of the defined functions returning it in memory. pub functions_with_indirect_return: RefCell, LValue<'gcc>>>, - /// Cache of emitted const globals (value -> global) - pub const_globals: RefCell, RValue<'gcc>>>, + /// Cache of emitted const globals (value -> global and its alignment) + pub const_globals: RefCell, (RValue<'gcc>, Align)>>, /// Map from the address of a global variable (rvalue) to the global variable itself (lvalue). /// FIXME(antoyo): remove when the rustc API is fixed. diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs new file mode 100644 index 0000000000000..e4dfd4bb26dd0 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_global_alignment.rs @@ -0,0 +1,44 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu + +#![crate_type = "lib"] + +// Constants with identical bytes share one global, which needs the largest alignment among +// their uses: lowering it to a later, smaller one or reading it as bits fails these checks. + +// CHECK: "A_WIDE": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES:global\.[0-9]+]]" +#[no_mangle] +pub static A_WIDE: &[u128; 4] = &[0x09090909_09090909_09090909_09090909; 4]; + +// CHECK: "B_BYTES": +// CHECK-NEXT: .quad "[[WIDE_THEN_BYTES]]" +#[no_mangle] +pub static B_BYTES: &[u8; 64] = &[9; 64]; + +// CHECK: "C_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE:global\.[0-9]+]]" +#[no_mangle] +pub static C_BYTES: &[u8; 64] = &[8; 64]; + +// CHECK: "D_WIDE": +// CHECK-NEXT: .quad "[[BYTES_THEN_WIDE]]" +#[no_mangle] +pub static D_WIDE: &[u128; 4] = &[0x08080808_08080808_08080808_08080808; 4]; + +// CHECK: "E_BYTES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES:global\.[0-9]+]]" +#[no_mangle] +pub static E_BYTES: &[u8; 4] = &[3; 4]; + +// CHECK: "F_HALVES": +// CHECK-NEXT: .quad "[[BYTES_THEN_HALVES]]" +#[no_mangle] +pub static F_HALVES: &[u16; 2] = &[0x0303; 2]; + +// CHECK: .section .rodata.[[WIDE_THEN_BYTES]],"a" +// CHECK-NEXT: .align {{16|32|64}}{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_WIDE]],"a" +// CHECK-NEXT: .align 16{{$}} +// CHECK: .section .rodata.[[BYTES_THEN_HALVES]],"a" +// CHECK-NEXT: .align 2{{$}} From a4f5320342eccb1036cf3c38e006c550d4041e6f Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 13:59:58 -0400 Subject: [PATCH 26/80] Remove integer packing workaround and replace by the new_array_from_bytes API to reduce RAM usage --- compiler/rustc_codegen_gcc/Cargo.lock | 8 +- compiler/rustc_codegen_gcc/Cargo.toml | 2 +- compiler/rustc_codegen_gcc/libgccjit.version | 2 +- compiler/rustc_codegen_gcc/src/common.rs | 79 ++++--------------- compiler/rustc_codegen_gcc/src/consts.rs | 16 +++- compiler/rustc_codegen_gcc/src/context.rs | 4 +- .../tests/compile/large_byte_constant.rs | 6 +- 7 files changed, 41 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index d94ce6575ac0e..aec0ef270bf73 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cd340f3c265988de3379689cbbd103c25b1f8a3d0cc73f91a49083e899d3e1" +checksum = "ea1820165dfb376a3bbfe8b69628d9e5a851c423e0480a666f71f560e2d0a924" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2d9d74572f004908f17ec1bd0af071e5db63a12c3335c9651a6125593951c6" +checksum = "a6091291fc118448b91df5b662d20564020198fee46d8445a6aa0c25b6b435be" dependencies = [ "libc", ] diff --git a/compiler/rustc_codegen_gcc/Cargo.toml b/compiler/rustc_codegen_gcc/Cargo.toml index 95c4c991297fa..3e279961bfbe0 100644 --- a/compiler/rustc_codegen_gcc/Cargo.toml +++ b/compiler/rustc_codegen_gcc/Cargo.toml @@ -20,7 +20,7 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "7.2.0", features = ["dlopen"] } +gccjit = { version = "7.3.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/compiler/rustc_codegen_gcc/libgccjit.version b/compiler/rustc_codegen_gcc/libgccjit.version index 0bcc711e4696b..0c6f40148d1e5 100644 --- a/compiler/rustc_codegen_gcc/libgccjit.version +++ b/compiler/rustc_codegen_gcc/libgccjit.version @@ -1 +1 @@ -37ea7cd851f04ee6a313c5067c9fde9328f876aa +e1c6e07b095d091177376a163da44039475e05e3 diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 21d92c6cc2936..948d2df6b92a5 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -126,18 +126,6 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } } -/// The element type and element count of the array used to represent a run of `len` constant bytes. -/// -/// Larger integers are used where possible: this reduces the number of rvalues, which is a -/// significant memory saving on constant-heavy crates. -fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u64) { - match len % 8 { - 0 => (cx.context.new_type::(), len as u64 / 8), - 4 => (cx.context.new_type::(), len as u64 / 4), - _ => (cx.context.new_type::(), len as u64), - } -} - /// The type [`bytes_in_context`] gives a run of `len` constant bytes. /// /// Exposed separately so that the type of a constant allocation can be computed before any of its @@ -147,65 +135,30 @@ fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u6 /// Two equal-but-distinct array types would key [`CodegenCx::type_struct`] differently and so /// produce two distinct anonymous structs, and libgccjit compares struct types by identity. pub fn bytes_type_in_context<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> Type<'gcc> { - let (element_type, count) = byte_run_shape(cx, len); - if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { + if let Some(&typ) = cx.byte_array_types.borrow().get(&len) { return typ; } - let typ = new_array_type(cx.context, None, element_type, count); - cx.byte_array_types.borrow_mut().insert((element_type, count), typ); + let typ = new_array_type(cx.context, None, cx.type_u8(), len as u64); + cx.byte_array_types.borrow_mut().insert(len, typ); typ } -// FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that -// `global_set_initializer` is more memory efficient than the current solution. -// `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, -// or is it using a more efficient representation? pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { let typ = bytes_type_in_context(cx, bytes.len()); - let (element_type, _) = byte_run_shape(cx, bytes.len()); - let context = &cx.context; - // Since we are representing arbitrary byte runs as integers, we need to follow the target - // endianness. - let endian = cx.sess().target.options.endian; - let elements: Vec<_> = match bytes.len() % 8 { - 0 => { - let (arrays, remainder) = bytes.as_chunks::<8>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_long( - element_type, - match endian { - rustc_abi::Endian::Little => u64::from_le_bytes(arr) as i64, - rustc_abi::Endian::Big => u64::from_be_bytes(arr) as i64, - }, - ) - }) - .collect() - } - 4 => { - let (arrays, remainder) = bytes.as_chunks::<4>(); - debug_assert!(remainder.is_empty()); - arrays - .iter() - .map(|&arr| { - context.new_rvalue_from_int( - element_type, - match endian { - rustc_abi::Endian::Little => u32::from_le_bytes(arr) as i32, - rustc_abi::Endian::Big => u32::from_be_bytes(arr) as i32, - }, - ) - }) - .collect() - } - _ => bytes + // Raw bytes take far less RAM in libgccjit than one rvalue per array element. + #[cfg(feature = "master")] + { + cx.context.new_array_from_bytes(None, typ, bytes) + } + #[cfg(not(feature = "master"))] + { + let byte_type = cx.type_u8(); + let elements: Vec<_> = bytes .iter() - .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) - .collect(), - }; - context.new_array_constructor(None, typ, &elements) + .map(|&byte| cx.context.new_rvalue_from_int(byte_type, byte as i32)) + .collect(); + cx.context.new_array_constructor(None, typ, &elements) + } } pub fn type_is_pointer(typ: Type<'_>) -> bool { diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 902f8ba52faaa..752f04906a2c8 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -359,18 +359,30 @@ fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec next_offset { - fields.push(AllocField::Bytes { range: next_offset..offset }); + push_byte_runs(&mut fields, next_offset..offset); } fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - fields.push(AllocField::Bytes { range: next_offset..alloc.len() }); + push_byte_runs(&mut fields, next_offset..alloc.len()); } fields } +/// `gcc_jit_context_new_array_from_bytes` rejects runs of `i32::MAX` bytes or more. +const MAX_BYTE_RUN_LEN: usize = i32::MAX as usize - 1; + +fn push_byte_runs(fields: &mut Vec, range: Range) { + let mut start = range.start; + while range.end - start > MAX_BYTE_RUN_LEN { + fields.push(AllocField::Bytes { range: start..start + MAX_BYTE_RUN_LEN }); + start += MAX_BYTE_RUN_LEN; + } + fields.push(AllocField::Bytes { range: start..range.end }); +} + /// The type [`const_alloc_to_gcc`] gives `alloc`, computed without building any rvalue. /// /// This lets `predefine_static` declare a static's global with the type its initializer will have, diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index b57e0fb2cd23a..3cb12a4c2e56c 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -97,11 +97,11 @@ pub struct CodegenCx<'gcc, 'tcx> { /// Cache of the anonymous struct types. pub struct_types: RefCell, Type<'gcc>>>, - /// Cache of the array types used for runs of constant bytes, keyed by element type and count. + /// Cache of the array types used for runs of constant bytes, keyed by length. /// /// libgccjit mints a fresh type on every `new_array_type`, and struct types are keyed on their /// field types, so without this two equal byte runs would yield two distinct anonymous structs. - pub byte_array_types: RefCell, u64), Type<'gcc>>>, + pub byte_array_types: RefCell>>, /// Cache of the return types of the pairs returned in two registers, keyed by the types of the /// two scalars. diff --git a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs index b26387ecacdb6..2ca3f4c2fec36 100644 --- a/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs +++ b/compiler/rustc_codegen_gcc/tests/compile/large_byte_constant.rs @@ -1,8 +1,8 @@ -//@ memory-limit: 2G +//@ memory-limit: 1G // Compiler: -// An 8 MiB constant must compile within 2 GiB of address space. A backend that builds one rvalue -// per byte of a constant needs more than 6 GiB for it and runs out of memory. +// An 8 MiB constant must compile within 1 GiB of address space. A backend that builds one rvalue +// per 8 bytes of a constant needs about 1.4 GiB for it and runs out of memory. #![crate_type = "lib"] From ff7732fedf38adede1463c62c26c11194f83b5ba Mon Sep 17 00:00:00 2001 From: Ohad Ravid Date: Tue, 20 Oct 2026 04:44:00 -0700 Subject: [PATCH 27/80] FLS dtors registered in a dtor might get called --- src/tools/miri/tests/pass/tls/windows-tls.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/tests/pass/tls/windows-tls.rs b/src/tools/miri/tests/pass/tls/windows-tls.rs index b29035a7e75cf..13e479d2d0fd6 100644 --- a/src/tools/miri/tests/pass/tls/windows-tls.rs +++ b/src/tools/miri/tests/pass/tls/windows-tls.rs @@ -1,7 +1,6 @@ //@only-target: windows # this directly tests windows-only functions //@revisions: with_exit without_exit in_thread -// FIXME: run-native explodes for the other revisions. (https://github.com/rust-lang/miri/issues/5444) -//@[without_exit] run-native +//@run-native use std::ffi::c_void; use std::ptr; @@ -97,9 +96,12 @@ fn fls_2_dtor_update_value_ignored() { assert_eq!(unsafe { FlsSetValue(early_key, ptr::without_provenance_mut(1)) }, TRUE); - // Registering new fls slots doesn't cause their dtor to run. - let a_new_key_in_dtor = unsafe { FlsAlloc(Some(dtor_unreachable)) }; + // Registering new fls slots in a dtor is always possible, + // but their dtor might not get called. + extern "system" fn new_in_dtor(_val: *mut c_void) {} + let a_new_key_in_dtor = unsafe { FlsAlloc(Some(new_in_dtor)) }; assert_eq!(unsafe { FlsSetValue(a_new_key_in_dtor, ptr::without_provenance_mut(1)) }, TRUE); + assert_eq!(unsafe { FlsGetValue(a_new_key_in_dtor).addr() }, 1); } let early_key = unsafe { FlsAlloc(Some(early_dtor)) }; From a4caed60ebb0694efb6e3ae20db37b6f0242ca02 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 20 Oct 2026 22:36:38 -0700 Subject: [PATCH 28/80] unix shims: migrate to check_shim_sig --- .../miri/src/shims/unix/foreign_items.rs | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index ed70187685321..6cc88b011645d 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -2,7 +2,7 @@ use std::ffi::OsStr; use std::str; use std::time::Duration; -use rustc_abi::{CanonAbi, Size}; +use rustc_abi::Size; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -1330,8 +1330,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_getguardsize" if this.frame_in_std() => { - let [_attr, guard_size] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_attr, guard_size] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; let guard_size_layout = this.machine.layouts.usize; let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?; this.write_scalar( @@ -1344,19 +1346,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => { - let [_] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; this.write_null(dest)?; } + "pthread_attr_setstacksize" if this.frame_in_std() => { - let [_, _] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, usize) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "pthread_attr_getstack" if this.frame_in_std() => { // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here. // Hence we can mostly ignore the input `attr_place`. - let [attr_place, addr_place, size_place] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [attr_place, addr_place, size_place] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_, *_) -> i32), + (link_name, abi, args), + )?; let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?; @@ -1375,20 +1384,34 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - "signal" | "sigaltstack" if this.frame_in_std() => { - let [_, _] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + "signal" if this.frame_in_std() => { + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, libc::sighandler_t) -> libc::sighandler_t), + (link_name, abi, args), + )?; + this.write_null(dest)?; + } + "sigaltstack" if this.frame_in_std() => { + let [_, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "sigaction" if this.frame_in_std() => { - let [_, _, _] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_, _, _] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish - let [uid, pwd, buf, buflen, result] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [uid, pwd, buf, buflen, result] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::uid_t, *_, *_, usize, *_) -> i32), + (link_name, abi, args), + )?; this.check_no_isolation("`getpwuid_r`")?; let uid = this.read_scalar(uid)?.to_u32()?; From c8654736a850d08737a8c9b3014895ff308b565c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 21 Oct 2026 16:18:48 +0200 Subject: [PATCH 29/80] fix comment in GetConsoleScreenBufferInfo --- src/tools/miri/src/shims/windows/foreign_items.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 13eb213802294..779188b0494af 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -1047,10 +1047,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { (link_name, abi, args), )?; this.read_target_isize(console)?; - // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std - this.deref_pointer(buffer_info)?; + this.read_pointer(buffer_info)?; // Indicate an error. - // FIXME: we should set last_error, but to what? + this.set_last_error(io::ErrorKind::Unsupported)?; this.write_null(dest)?; } "GetStdHandle" => { From 9eb9a688b34f7ec2e49b8cd8b7a29b3b6f9ecfc8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 21 Oct 2026 16:18:55 +0200 Subject: [PATCH 30/80] update shim example --- src/tools/miri/src/shims/foreign_items.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 95e8e5d04a752..9f58c0c36a6f2 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -278,9 +278,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // When adding a new shim, you should follow the following pattern: // ``` // "shim_name" => { - // let [arg1, arg2, arg3] = this.check_shim(abi, CanonAbi::C , link_name, args)?; - // let result = this.shim_name(arg1, arg2, arg3)?; - // this.write_scalar(result, dest)?; + // let [arg1, arg2, arg3] = &this.check_shim_sig( + // shim_sig!(extern "C" fn(usize, *libc::some_type, *i32) -> u32), + // (link_name, abi, args), + // )?; + // this.shim_name(arg1, arg2, arg3, dest)?; // } // ``` // and then define `shim_name` as a helper function in an extension trait in a suitable file @@ -291,19 +293,19 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // arg1: &OpTy<'tcx>, // arg2: &OpTy<'tcx>, // arg3: &OpTy<'tcx>, - // arg4: &OpTy<'tcx>) + // dest: &MPlaceTy<'tcx>) // -> InterpResult<'tcx, Scalar> { // let this = self.eval_context_mut(); // // // First thing: load all the arguments. Details depend on the shim. // let arg1 = this.read_scalar(arg1)?.to_u32()?; // let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly - // // when you want to load/store through the pointer - // let arg3 = this.deref_pointer(arg3)?; + // let arg3 = this.deref_pointer(arg3)?; // when you want to load/store through the pointer // // // ... // - // interp_ok(Scalar::from_u32(42)) + // this.write_int(42, dest)?; + // interp_ok(()) // } // ``` // You might find existing shims not following this pattern, most From a14fcb3474ad48639a2569f0cb28bd9bad9521b5 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 11 Oct 2026 13:32:35 +0300 Subject: [PATCH 31/80] [Miri] Build Priroda from the main package Make Priroda an optional binary and integration-test target of the Miri package. This shares the lockfile and build artifacts while keeping Priroda out of regular Miri builds. --- src/tools/miri/Cargo.lock | 12 + src/tools/miri/Cargo.toml | 17 + src/tools/miri/miri-script/src/util.rs | 2 + src/tools/miri/priroda/Cargo.lock | 1553 -------------------- src/tools/miri/priroda/Cargo.toml | 38 - src/tools/miri/priroda/rust-toolchain.toml | 2 - src/tools/miri/priroda/tests/cli.rs | 24 +- 7 files changed, 51 insertions(+), 1597 deletions(-) delete mode 100644 src/tools/miri/priroda/Cargo.lock delete mode 100644 src/tools/miri/priroda/Cargo.toml delete mode 100644 src/tools/miri/priroda/rust-toolchain.toml diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 5037ac5b2c217..86e1a0cd13fd5 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -472,6 +472,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -993,6 +1004,7 @@ dependencies = [ "chrono-tz", "colored", "directories", + "emmy_dap_types", "genmc-sys", "getrandom 0.4.3", "ipc-channel", diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 34a7a8789e4de..4d0180cfa1939 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -16,6 +16,13 @@ name = "miri" test = false # we have no unit tests doctest = false # and no doc tests +[[bin]] +name = "priroda" +path = "priroda/src/main.rs" +test = false +doctest = false +required-features = ["priroda"] + [dependencies] getrandom = { version = "0.4", features = ["std"] } rand = "0.10" @@ -30,6 +37,9 @@ serde_json = { version = "1.0", optional = true } mio = { version = "1.1.1", features = ["os-poll", "net"] } socket2 = "0.6.5" +# Only used by priroda binary. +emmy_dap_types = { version = "0.2.0", optional = true } + [target.'cfg(unix)'.dependencies] libc = "0.2" # native-lib dependencies @@ -69,12 +79,19 @@ rustc_private = true name = "ui" harness = false +[[test]] +name = "priroda_cli" +path = "priroda/tests/cli.rs" +harness = false +required-features = ["priroda"] + [features] default = ["stack-cache", "native-lib"] genmc = ["dep:genmc-sys"] stack-cache = [] expensive-consistency-checks = ["stack-cache"] tracing = ["serde_json"] +priroda = ["dep:emmy_dap_types"] native-lib = ["dep:libffi", "dep:libffi-sys", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"] check_only = ["libffi?/check_only", "capstone?/check_only", "genmc-sys?/check_only"] diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index 01743c79f140b..391ff2bdcc974 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -203,6 +203,8 @@ impl MiriEnv { let json: serde_json::Value = serde_json::from_str(&line)?; if json["reason"] == "compiler-artifact" && !json["profile"]["test"].as_bool().unwrap() + // The "miri" crate ships a second binary ("priroda"); pick the "miri" driver. + && json["target"]["name"] == "miri" && !json["executable"].is_null() { if bin.is_some() { diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock deleted file mode 100644 index a24d764a39d3a..0000000000000 --- a/src/tools/miri/priroda/Cargo.lock +++ /dev/null @@ -1,1553 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aes" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" -dependencies = [ - "cipher", - "cpubits", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "annotate-snippets" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" -dependencies = [ - "anstyle", - "unicode-width", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - -[[package]] -name = "bstr" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "camino" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" -dependencies = [ - "serde_core", -] - -[[package]] -name = "capstone" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" -dependencies = [ - "capstone-sys", - "static_assertions", -] - -[[package]] -name = "capstone-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" -dependencies = [ - "cc", -] - -[[package]] -name = "cargo-platform" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "cc" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "num-traits", -] - -[[package]] -name = "chrono-tz" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" -dependencies = [ - "chrono", - "phf", -] - -[[package]] -name = "cipher" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.21", -] - -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "colored" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "comma" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" - -[[package]] -name = "console" -version = "0.16.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" -dependencies = [ - "encode_unicode", - "libc", - "unicode-width", - "windows-sys", -] - -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "emmy_dap_types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "eyre" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" -dependencies = [ - "autocfg", - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "typenum", -] - -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - -[[package]] -name = "indicatif" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" -dependencies = [ - "console", - "portable-atomic", - "unicode-width", - "unit-prefix", - "web-time", -] - -[[package]] -name = "inout" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ipc-channel" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347ef783e380784658248bdb0b87ca1293e3e3df3fc7dee061e129aa5f013d61" -dependencies = [ - "crossbeam-channel", - "libc", - "mio", - "postcard", - "rand 0.9.5", - "rustc-hash 2.1.3", - "serde_core", - "tempfile", - "thiserror 2.0.21", - "uuid", - "windows", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" - -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libffi" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" -dependencies = [ - "libc", - "libffi-sys", -] - -[[package]] -name = "libffi-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" -dependencies = [ - "cc", -] - -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - -[[package]] -name = "libredox" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" -dependencies = [ - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "measureme" -version = "12.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebd1ebda747ae161a4a377bf93f87e18d46faad2331cc0c7d25b84b1d445f49" -dependencies = [ - "log", - "memmap2", - "parking_lot", - "perf-event-open-sys", - "rustc-hash 1.1.0", - "smallvec", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "miri" -version = "0.1.0" -dependencies = [ - "aes", - "bitflags", - "capstone", - "chrono", - "chrono-tz", - "directories", - "getrandom 0.4.3", - "ipc-channel", - "libc", - "libffi", - "libffi-sys", - "libloading", - "measureme", - "mio", - "nix", - "rand 0.10.3", - "serde", - "smallvec", - "socket2", - "windows-sys", -] - -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "owo-colors" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "perf-event-open-sys" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b29be2ba35c12c6939f6bc73187f728bba82c3c062ecdc5fa90ea739282a1f58" -dependencies = [ - "libc", -] - -[[package]] -name = "phf" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettydiff" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" -dependencies = [ - "owo-colors", -] - -[[package]] -name = "priroda" -version = "0.1.0" -dependencies = [ - "emmy_dap_types", - "miri", - "regex", - "ui_test", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" -dependencies = [ - "libredox", - "thiserror 2.0.21", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "rustc-demangle" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustfix" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "siphasher" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "spanned" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" -dependencies = [ - "anyhow", - "bstr", - "color-eyre", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl 2.0.21", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "ui_test" -version = "0.30.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8811281d587a786747c0c49245925016c07767bc996305bdd34d5ce076786a" -dependencies = [ - "annotate-snippets", - "anyhow", - "bstr", - "cargo-platform", - "cargo_metadata", - "color-eyre", - "colored", - "comma", - "crossbeam-channel", - "indicatif", - "levenshtein", - "prettydiff", - "regex", - "rustc_version", - "rustfix", - "serde", - "serde_json", - "spanned", -] - -[[package]] -name = "unicode-ident" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unit-prefix" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" - -[[package]] -name = "uuid" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "zerocopy" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml deleted file mode 100644 index 48a20fecb0ba8..0000000000000 --- a/src/tools/miri/priroda/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -description = "Debugger for Rust MIR." -license = "MIT OR Apache-2.0" -name = "priroda" -repository = "https://github.com/rust-lang/miri" -version = "0.1.0" -edition = "2024" - -[workspace] - -[[bin]] -name = "priroda" -path = "src/main.rs" -test = false # Disable unit tests to avoid errors with --bless -doctest = false # and no doc tests - -[[test]] -name = "cli" -harness = false - -[dependencies] -emmy_dap_types = "0.2.0" -miri = { path = ".." } - -[package.metadata.rust-analyzer] -rustc_private = true - -# Same lint policy as miri/src/lib.rs. -[lints.rust] -rust_2018_idioms = "warn" - -[lints.clippy] -as_conversions = "warn" -manual_let_else = "warn" - -[dev-dependencies] -ui_test = "0.30.2" -regex = "1.5.5" diff --git a/src/tools/miri/priroda/rust-toolchain.toml b/src/tools/miri/priroda/rust-toolchain.toml deleted file mode 100644 index c5d0b97ed4f01..0000000000000 --- a/src/tools/miri/priroda/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "miri" diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs index 2bf7f22bd1d98..83e93b529595f 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/cli.rs @@ -8,8 +8,24 @@ use ui_test::status_emitter::StatusEmitter; use ui_test::{CommandBuilder, Config, default_file_filter, run_tests_generic}; fn main() -> Result<(), Box> { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let miri_dir = manifest_dir.parent().unwrap(); + // `priroda` is a target of the `miri` package, so `CARGO_MANIFEST_DIR` is the Miri repo root. + let miri_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let priroda_dir = miri_dir.join("priroda"); + + // The fixtures assume a host build of Miri, so skip them under cross-target bootstrap runs. + // The bootstrap Priroda test step sets `MIRI_TEST_TARGET`. + if let Some(miri_test_target) = env::var_os("MIRI_TEST_TARGET") { + let host_target = Command::new("rustc").arg("--print").arg("host-tuple").output()?; + let host_target = String::from_utf8(host_target.stdout)?.trim().to_owned(); + if miri_test_target.to_str() != Some(&host_target) { + println!("skipping Priroda tests for non-host target {miri_test_target:?}"); + return Ok(()); + } + } + + // The test fixtures use paths relative to the Priroda directory; run the harness from there + // so the relative `tests/ui/...` paths resolve in the same way as a standalone crate. + std::env::set_current_dir(&priroda_dir)?; let rustc_sysroot = Command::new("rustc").arg("--print").arg("sysroot").output()?; let rustc_sysroot = String::from_utf8(rustc_sysroot.stdout)?.trim().to_owned(); @@ -29,7 +45,7 @@ fn main() -> Result<(), Box> { // Replace the dynamic paths in the actual stdout with the stable placeholders let manifest_dir_regex = - Regex::new(®ex::escape(&manifest_dir.display().to_string())).unwrap(); + Regex::new(®ex::escape(&priroda_dir.display().to_string())).unwrap(); let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); @@ -49,7 +65,7 @@ fn main() -> Result<(), Box> { (content_length_regex.into(), b"Content-Length: {CONTENT_LENGTH}".to_vec()), ]); - // Priroda CLI tests do not currently require annotation comments in the test files + // Priroda tests do not currently require annotation comments in the test files. config.comment_defaults.base().exit_status = Spanned::dummy(0).into(); config.comment_defaults.base().require_annotations = Spanned::dummy(false).into(); From 8b6b82f63dc64769eea3ec89d5801bca35d68591 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 11 Oct 2026 13:32:35 +0300 Subject: [PATCH 32/80] [Priroda] Fix rustc-internal lint warnings Use rustc data structures and current type-pattern syntax now that Priroda inherits Miri's rustc-internal lint policy. --- src/tools/miri/priroda/src/debugger.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 73fa355e164eb..cd403844b2469 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -1,4 +1,3 @@ -use std::collections::{HashMap, HashSet}; use std::ops::Range; use std::path::PathBuf; @@ -6,10 +5,11 @@ use miri::Immediate::Uninit; use miri::{InterpErrorInfo, InterpErrorKind, TerminationInfo, *}; use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; use rustc_data_structures::either::Either; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::CtorKind; use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; -use rustc_middle::ty::{self, TyKind}; +use rustc_middle::ty; use rustc_span::source_map::SourceMap; use rustc_span::{Span, Symbol}; @@ -30,7 +30,7 @@ impl SourceLocation { } /// Source-level breakpoints indexed by normalized path, then line. -type BreakpointTable = HashMap>; +type BreakpointTable = FxHashMap>; /// Owns one interpreter session and its debugger state. /// @@ -161,7 +161,7 @@ impl<'tcx> PrirodaContext<'tcx> { pub(super) fn new(ecx: MiriInterpCx<'tcx>) -> Self { Self { ecx, - breakpoints: HashMap::new(), + breakpoints: FxHashMap::default(), current_location: None, last_location: None, exit_code: None, @@ -827,14 +827,13 @@ impl<'tcx> PrirodaContext<'tcx> { match elem { ProjectionElem::Field(field_idx, field_ty) => { let rendered = match projection_ty.kind() { - TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { + ty::Adt(adt_def, _args) if adt_def.is_struct() => { let variant = adt_def.non_enum_variant(); let field = &variant.fields[*field_idx]; Symbol::intern(&format!(".{}", field.name)) } - TyKind::Tuple(_) => - Symbol::intern(&format!(".{}", field_idx.index())), + ty::Tuple(_) => Symbol::intern(&format!(".{}", field_idx.index())), _ => Symbol::intern("."), }; From bb2e56e8145720e272b32f8e54b044a53473b862 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Mon, 19 Oct 2026 16:13:08 +0300 Subject: [PATCH 33/80] [Priroda] Rename the UI test harness Use a neutral test target and filename because the harness discovers both CLI and DAP fixtures. --- src/tools/miri/Cargo.toml | 4 ++-- src/tools/miri/priroda/tests/{cli.rs => ui.rs} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/tools/miri/priroda/tests/{cli.rs => ui.rs} (97%) diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 4d0180cfa1939..7c0c196c33eac 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -80,8 +80,8 @@ name = "ui" harness = false [[test]] -name = "priroda_cli" -path = "priroda/tests/cli.rs" +name = "priroda" +path = "priroda/tests/ui.rs" harness = false required-features = ["priroda"] diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/ui.rs similarity index 97% rename from src/tools/miri/priroda/tests/cli.rs rename to src/tools/miri/priroda/tests/ui.rs index 83e93b529595f..41c98300d8ebb 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/ui.rs @@ -65,7 +65,7 @@ fn main() -> Result<(), Box> { (content_length_regex.into(), b"Content-Length: {CONTENT_LENGTH}".to_vec()), ]); - // Priroda tests do not currently require annotation comments in the test files. + // Priroda UI tests do not currently require annotation comments in the test files. config.comment_defaults.base().exit_status = Spanned::dummy(0).into(); config.comment_defaults.base().require_annotations = Spanned::dummy(false).into(); From 7a039bd65e4ac9684f8dc434b483c10b684e5cce Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Mon, 19 Oct 2026 16:13:08 +0300 Subject: [PATCH 34/80] [Priroda] Update the VS Code task for the Miri package Build Priroda from the root Miri manifest with the priroda feature enabled. --- src/tools/miri/priroda/vscode_tasks.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/priroda/vscode_tasks.json b/src/tools/miri/priroda/vscode_tasks.json index 9663bb3169bae..0ed7d9ee15664 100644 --- a/src/tools/miri/priroda/vscode_tasks.json +++ b/src/tools/miri/priroda/vscode_tasks.json @@ -8,7 +8,11 @@ "args": [ "run", "--manifest-path", - "${workspaceFolder}/Cargo.toml", + "${workspaceFolder}/../Cargo.toml", + "--bin", + "priroda", + "--features", + "priroda", "--", "--dap", "--port", From c5b2fabdf04c796016f753c5de988ee1c939263b Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 11 Oct 2026 13:32:36 +0300 Subject: [PATCH 35/80] [Miri] Add ./miri test-priroda Prepare the Miri sysroot internally and run the complete Priroda UI test suite. Support --bless without requiring ./miri install or a user-provided MIRI_SYSROOT. --- src/tools/miri/miri-script/src/commands.rs | 26 ++++++++++++++++++++++ src/tools/miri/miri-script/src/main.rs | 13 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index 90fdef1da949a..9026e1f9d0a5a 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -96,6 +96,7 @@ impl Command { | Command::Build { .. } | Command::Check { .. } | Command::Test { .. } + | Command::TestPriroda { .. } | Command::Run { .. } | Command::Fmt { .. } | Command::Doc { .. } @@ -109,6 +110,7 @@ impl Command { Command::Check { features, flags } => Self::check(features, flags), Command::Test { bless, target, coverage, features, flags } => Self::test(bless, target, coverage, features, flags), + Command::TestPriroda { bless, flags } => Self::test_priroda(bless, flags), Command::Run { dep, native, quiet, target, edition, features, flags } => Self::run(dep, native, quiet, target, edition, features, flags), Command::Doc { features, flags } => Self::doc(features, flags), @@ -479,6 +481,30 @@ impl Command { Ok(()) } + fn test_priroda(bless: bool, flags: Vec) -> Result<()> { + let mut e = MiriEnv::new()?; + + // Build cargo-miri and the local sysroot. + let features = ["priroda".to_owned()]; + let quiet = false; + e.build_miri_sysroot(quiet, None::<&str>, &features)?; + + // Forward information to test harness. + if bless { + e.sh.set_var("RUSTC_BLESS", "Gesundheit"); + } + + // `priroda` is a bin/test target of the `miri` package, so test it there. + let mut args = vec!["--test".to_owned(), "priroda".to_owned()]; + // Pass flags as well. Make sure they are going to the test harness, not cargo. + args.push("--".to_owned()); + args.extend(flags); + + e.test(".", &features, &args)?; + + Ok(()) + } + fn run( dep: bool, native: bool, diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs index bcad8f148d23f..ee085d9c703fb 100644 --- a/src/tools/miri/miri-script/src/main.rs +++ b/src/tools/miri/miri-script/src/main.rs @@ -72,6 +72,15 @@ pub enum Command { #[arg(trailing_var_arg = true, allow_hyphen_values = true)] flags: Vec, }, + /// Run the Priroda test suite. + TestPriroda { + /// Update stdout reference files. + #[arg(long)] + bless: bool, + /// Flags that are passed through to the test harness. + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + flags: Vec, + }, /// Run the Miri driver. /// /// Also respects MIRIFLAGS environment variable. @@ -177,6 +186,10 @@ impl Command { flags.extend(remainder); Ok(()) } + Self::TestPriroda { flags, .. } => { + flags.extend(remainder); + Ok(()) + } Self::Bench { .. } | Self::Squash => bail!("unexpected \"--\" found in arguments"), } } From 8000713cda5bef75d0df9a18a52ca6b04110d851 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 11 Oct 2026 13:32:36 +0300 Subject: [PATCH 36/80] [Miri] Run the Priroda suite in CI Run Priroda clippy and UI tests through ./miri. Temporarily skip its bootstrap check while the Miri package is reorganized. --- src/tools/miri/.github/workflows/ci.yml | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 21ce9f16021b8..dd6d684dd5959 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -183,30 +183,23 @@ jobs: - name: check build run: | cd ../rust # ./x does not seem to like being invoked from elsewhere - # checks every tool in that folder (including priroda) - ./x check src/tools/miri + # FIXME: skipping the priroda check as it is temporarily broken while + # we reorganize the Miri crate. + ./x check miri cargo-miri - # This job is intentionally separate from `test` so that Priroda can be - # developed as a separate crate inside the Miri repository for now. + # This job is intentionally separate from `test` so that the Priroda test + # suite is exercised on its own (it is gated behind the `priroda` feature of + # the `miri` crate, and so is not part of the regular `./miri test` run). priroda: - name: Priroda + name: priroda runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ./.github/workflows/setup - - name: install Miri driver - run: ./miri install - - name: build Priroda - working-directory: priroda - run: cargo build --locked - name: clippy Priroda - working-directory: priroda - run: cargo clippy --all-targets --locked -- -D warnings + run: CARGO_EXTRA_FLAGS=--locked ./miri clippy --features priroda -- -D warnings - name: test Priroda - working-directory: priroda - run: | - sysroot="$(cargo miri setup --print-sysroot)" - MIRI_SYSROOT="$sysroot" cargo test --locked + run: CARGO_EXTRA_FLAGS=--locked ./miri test-priroda coverage: name: coverage report From a51d3d5efafcf5dc580c8bffb1f5f53e5035cb59 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 11 Oct 2026 13:32:36 +0300 Subject: [PATCH 37/80] [Miri] Document the Priroda test workflow Document testing, blessing, installation, and manual execution after moving Priroda into the main Miri package. --- src/tools/miri/CONTRIBUTING.md | 4 ++++ src/tools/miri/priroda/README.md | 41 ++++++++++++++------------------ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md index 4bc02217c6f7b..af4d1b32f63e7 100644 --- a/src/tools/miri/CONTRIBUTING.md +++ b/src/tools/miri/CONTRIBUTING.md @@ -187,6 +187,10 @@ You can (cross-)run the entire test suite using: base directory, e.g. `./miri test fail` will run all compile-fail tests). Multiple filters are supported: `./miri test FILTER1 FILTER2` runs all tests that contain either string. +Priroda has a separate test suite. Run it with `./miri test-priroda`, or use +`./miri test-priroda --bless` to update its expected output files after an +intentional output change. + #### Fine grained logging You can get a trace of which MIR statements are being executed by setting the diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index 25ec34dce64d8..30fec23f430a7 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -14,28 +14,28 @@ Current focus: ## Setup -From `miri/`, install the pinned toolchain and the local `cargo-miri` -command: +From `miri/`, install the pinned toolchain: ```sh ./miri toolchain -./miri install ``` -Then build the Miri sysroot and export it for Priroda: +This is sufficient for running the [tests](#test) with `./miri test-priroda`, +which builds the driver and prepares the sysroot itself. To run Priroda +manually, install it together with Miri and prepare the local Miri sysroot: ```sh -cargo +miri miri setup +./miri install --features priroda export MIRI_SYSROOT="$(cargo +miri miri setup --print-sysroot)" ``` ## Run -Priroda currently reads `MIRI_SYSROOT` directly. After setup, run Priroda -from `miri/priroda/`: +Priroda currently reads `MIRI_SYSROOT` directly. After setup, run the installed +binary from the miri toolchain: ```sh -cargo run -- ../tests/pass/empty_main.rs +rustup run miri priroda tests/pass/empty_main.rs ``` ## DAP Prototype @@ -93,14 +93,15 @@ Before running the debugger configuration, make sure: it in your shell before launching VS Code, or replace the argument with the absolute sysroot path. - The `cargo` in `command` resolves to the `miri` toolchain's cargo, so the task - builds Priroda with `rustc_private`. That is automatic when the workspace is - `miri/priroda`; when using the templates from another project, pass `+miri` as - the first `cargo` argument. + builds Priroda with `rustc_private`. When using the templates outside a Miri + checkout with its toolchain override, pass `+miri` as the first `cargo` + argument. -The task runs Priroda through `cargo run` against the Priroda crate: +The task runs the Priroda binary target from the main Miri package: ```sh -cargo run --manifest-path /path/to/miri/priroda/Cargo.toml -- \ +cargo run --manifest-path /path/to/miri/Cargo.toml \ + --bin priroda --features priroda -- \ --dap --port 4711 --sysroot "$MIRI_SYSROOT" /path/to/project/src/main.rs ``` @@ -133,22 +134,16 @@ skips unrecognized requests rather than failing, so those are ignored. ## Test -Priroda's CLI tests also need `MIRI_SYSROOT`. Run them from `miri/priroda/`: +Run Priroda's tests from the Miri repository root: ```sh -cargo test +./miri test-priroda ``` -If the CLI tests fail due to mismatched output, you can update the expected output files by running the tests with the `--bless` flag: +To update the expected output files after an intentional output change, run: ```sh -cargo test -- --bless -``` - -or - -```sh -RUSTC_BLESS=1 cargo test +./miri test-priroda --bless ``` ## Commands From d0bbba66a802cb8394949950e79635bfde3b8b14 Mon Sep 17 00:00:00 2001 From: Mehmet Toplu <262413094+mehmettoplu-rs@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:52:45 +0300 Subject: [PATCH 38/80] freebsd shims: migrate to check_shim_sig --- .../src/shims/unix/freebsd/foreign_items.rs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index b2a101a0938b2..d4e66f5a5d08e 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -24,8 +23,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; let max_len = u64::MAX; // FreeBSD does not seem to have a limit. let res = match this.pthread_setname_np( this.read_scalar(thread)?, @@ -39,8 +40,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value, // but always adds a null terminator (except for zero-sized buffers). // https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144 @@ -57,15 +60,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getthreadid_np" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?; let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } "cpuset_getaffinity" => { // The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically. - let [level, which, id, set_size, mask] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [level, which, id, set_size, mask] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i64, usize, *_) -> i32), + (link_name, abi, args), + )?; let level = this.read_scalar(level)?.to_i32()?; let which = this.read_scalar(which)?.to_i32()?; @@ -136,14 +142,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "_umtx_op" => { - let [obj, op, val, uaddr, uaddr2] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [obj, op, val, uaddr, uaddr2] = &this.check_shim_sig( + shim_sig!(extern "C" fn(*_, i32, usize, *_, *_) -> i32), + (link_name, abi, args), + )?; this._umtx_op(obj, op, val, uaddr, uaddr2, dest)?; } // Miscellaneous "__error" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } @@ -162,8 +171,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_get_np" if this.frame_in_std() => { - let [_thread, _attr] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [_thread, _attr] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; this.write_null(dest)?; } From 264347bedc69255663b58b82160595013e0f8853 Mon Sep 17 00:00:00 2001 From: Jose Torres Date: Wed, 21 Oct 2026 22:16:59 -0400 Subject: [PATCH 39/80] resolving old FIXME in random --- src/tools/miri/tests/pass-dep/libc/random.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/random.rs b/src/tools/miri/tests/pass-dep/libc/random.rs index 7c4010f6c0ac6..135eafa1e01ef 100644 --- a/src/tools/miri/tests/pass-dep/libc/random.rs +++ b/src/tools/miri/tests/pass-dep/libc/random.rs @@ -67,11 +67,7 @@ fn test_getrandom() { #[cfg(any(target_os = "freebsd", target_os = "illumos", target_os = "solaris"))] fn test_arc4random_buf() { - // FIXME: Use declaration from libc once lands. - extern "C" { - fn arc4random_buf(buf: *mut libc::c_void, size: libc::size_t); - } let mut buf = [0u8; 5]; - unsafe { arc4random_buf(buf.as_mut_ptr() as _, buf.len()) }; + unsafe { libc::arc4random_buf(buf.as_mut_ptr() as _, buf.len()) }; assert!(buf != [0u8; 5]); } From f862e9fa7ba6cb6832e48d4241adb796bf8ef023 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Wed, 21 Oct 2026 19:38:53 -0700 Subject: [PATCH 40/80] solarish shims: migrate to check_shim_sig --- .../src/shims/unix/solarish/foreign_items.rs | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 35c9144235b6c..2e7c6a83b4d81 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -1,4 +1,3 @@ -use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -28,35 +27,44 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // epoll, eventfd (NOT available on Solaris!) "epoll_create1" => { this.assert_target_os(Os::Illumos, "epoll_create1"); - let [flag] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [flag] = &this + .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { this.assert_target_os(Os::Illumos, "epoll_ctl"); - let [epfd, op, fd, event] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, op, fd, event] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { this.assert_target_os(Os::Illumos, "epoll_wait"); - let [epfd, events, maxevents, timeout] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [epfd, events, maxevents, timeout] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, i32, i32) -> i32), + (link_name, abi, args), + )?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { this.assert_target_os(Os::Illumos, "eventfd"); - let [val, flag] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val, flag] = &this.check_shim_sig( + shim_sig!(extern "C" fn(u32, i32) -> i32), + (link_name, abi, args), + )?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32), + (link_name, abi, args), + )?; // THREAD_NAME_MAX allows a thread name of 31+1 length // https://github.com/illumos/illumos-gate/blob/7671517e13b8123748eda4ef1ee165c6d9dba7fe/usr/src/uts/common/sys/thread.h#L613 let max_len = 32; @@ -73,8 +81,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [thread, name, len] = &this.check_shim_sig( + shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32), + (link_name, abi, args), + )?; // See https://illumos.org/man/3C/pthread_getname_np for the error codes. let res = match this.pthread_getname_np( this.read_scalar(thread)?, @@ -88,26 +98,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } - // File related shims - "stat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; - let result = this.stat(path, buf)?; - this.write_scalar(result, dest)?; - } - "lstat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; - let result = this.lstat(path, buf)?; - this.write_scalar(result, dest)?; - } - // Sockets and pipes "__xnet_socketpair" => { - let [domain, type_, protocol, sv] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [domain, type_, protocol, sv] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, i32, i32, *_) -> i32), + (link_name, abi, args), + )?; let result = this.socketpair(domain, type_, protocol, sv)?; this.write_scalar(result, dest)?; } @@ -156,14 +152,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "___errno" => { - let [] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [] = &this + .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "stack_getbounds" => { // FIXME: This does not have a direct test (#3179). - let [stack] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [stack] = &this + .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?; let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?; this.write_int_fields_named( @@ -182,8 +180,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "pset_info" => { // FIXME: This does not have a direct test (#3179). - let [pset, tpe, cpus, list] = - this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [pset, tpe, cpus, list] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *_, *_, *_) -> i32), + (link_name, abi, args), + )?; // We do not need to handle the current process cpu mask, available_parallelism // implementation pass null anyway. We only care for the number of // cpus. @@ -212,7 +212,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "__sysconf_xpg7" => { - let [val] = this.check_shim_sig_deprecated(abi, CanonAbi::C, link_name, args)?; + let [val] = &this.check_shim_sig( + shim_sig!(extern "C" fn(i32) -> isize), + (link_name, abi, args), + )?; let result = this.sysconf(val)?; this.write_scalar(result, dest)?; } From d978f0be0e707731020928cd611842df3599753d Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Wed, 21 Oct 2026 21:21:35 -0700 Subject: [PATCH 41/80] NetBSD test filters no longer matched anything --- src/tools/miri/CONTRIBUTING.md | 2 +- src/tools/miri/ci/ci.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md index 4bc02217c6f7b..546a382660d57 100644 --- a/src/tools/miri/CONTRIBUTING.md +++ b/src/tools/miri/CONTRIBUTING.md @@ -173,7 +173,7 @@ For example, you can (cross-)run the driver on a particular file by doing Tests in ``pass-dep`` need to be run using ``./miri run --dep ``. For example: ```sh -./miri run --dep tests/pass-dep/shims/libc-fs.rs +./miri run --dep tests/pass-dep/libc/fs.rs ``` You can (cross-)run the entire test suite using: diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index c65d8097e0e92..e7b0fd652f01e 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -177,7 +177,7 @@ case $HOST_TARGET in MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests - MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-netbsd run_tests_minimal hello libc-env libc-misc + MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-netbsd run_tests_minimal hello libc/env.rs libc/misc.rs ;; armv7-unknown-linux-gnueabihf) # Host From 4a0caf71cc5add9100431093b752dd7e1d6f4dc4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 22 Oct 2026 04:31:53 +0000 Subject: [PATCH 42/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 1a2c4797ef2570bd76ca5dee34449169bc7dc457. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 60438fb681c3b..50040f5a20e87 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -8144389c895da997dbdecaa635e41d6a50be6ffd +1a2c4797ef2570bd76ca5dee34449169bc7dc457 From bbc40ba4d7b7d0eded443f7e85290e6df11e1d9d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 22 Oct 2026 10:58:45 +0200 Subject: [PATCH 43/80] get rid of check_shim_sig_deprecated --- src/tools/miri/src/shims/backtrace.rs | 27 +++++---------- src/tools/miri/src/shims/sig.rs | 34 +------------------ .../shims/backtrace/bad-backtrace-decl.rs | 16 --------- .../shims/backtrace/bad-backtrace-decl.stderr | 13 ------- .../fail/shims/backtrace/bad-backtrace-ptr.rs | 9 ----- .../shims/backtrace/bad-backtrace-ptr.stderr | 13 ------- .../backtrace/bad-backtrace-resolve-flags.rs | 22 +++--------- .../bad-backtrace-resolve-flags.stderr | 4 +-- .../tests/pass/backtrace/backtrace-api-v1.rs | 32 +++++------------ src/tools/miri/tests/utils/miri_extern.rs | 6 ++-- 10 files changed, 29 insertions(+), 147 deletions(-) delete mode 100644 src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs delete mode 100644 src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr delete mode 100644 src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs delete mode 100644 src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 774f7f1e8f953..b96acb8ecf8c0 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -1,4 +1,4 @@ -use rustc_abi::{CanonAbi, FieldIdx, Size}; +use rustc_abi::{FieldIdx, Size}; use rustc_middle::ty::{self, Instance, Ty}; use rustc_span::{BytePos, Loc, Symbol, hygiene}; use rustc_target::callconv::FnAbi; @@ -116,7 +116,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [ptr, flags] = this.check_shim_sig_deprecated(abi, CanonAbi::Rust, link_name, args)?; + + // FIXME: not nice to read the type from std but we don't have a good way to construct the + // type here and now. ADTs need a DefId. + let [ptr, flags] = &this.check_shim_sig( + shim_sig!(extern "Rust" fn(*_, u64) -> std::backtrace_rs::backtrace::miri::MiriFrame), + (link_name, abi, args), + )?; let flags = this.read_scalar(flags)?.to_u64()?; @@ -126,16 +132,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // which we pass to user code. let fn_ptr = this.fn_ptr(FnVal::Instance(fn_instance)); - let num_fields = dest.layout.fields.count(); - - if !(4..=5).contains(&num_fields) { - // Always mention 5 fields, since the 4-field struct - // is deprecated and slated for removal. - throw_ub_format!( - "bad declaration of miri_resolve_frame - should return a struct with 5 fields" - ); - } - // `u32` is not enough to fit line/colno, which can be `usize`. It seems unlikely that a // file would have more than 2^32 lines or columns, but whatever, just default to 0. let lineno: u32 = u32::try_from(lo.line).unwrap_or(0); @@ -175,12 +171,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Scalar::from_u32(colno), &this.project_field(dest, FieldIdx::from_u32(3))?, )?; - - // Support a 4-field struct for now - this is deprecated - // and slated for removal. - if num_fields == 5 { - this.write_pointer(fn_ptr, &this.project_field(dest, FieldIdx::from_u32(4))?)?; - } + this.write_pointer(fn_ptr, &this.project_field(dest, FieldIdx::from_u32(4))?)?; interp_ok(()) } diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 7d0a995a64797..c62926369e9dd 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -1,6 +1,6 @@ //! Everything related to checking the signature of shim invocations. -use rustc_abi::{CanonAbi, ExternAbi}; +use rustc_abi::ExternAbi; use rustc_middle::ty::{Binder, FnSig, FnSigKind, Ty}; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; @@ -296,38 +296,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - /// 'Lenient' signature check. Deprecated; use `check_shim_sig` instead. - fn check_shim_sig_deprecated<'a, const N: usize>( - &mut self, - abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: CanonAbi, - link_name: Symbol, - args: &'a [OpTy<'tcx>], - ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { - self.check_shim_symbol_clash(link_name)?; - - if abi.conv != exp_abi { - throw_ub_format!( - r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#, - abi.conv - ); - } - if abi.c_variadic { - throw_ub_format!( - "calling a non-variadic function with a c-variadic caller-side signature" - ); - } - - if let Ok(ops) = args.try_into() { - return interp_ok(ops); - } - throw_ub_format!( - "incorrect number of arguments for `{link_name}`: got {}, expected {}", - args.len(), - N - ) - } - /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and /// then returns the list of arguments, transmuted to the expected (shim) types. fn check_shim_sig( diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs deleted file mode 100644 index c557c35c9dea8..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64); -} - -fn main() { - let size = unsafe { miri_backtrace_size(0) }; - let mut frames = vec![std::ptr::null_mut(); size]; - unsafe { miri_get_backtrace(1, frames.as_mut_ptr()) }; - for frame in frames.iter() { - unsafe { - miri_resolve_frame(*frame, 0); //~ ERROR: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields - } - } -} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr deleted file mode 100644 index e50fd41617a81..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-decl.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 5 fields - --> tests/fail/shims/backtrace/bad-backtrace-decl.rs:LL:CC - | -LL | ... miri_resolve_frame(*frame, 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs deleted file mode 100644 index a398eb1ae0c22..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.rs +++ /dev/null @@ -1,9 +0,0 @@ -extern "Rust" { - fn miri_resolve_frame(ptr: *mut (), flags: u64); -} - -fn main() { - unsafe { - miri_resolve_frame(std::ptr::null_mut(), 0); //~ ERROR: null pointer - } -} diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr deleted file mode 100644 index bb4615dd14b50..0000000000000 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-ptr.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: Undefined Behavior: pointer not dereferenceable: pointer must point to some allocation, but got null pointer - --> tests/fail/shims/backtrace/bad-backtrace-ptr.rs:LL:CC - | -LL | miri_resolve_frame(std::ptr::null_mut(), 0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs index 31e3915f3d64f..a33c1f191f530 100644 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs @@ -1,25 +1,13 @@ -#[repr(C)] -struct MiriFrame { - name_len: usize, - filename_len: usize, - lineno: u32, - colno: u32, - fn_ptr: *mut (), -} - -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; -} +#[path = "../../../utils/mod.no_std.rs"] +mod utils; fn main() { unsafe { - let mut buf = vec![std::ptr::null_mut(); miri_backtrace_size(0)]; + let mut buf = vec![std::ptr::null_mut(); utils::miri_backtrace_size(0)]; - miri_get_backtrace(1, buf.as_mut_ptr()); + utils::miri_get_backtrace(1, buf.as_mut_ptr()); // miri_resolve_frame will error from an invalid backtrace before it will from invalid flags - miri_resolve_frame(buf[0], 2); //~ ERROR: unsupported operation: unknown `miri_resolve_frame` flags 2 + utils::miri_resolve_frame(buf[0], 2); //~ ERROR: unsupported operation: unknown `miri_resolve_frame` flags 2 } } diff --git a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr index 947c42397309d..85c9ea4f55bb2 100644 --- a/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr +++ b/src/tools/miri/tests/fail/shims/backtrace/bad-backtrace-resolve-flags.stderr @@ -1,8 +1,8 @@ error: unsupported operation: unknown `miri_resolve_frame` flags 2 --> tests/fail/shims/backtrace/bad-backtrace-resolve-flags.rs:LL:CC | -LL | miri_resolve_frame(buf[0], 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here +LL | utils::miri_resolve_frame(buf[0], 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here | = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support diff --git a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs index a3060abc39402..23e84b67308ef 100644 --- a/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs +++ b/src/tools/miri/tests/pass/backtrace/backtrace-api-v1.rs @@ -22,9 +22,9 @@ fn func_c() -> Box<[*mut ()]> { #[inline(never)] fn func_d() -> Box<[*mut ()]> { unsafe { - let count = miri_backtrace_size(0); + let count = utils::miri_backtrace_size(0); let mut buf = vec![std::ptr::null_mut(); count]; - miri_get_backtrace(1, buf.as_mut_ptr()); + utils::miri_get_backtrace(1, buf.as_mut_ptr()); buf.into() } } @@ -33,20 +33,20 @@ fn main() { let mut seen_main = false; let frames = func_a(); for frame in frames.iter() { - let miri_frame = unsafe { miri_resolve_frame(*frame, 1) }; + let miri_frame = unsafe { utils::miri_resolve_frame(*frame, 1) }; - let mut name = vec![0; miri_frame.name_len]; - let mut filename = vec![0; miri_frame.filename_len]; + let mut name = vec![0u8; miri_frame.name_len]; + let mut filename = vec![0u8; miri_frame.filename_len]; unsafe { - miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr()); + utils::miri_resolve_frame_names(*frame, 0, name.as_mut_ptr(), filename.as_mut_ptr()); } let name = String::from_utf8(name).unwrap(); let filename = String::from_utf8(filename).unwrap(); if name == "func_a" { - assert_eq!(func_a as *mut (), miri_frame.fn_ptr); + assert_eq!(func_a as *mut std::ffi::c_void, miri_frame.fn_ptr); } // Print every frame to stderr. @@ -66,19 +66,5 @@ fn main() { // This goes at the bottom of the file so that we can change it // without disturbing line numbers of the functions in the backtrace. -extern "Rust" { - fn miri_backtrace_size(flags: u64) -> usize; - fn miri_get_backtrace(flags: u64, buf: *mut *mut ()); - fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame; - fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8); -} - -#[derive(Debug)] -#[repr(C)] -struct MiriFrame { - name_len: usize, - filename_len: usize, - lineno: u32, - colno: u32, - fn_ptr: *mut (), -} +#[path = "../../utils/mod.no_std.rs"] +mod utils; diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index fa5c540e4ca97..d54639714a278 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -13,7 +13,7 @@ pub struct MiriFrame { // The function pointer to the function currently being executed. // This can be compared against function pointers obtained by // casting a function (e.g. `my_fn as *mut ()`) - pub fn_ptr: *mut (), + pub fn_ptr: *mut core::ffi::c_void, } #[cfg(miri)] @@ -48,8 +48,8 @@ extern "Rust" { pub fn miri_resolve_frame_names( ptr: *mut (), flags: u64, - name_buf: *mut (), - filename_buf: *mut (), + name_buf: *mut u8, + filename_buf: *mut u8, ); /// Miri-provided extern function to begin unwinding with the given payload. From aea90b8d92e3d608e975bae4ee319c92590b06a8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 22 Oct 2026 18:13:32 +0000 Subject: [PATCH 44/80] Update dependency jinja2 to v3.1.6 [SECURITY] --- .../host-x86_64/test-pr-check-1/reuse-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt index 8784e18864b23..231e06ba0a00c 100644 --- a/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt +++ b/src/ci/docker/host-x86_64/test-pr-check-1/reuse-requirements.txt @@ -24,9 +24,9 @@ chardet==5.2.0 \ # via # binaryornot # python-debian -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d +Jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via reuse license-expression==30.3.0 \ --hash=sha256:1295406f736b4f395ff069aec1cebfad53c0fcb3cf57df0f5ec58fc7b905aea5 \ From fccc8a7d96b6509c750f4bc467ca948ce35f9858 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 23 Oct 2026 08:07:41 +0200 Subject: [PATCH 45/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 7189d39d9ce936881b8132c6addeaf3e295cd455. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 50040f5a20e87..fde81e3cc035b 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -1a2c4797ef2570bd76ca5dee34449169bc7dc457 +7189d39d9ce936881b8132c6addeaf3e295cd455 From 8563ecf76d0b80ffdacc35d7d2930ee7b9e6b9a4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 23 Oct 2026 08:14:33 +0200 Subject: [PATCH 46/80] rustfmt got better at dealing with cfg_select --- src/tools/miri/src/shims/unix/fs.rs | 2 -- src/tools/miri/src/shims/unix/tcp_socket.rs | 38 ++++++++------------- src/tools/miri/tests/pass-dep/gettid.rs | 1 + 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 983761a943e33..5c67d99affa21 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -795,7 +795,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/7045 let result = cfg_select! { unix => fs::remove_file(path), windows => {{ @@ -1331,7 +1330,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/7045 let result = cfg_select! { unix => fs::remove_dir(path), windows => {{ diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 3cd462d31db4a..708757b72d88e 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -401,9 +401,6 @@ impl UnixSocketFileDescription for TcpSocket { unreachable!() }; - // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside - // a `cfg_select!` block. See . - #[rustfmt::skip] let listener = cfg_select! { // Turn the `socket2::Socket` into a `mio::TcpListener`. // We follow Tokio's TCP socket implementation: @@ -412,18 +409,17 @@ impl UnixSocketFileDescription for TcpSocket { // `from_raw_socket` as long as it's ensured that the socket is non-blocking. // Because we immediately make the socket non-blocking after creation, that // is always guaranteed. - unix => {{ - use std::os::fd::{IntoRawFd, FromRawFd}; + use std::os::fd::{FromRawFd, IntoRawFd}; let raw_fd = socket.into_raw_fd(); unsafe { TcpListener::from_raw_fd(raw_fd) } }}, windows => {{ - use std::os::windows::io::{IntoRawSocket, FromRawSocket}; + use std::os::windows::io::{FromRawSocket, IntoRawSocket}; let raw_socket = socket.into_raw_socket(); unsafe { TcpListener::from_raw_socket(raw_socket) } }}, - _ => unreachable!("unsupported host platform") + _ => unreachable!("unsupported host platform"), }; *state = SocketState::Listening(listener); @@ -539,9 +535,6 @@ impl UnixSocketFileDescription for TcpSocket { unreachable!() }; - // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside - // a `cfg_select!` block. See . - #[rustfmt::skip] let stream = cfg_select! { // Turn the `socket2::Socket` into a `mio::TcpStream`. // We follow Tokio's TCP socket implementation: @@ -550,19 +543,18 @@ impl UnixSocketFileDescription for TcpSocket { // `from_raw_socket` as long as it's ensured that the socket is non-blocking. // Because we immediately make the socket non-blocking after creation, that // is always guaranteed. - - unix => {{ - use std::os::fd::{IntoRawFd, FromRawFd}; - let raw_fd = socket.into_raw_fd(); - unsafe { TcpStream::from_raw_fd(raw_fd) } - }}, - windows => {{ - use std::os::windows::io::{IntoRawSocket, FromRawSocket}; - let raw_socket = socket.into_raw_socket(); - unsafe { TcpStream::from_raw_socket(raw_socket) } - }}, - _ => unreachable!("unsupported host platform") - }; + unix => {{ + use std::os::fd::{FromRawFd, IntoRawFd}; + let raw_fd = socket.into_raw_fd(); + unsafe { TcpStream::from_raw_fd(raw_fd) } + }}, + windows => {{ + use std::os::windows::io::{FromRawSocket, IntoRawSocket}; + let raw_socket = socket.into_raw_socket(); + unsafe { TcpStream::from_raw_socket(raw_socket) } + }}, + _ => unreachable!("unsupported host platform"), + }; *state = SocketState::Connecting(stream); drop(state); diff --git a/src/tools/miri/tests/pass-dep/gettid.rs b/src/tools/miri/tests/pass-dep/gettid.rs index 1466770694117..5dc479e241ce5 100644 --- a/src/tools/miri/tests/pass-dep/gettid.rs +++ b/src/tools/miri/tests/pass-dep/gettid.rs @@ -7,6 +7,7 @@ #![allow(unused_features)] // only used on some targets fn gettid() -> u64 { + #[rustfmt::skip] // FIXME rustfmt is eating comments cfg_select! { any(target_os = "android", target_os = "linux") => gettid_linux_like(), any(target_os = "nto", target_os = "qnx") => unsafe { libc::gettid() as u64 }, From 84173ce02b8b271a85b97cd78ff713351d628cb6 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:14:06 +0200 Subject: [PATCH 47/80] Safely convert `socket2::Socket` into mio `TcpStream` and `TcpListener` On Unix-like hosts we use `From` for the conversion while on Windows hosts the `From` implementation is used. --- src/tools/miri/src/shims/unix/tcp_socket.rs | 28 ++++++--------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 708757b72d88e..9cd6c3a312068 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -405,19 +405,13 @@ impl UnixSocketFileDescription for TcpSocket { // Turn the `socket2::Socket` into a `mio::TcpListener`. // We follow Tokio's TCP socket implementation: // See - // SAFETY: mio specifies that it is safe to use `from_raw_fd` and - // `from_raw_socket` as long as it's ensured that the socket is non-blocking. - // Because we immediately make the socket non-blocking after creation, that - // is always guaranteed. unix => {{ - use std::os::fd::{FromRawFd, IntoRawFd}; - let raw_fd = socket.into_raw_fd(); - unsafe { TcpListener::from_raw_fd(raw_fd) } + use std::os::fd::OwnedFd; + TcpListener::from(OwnedFd::from(socket)) }}, windows => {{ - use std::os::windows::io::{FromRawSocket, IntoRawSocket}; - let raw_socket = socket.into_raw_socket(); - unsafe { TcpListener::from_raw_socket(raw_socket) } + use std::os::windows::io::OwnedSocket; + TcpListener::from(OwnedSocket::from(socket)) }}, _ => unreachable!("unsupported host platform"), }; @@ -539,19 +533,13 @@ impl UnixSocketFileDescription for TcpSocket { // Turn the `socket2::Socket` into a `mio::TcpStream`. // We follow Tokio's TCP socket implementation: // See - // SAFETY: mio specifies that it is safe to use `from_raw_fd` and - // `from_raw_socket` as long as it's ensured that the socket is non-blocking. - // Because we immediately make the socket non-blocking after creation, that - // is always guaranteed. unix => {{ - use std::os::fd::{FromRawFd, IntoRawFd}; - let raw_fd = socket.into_raw_fd(); - unsafe { TcpStream::from_raw_fd(raw_fd) } + use std::os::fd::OwnedFd; + TcpStream::from(OwnedFd::from(socket)) }}, windows => {{ - use std::os::windows::io::{FromRawSocket, IntoRawSocket}; - let raw_socket = socket.into_raw_socket(); - unsafe { TcpStream::from_raw_socket(raw_socket) } + use std::os::windows::io::OwnedSocket; + TcpStream::from(OwnedSocket::from(socket)) }}, _ => unreachable!("unsupported host platform"), }; From f02b7f1c22a995b834c3ca8c89261849826295a9 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Thu, 22 Oct 2026 08:02:18 -0700 Subject: [PATCH 48/80] update no direct test FIXMEs on shims --- src/tools/miri/src/shims/unix/foreign_items.rs | 1 - src/tools/miri/src/shims/unix/macos/foreign_items.rs | 1 + src/tools/miri/src/shims/windows/foreign_items.rs | 11 ----------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 6cc88b011645d..f29b2e34488c1 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -437,7 +437,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "rename" => { - // FIXME: This does not have a direct test (#3179). let [oldpath, newpath] = &this.check_shim_sig( shim_sig!(extern "C" fn(*_, *_) -> i32), (link_name, abi, args), diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 5d2d800c68fd1..bd60bc69f4c48 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -429,6 +429,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "confstr" => { + // FIXME: This does not have a direct test (#3179). let [_key, _buf, _buflen] = &this.check_shim_sig( shim_sig!(extern "C" fn(i32, *_, usize) -> usize), (link_name, abi, args), diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 779188b0494af..9051522a030fb 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -505,7 +505,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(new_ptr, dest)?; } "LocalFree" => { - // FIXME: This does not have a direct test (#3179). let [ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(winapi::HLOCAL) -> winapi::HLOCAL), (link_name, abi, args), @@ -586,7 +585,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "TlsAlloc" => { - // FIXME: This does not have a direct test (#3179). // This just creates a key; Windows does not natively support TLS destructors. // Create key and return it. @@ -598,7 +596,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "TlsGetValue" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *_), (link_name, abi, args), @@ -609,7 +606,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(ptr, dest)?; } "TlsSetValue" => { - // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> winapi::BOOL), (link_name, abi, args), @@ -623,7 +619,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "TlsFree" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), (link_name, abi, args), @@ -637,7 +632,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Fiber-local storage - similar to TLS but supports destructors. "FlsAlloc" => { - // FIXME: This does not have a direct test (#3179). // Create key and return it. let [dtor] = &this.check_shim_sig( shim_sig!(extern "system" fn(winapi::PFLS_CALLBACK_FUNCTION) -> u32), @@ -659,7 +653,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?; } "FlsGetValue" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> *_), (link_name, abi, args), @@ -670,7 +663,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(ptr, dest)?; } "FlsSetValue" => { - // FIXME: This does not have a direct test (#3179). let [key, new_ptr] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> winapi::BOOL), (link_name, abi, args), @@ -684,7 +676,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "FlsFree" => { - // FIXME: This does not have a direct test (#3179). let [key] = &this.check_shim_sig( shim_sig!(extern "system" fn(u32) -> winapi::BOOL), (link_name, abi, args), @@ -703,7 +694,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(1, dest)?; } "IsThreadAFiber" => { - // FIXME: This does not have a direct test (#3179). let [] = &this.check_shim_sig( shim_sig!(extern "system" fn() -> winapi::BOOL), (link_name, abi, args), @@ -991,7 +981,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_bool(true), dest)?; } "ProcessPrng" => { - // FIXME: This does not have a direct test (#3179). // used by `std` let [ptr, len] = &this.check_shim_sig( shim_sig!(extern "system" fn(*_, usize) -> winapi::BOOL), From f232305bdd6c7855b1f5a45e9e868049e1da97b7 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:20:45 +0200 Subject: [PATCH 49/80] Add support for creating and closing a UDP socket --- src/tools/miri/src/shims/unix/mod.rs | 1 + src/tools/miri/src/shims/unix/socket.rs | 43 +++++--- src/tools/miri/src/shims/unix/tcp_socket.rs | 2 +- src/tools/miri/src/shims/unix/udp_socket.rs | 100 ++++++++++++++++++ .../miri/tests/pass-dep/libc/socket-tcp.rs | 30 ------ .../pass-dep/libc/socket-with-isolation.rs | 8 +- ...tderr => socket-with-isolation.tcp.stderr} | 0 .../libc/socket-with-isolation.udp.stderr | 4 + src/tools/miri/tests/pass-dep/libc/socket.rs | 57 ++++++++++ 9 files changed, 196 insertions(+), 49 deletions(-) create mode 100644 src/tools/miri/src/shims/unix/udp_socket.rs rename src/tools/miri/tests/pass-dep/libc/{socket-with-isolation.stderr => socket-with-isolation.tcp.stderr} (100%) create mode 100644 src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr create mode 100644 src/tools/miri/tests/pass-dep/libc/socket.rs diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 8a3ae89ae1dee..e03cbecd34154 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -10,6 +10,7 @@ mod socket_address; mod sync; mod tcp_socket; mod thread; +mod udp_socket; mod virtual_socket; mod android; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index a32ae29541493..0456f0928045e 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -5,11 +5,12 @@ use std::time::Duration; use rustc_abi::Size; use rustc_target::spec::Os; -use crate::shims::FileDescriptionRef; use crate::shims::files::FdNum; use crate::shims::unix::UnixFileDescription; use crate::shims::unix::socket_address::EvalContextExt as _; use crate::shims::unix::tcp_socket::TcpSocket; +use crate::shims::unix::udp_socket::UdpSocket; +use crate::shims::{FileDescription, FileDescriptionRef}; use crate::*; /// Represents unix-specific socket file descriptions. @@ -266,28 +267,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); }; - if flags != this.eval_libc_i32("SOCK_STREAM") { + let fd_result = if flags == this.eval_libc_i32("SOCK_STREAM") { + if ![0, this.eval_libc_i32("IPPROTO_TCP")].contains(&protocol) { + throw_unsup_format!( + "socket: socket protocol {protocol} is unsupported for \ + streaming socket, only IPPROTO_TCP and 0 are allowed" + ); + } + + TcpSocket::new(family, is_non_block).map(|s| this.machine.fds.new_ref(s).as_unix()) + } else if flags == this.eval_libc_i32("SOCK_DGRAM") { + if ![0, this.eval_libc_i32("IPPROTO_UDP")].contains(&protocol) { + throw_unsup_format!( + "socket: socket protocol {protocol} is unsupported for \ + datagram socket, only IPPROTO_UDP and 0 are allowed" + ); + } + + UdpSocket::new(family, is_non_block).map(|s| this.machine.fds.new_ref(s).as_unix()) + } else { throw_unsup_format!( "socket: type {:#x} is unsupported, only SOCK_STREAM, \ - SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", + SOCK_DGRAM, SOCK_CLOEXEC and SOCK_NONBLOCK are allowed", flags ); - } - if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") { - throw_unsup_format!( - "socket: socket protocol {protocol} is unsupported, \ - only IPPROTO_TCP and 0 are allowed" - ); - } - - let socket = match TcpSocket::new(family, is_non_block) { - Ok(socket) => socket, - Err(e) => return this.set_errno_and_return_neg1_i32(e), }; - let fds = &mut this.machine.fds; - let fd = fds.new_ref(socket); - interp_ok(Scalar::from_i32(fds.insert(fd))) + match fd_result { + Ok(fd) => interp_ok(Scalar::from_i32(this.machine.fds.insert(fd))), + Err(e) => this.set_errno_and_return_neg1_i32(e), + } } fn bind( diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 708757b72d88e..58517ff56ef2a 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -147,7 +147,7 @@ impl TcpSocket { impl FileDescription for TcpSocket { fn name(&self) -> &'static str { - "socket" + "tcp socket" } fn read<'tcx>( diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs new file mode 100644 index 0000000000000..c9b278fdefd6b --- /dev/null +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -0,0 +1,100 @@ +use std::cell::Cell; +use std::io; + +use crate::shims::files::{FileDescription, FileDescriptionRef}; +use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::socket::UnixSocketFileDescription; +use crate::*; + +#[derive(Debug)] +#[expect(unused)] +pub(super) struct UdpSocket { + /// Family of the socket, used to ensure that the socket only binds/connects to addresses + /// of the same family. + family: socket2::Domain, + /// The underlying host socket. + socket: mio::net::UdpSocket, + /// Whether this fd is non-blocking or not. + is_non_block: Cell, +} + +impl UdpSocket { + pub fn new(family: socket2::Domain, is_non_block: bool) -> io::Result { + let socket = + socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))?; + // The underlying host socket needs to be non-blocking. The actual + // blocking mode of the socket is stored in `is_non_block`. + socket.set_nonblocking(true)?; + + let socket = cfg_select! { + // Turn the `socket2::Socket` into a `mio::UdpSocket`. We cannot construct the + // mio socket directly, since its only constructor is `UdpSocket::bind`. + unix => {{ + use std::os::fd::OwnedFd; + mio::net::UdpSocket::from(OwnedFd::from(socket)) + }}, + windows => {{ + use std::os::windows::io::OwnedSocket; + mio::net::UdpSocket::from(OwnedSocket::from(socket)) + }}, + _ => unreachable!("unsupported host platform"), + }; + + // TODO: Add the underlying host socket to the blocking I/O manager. + + Ok(UdpSocket { family, socket, is_non_block: Cell::new(is_non_block) }) + } +} + +impl FileDescription for UdpSocket { + fn name(&self) -> &'static str { + "udp socket" + } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } +} + +impl UnixFileDescription for UdpSocket { + fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + let mut flags = ecx.eval_libc_i32("O_RDWR"); + + if self.is_non_block.get() { + flags |= ecx.eval_libc_i32("O_NONBLOCK"); + } + + interp_ok(Scalar::from_i32(flags)) + } + + fn set_flags<'tcx>( + &self, + mut flag: i32, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK"); + + // O_NONBLOCK flag can be set / unset by user. + if flag & o_nonblock == o_nonblock { + self.is_non_block.set(true); + flag &= !o_nonblock; + } else { + self.is_non_block.set(false); + } + + // Throw error if there is any unsupported flag. + if flag != 0 { + throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets") + } + + interp_ok(Scalar::from_i32(0)) + } + + fn as_socket( + self: FileDescriptionRef, + ) -> Option> { + Some(self) + } +} + +impl UnixSocketFileDescription for UdpSocket {} diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index 4e53a5497e14b..0dc8cf0de3909 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -16,8 +16,6 @@ use libc_utils::*; const TEST_BYTES: &[u8] = b"these are some test bytes!"; fn main() { - test_create_close(); - test_create_close_tcp(); test_bind_ipv4(); test_bind_ipv4_reuseaddr(); test_set_reuseaddr_invalid_len(); @@ -72,34 +70,6 @@ fn main() { test_unblock_after_socket_close(); } -/// Test creating a socket and then closing it afterwards. -fn test_create_close() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; - - // Ensure that socket is initially blocking. - assert_eq!(flags & libc::O_NONBLOCK, 0); - - unsafe { errno_check(libc::close(sockfd)) }; -} - -/// Test creating a socket and then closing it afterwards but we explicitly -/// specify that the TCP protocol should be used. -fn test_create_close_tcp() { - let sockfd = unsafe { - errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, libc::IPPROTO_TCP)).unwrap() - }; - - let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; - - // Ensure that socket is initially blocking. - assert_eq!(flags & libc::O_NONBLOCK, 0); - - unsafe { errno_check(libc::close(sockfd)) }; -} - fn test_bind_ipv4() { let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs index 0826db7e94a19..034236297651a 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # No libc socket on Windows //@compile-flags: -Zmiri-isolation-error=warn-nobacktrace +//@revisions: tcp udp use std::io::ErrorKind; @@ -7,9 +8,14 @@ use std::io::ErrorKind; mod libc_utils; use libc_utils::*; +const SOCKET_TYPE: libc::c_int = cfg_select! { + tcp => libc::SOCK_STREAM, + udp => libc::SOCK_DGRAM, +}; + fn main() { unsafe { - let err = errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap_err(); + let err = errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap_err(); assert_eq!(err.kind(), ErrorKind::PermissionDenied); // check that it is the right kind of `PermissionDenied` assert_eq!(err.raw_os_error(), Some(libc::EACCES)); diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.stderr b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.tcp.stderr similarity index 100% rename from src/tools/miri/tests/pass-dep/libc/socket-with-isolation.stderr rename to src/tools/miri/tests/pass-dep/libc/socket-with-isolation.tcp.stderr diff --git a/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr new file mode 100644 index 0000000000000..f7cfb6138cdcc --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-with-isolation.udp.stderr @@ -0,0 +1,4 @@ +warning: `socket` was made to return an error due to isolation + +warning: 1 warning emitted + diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs new file mode 100644 index 0000000000000..4d94543e70e97 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -0,0 +1,57 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@revisions: tcp udp +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use libc_utils::*; + +// This file contains tests which are executed with different socket types (e.g., TCP, UDP, ...). +// Every socket type has its own revision and based on that the `SOCKET_TYPE` and `SOCKET_PROTOCOL` +// constants get set. + +cfg_select! { + tcp => { + const SOCKET_TYPE: libc::c_int = libc::SOCK_STREAM; + const SOCKET_PROTOCOL: libc::c_int = libc::IPPROTO_TCP; + } + udp => { + const SOCKET_TYPE: libc::c_int = libc::SOCK_DGRAM; + const SOCKET_PROTOCOL: libc::c_int = libc::IPPROTO_UDP; + } +} + +fn main() { + test_create_close(); + test_create_close_with_protocol(); +} + +/// Test creating a socket and then closing it afterwards. +fn test_create_close() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; + + // Ensure that socket is initially blocking. + assert_eq!(flags & libc::O_NONBLOCK, 0); + + unsafe { errno_check(libc::close(sockfd)) }; +} + +/// Test creating a socket and then closing it afterwards but we explicitly +/// specify which protocol the socket should use. +fn test_create_close_with_protocol() { + let sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, SOCKET_PROTOCOL)).unwrap() }; + + let flags = unsafe { errno_result(libc::fcntl(sockfd, libc::F_GETFL, 0)).unwrap() }; + + // Ensure that socket is initially blocking. + assert_eq!(flags & libc::O_NONBLOCK, 0); + + unsafe { errno_check(libc::close(sockfd)) }; +} From 30a8a24e8d2207928ca55068aa09598df86f6f31 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:46:33 +0200 Subject: [PATCH 50/80] Add `bind` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 52 ++++++++++- .../miri/tests/pass-dep/libc/socket-tcp.rs | 62 ------------- src/tools/miri/tests/pass-dep/libc/socket.rs | 88 +++++++++++++++++++ src/tools/miri/tests/pass/shims/socket-udp.rs | 18 ++++ 4 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 src/tools/miri/tests/pass/shims/socket-udp.rs diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index c9b278fdefd6b..4fb6718ad5da2 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -1,5 +1,8 @@ use std::cell::Cell; use std::io; +use std::net::SocketAddr; + +use rustc_target::spec::Os; use crate::shims::files::{FileDescription, FileDescriptionRef}; use crate::shims::unix::UnixFileDescription; @@ -7,7 +10,6 @@ use crate::shims::unix::socket::UnixSocketFileDescription; use crate::*; #[derive(Debug)] -#[expect(unused)] pub(super) struct UdpSocket { /// Family of the socket, used to ensure that the socket only binds/connects to addresses /// of the same family. @@ -97,4 +99,50 @@ impl UnixFileDescription for UdpSocket { } } -impl UnixSocketFileDescription for UdpSocket {} +impl UnixSocketFileDescription for UdpSocket { + fn bind<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + address: SocketAddr, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result<(), IoError>> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + let address_family = match &address { + SocketAddr::V4(_) => socket2::Domain::IPV4, + SocketAddr::V6(_) => socket2::Domain::IPV6, + }; + + if self.family != address_family { + // Attempted to bind an address from a family that doesn't match + // the family of the socket. + let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs) { + // Linux man page states that `EINVAL` is used when there is an address family mismatch. + // See + // macOS also returns `EINVAL` but their man page does not specify this. + LibcError("EINVAL") + } else { + // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address + // family mismatch. + // See + LibcError("EAFNOSUPPORT") + }; + return interp_ok(Err(err)); + } + + // `bind` is a non-blocking operation for UDP sockets. + if let Err(e) = self.with_socket_ref(|s| s.bind(&socket2::SockAddr::from(address))).unwrap() + { + return interp_ok(Err(IoError::HostError(e))); + } + + interp_ok(Ok(())) + } + + fn with_socket_ref(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result + where + Self: Sized, + { + Ok(cb(socket2::SockRef::from(&self.socket))) + } +} diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index 0dc8cf0de3909..9cf860b652daf 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -16,7 +16,6 @@ use libc_utils::*; const TEST_BYTES: &[u8] = b"these are some test bytes!"; fn main() { - test_bind_ipv4(); test_bind_ipv4_reuseaddr(); test_set_reuseaddr_invalid_len(); #[cfg(any( @@ -29,8 +28,6 @@ fn main() { test_bind_ipv4_nosigpipe(); test_set_nosigpipe_invalid_len(); } - test_bind_ipv4_invalid_addr_len(); - test_bind_ipv6(); test_bind_twice(); test_bind_connected(); test_bind_listening(); @@ -70,19 +67,6 @@ fn main() { test_unblock_after_socket_close(); } -fn test_bind_ipv4() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - /// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket. fn test_bind_ipv4_reuseaddr() { let sockfd = @@ -160,52 +144,6 @@ fn test_set_nosigpipe_invalid_len() { assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); } -/// Tests binding an IPv4 socket with an IPv4 address but the addrlen argument -/// has the wrong size. -fn test_bind_ipv4_invalid_addr_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - // A too small size is invalid. - let err = unsafe { - errno_result(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - (size_of::() - 1) as libc::socklen_t, - )) - .unwrap_err() - }; - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - - if cfg!(miri) { - // A too big size is also invalid. Some native hosts (e.g. Linux) - // allow too big sizes, so we skip this test on native hosts: - // - let err = unsafe { - errno_result(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - (size_of::() + 1) as libc::socklen_t, - )) - .unwrap_err() - }; - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - } -} - -fn test_bind_ipv6() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in6).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - /// Test that invoking `bind` on an already bound TCP socket /// returns EINVAL. fn test_bind_twice() { diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs index 4d94543e70e97..2c9b09e059e47 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -28,6 +28,11 @@ cfg_select! { fn main() { test_create_close(); test_create_close_with_protocol(); + + test_bind_ipv4(); + test_bind_ipv4_invalid_addr_len(); + test_bind_ipv6(); + test_bind_wrong_address_family(); } /// Test creating a socket and then closing it afterwards. @@ -55,3 +60,86 @@ fn test_create_close_with_protocol() { unsafe { errno_check(libc::close(sockfd)) }; } + +/// Test binding a newly created socket to an IPv4 address. +fn test_bind_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Tests binding an IPv4 socket with an IPv4 address but the addrlen argument +/// has the wrong size. +fn test_bind_ipv4_invalid_addr_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + // A too small size is invalid. + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + (size_of::() - 1) as libc::socklen_t, + )) + .unwrap_err() + }; + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + + if cfg!(miri) { + // A too big size is also invalid. Some native hosts (e.g. Linux) + // allow too big sizes, so we skip this test on native hosts: + // + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + (size_of::() + 1) as libc::socklen_t, + )) + .unwrap_err() + }; + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + } +} + +/// Test binding a newly created socket to an IPv6 address. +fn test_bind_ipv6() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in6).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Test binding a newly created IPv6 UDP socket to an IPv4 address. +fn test_bind_wrong_address_family() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + + let err = unsafe { + errno_result(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )) + .unwrap_err() + }; + + if cfg!(any(target_os = "linux", target_os = "android")) || cfg!(all(udp, target_os = "macos")) + { + // For both UDP and TCP sockets Linux and Android targets return EINVAL + // instead of EAFNOSUPPORT. However, for UDP sockets macOS also returns + // EINVAL while for TCP sockets macOS returns EAFNOSUPPORT. + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)) + } else { + assert_eq!(err.raw_os_error(), Some(libc::EAFNOSUPPORT)) + } +} diff --git a/src/tools/miri/tests/pass/shims/socket-udp.rs b/src/tools/miri/tests/pass/shims/socket-udp.rs new file mode 100644 index 0000000000000..3ce28ae818896 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/socket-udp.rs @@ -0,0 +1,18 @@ +//@ignore-target: windows # No socket support on Windows +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +use std::net::UdpSocket; + +fn main() { + test_create_ipv4(); + test_create_ipv6(); +} + +fn test_create_ipv4() { + let _socket_ipv4 = UdpSocket::bind("127.0.0.1:0").unwrap(); +} + +fn test_create_ipv6() { + let _socket_ipv6 = UdpSocket::bind("[::1]:0").unwrap(); +} From 5c4888b5d3f0c74201cb7ac71a7f7048b08f1b46 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:47:00 +0200 Subject: [PATCH 51/80] Add `connect` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 12 ++++++++++++ src/tools/miri/tests/pass/shims/socket-udp.rs | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index 4fb6718ad5da2..0eadc0032e7a9 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -139,6 +139,18 @@ impl UnixSocketFileDescription for UdpSocket { interp_ok(Ok(())) } + fn connect<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + address: SocketAddr, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result<(), IoError>>, + ) -> InterpResult<'tcx> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + finish.call(ecx, self.socket.connect(address).map_err(IoError::HostError)) + } + fn with_socket_ref(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result where Self: Sized, diff --git a/src/tools/miri/tests/pass/shims/socket-udp.rs b/src/tools/miri/tests/pass/shims/socket-udp.rs index 3ce28ae818896..b6ffa09fff419 100644 --- a/src/tools/miri/tests/pass/shims/socket-udp.rs +++ b/src/tools/miri/tests/pass/shims/socket-udp.rs @@ -7,6 +7,7 @@ use std::net::UdpSocket; fn main() { test_create_ipv4(); test_create_ipv6(); + test_connect(); } fn test_create_ipv4() { @@ -16,3 +17,9 @@ fn test_create_ipv4() { fn test_create_ipv6() { let _socket_ipv6 = UdpSocket::bind("[::1]:0").unwrap(); } + +/// Test setting the default destination address using `connect`. +fn test_connect() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + socket.connect("127.0.1.1:8080").unwrap() +} From 39dee5fea632113a904f38314648d639b46de646 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:53:19 +0200 Subject: [PATCH 52/80] Add basic `setsockopt` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 64 ++++++++++++- .../miri/tests/pass-dep/libc/socket-tcp.rs | 89 ------------------- src/tools/miri/tests/pass-dep/libc/socket.rs | 85 ++++++++++++++++++ src/tools/miri/tests/pass/shims/socket-udp.rs | 69 ++++++++++++++ 4 files changed, 216 insertions(+), 91 deletions(-) diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index 0eadc0032e7a9..42c26dee5408c 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -1,12 +1,13 @@ use std::cell::Cell; use std::io; use std::net::SocketAddr; +use std::time::Duration; use rustc_target::spec::Os; use crate::shims::files::{FileDescription, FileDescriptionRef}; use crate::shims::unix::UnixFileDescription; -use crate::shims::unix::socket::UnixSocketFileDescription; +use crate::shims::unix::socket::{EvalContextExt, UnixSocketFileDescription}; use crate::*; #[derive(Debug)] @@ -18,6 +19,18 @@ pub(super) struct UdpSocket { socket: mio::net::UdpSocket, /// Whether this fd is non-blocking or not. is_non_block: Cell, + /// Read timeout of the socket. [`None`] means that reads can block indefinitely. + /// The timeout is applied to the monotonic clock (the Unix specification doesn't + /// specify which clock to use, but the monotonic clock is more common for + /// relative timeouts). + /// This is ignored when the socket is non-blocking. + read_timeout: Cell>, + /// Write timeout of the socket. [`None`] means that writes can block indefinitely. + /// The timeout is applied to the monotonic clock (the Unix specification doesn't + /// specify which clock to use, but the monotonic clock is more common + /// for relative timeouts). + /// This is ignored when the socket is non-blocking. + write_timeout: Cell>, } impl UdpSocket { @@ -44,7 +57,13 @@ impl UdpSocket { // TODO: Add the underlying host socket to the blocking I/O manager. - Ok(UdpSocket { family, socket, is_non_block: Cell::new(is_non_block) }) + Ok(UdpSocket { + family, + socket, + is_non_block: Cell::new(is_non_block), + read_timeout: Cell::new(None), + write_timeout: Cell::new(None), + }) } } @@ -151,6 +170,47 @@ impl UnixSocketFileDescription for UdpSocket { finish.call(ecx, self.socket.connect(address).map_err(IoError::HostError)) } + fn setsockopt<'tcx>( + self: FileDescriptionRef, + level: i32, + option: i32, + value_ptr: Pointer, + value_len: u64, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result<(), IoError>> { + if level == ecx.eval_libc_i32("SOL_SOCKET") { + return ecx.set_generic_socket_option(option, value_ptr, value_len, self); + } else if level == ecx.eval_libc_i32("IPPROTO_IP") { + return ecx.set_ip_socket_option( + option, + value_ptr, + value_len, + socket2::SockRef::from(&self.socket), + ); + } + + throw_unsup_format!( + "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \ + are allowed" + ); + } + + fn set_read_timeout<'tcx>( + self: FileDescriptionRef, + timeout: Option, + ) -> InterpResult<'tcx> { + self.read_timeout.set(timeout); + interp_ok(()) + } + + fn set_write_timeout<'tcx>( + self: FileDescriptionRef, + timeout: Option, + ) -> InterpResult<'tcx> { + self.write_timeout.set(timeout); + interp_ok(()) + } + fn with_socket_ref(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result where Self: Sized, diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index 9cf860b652daf..d0187d12cc00a 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -16,18 +16,6 @@ use libc_utils::*; const TEST_BYTES: &[u8] = b"these are some test bytes!"; fn main() { - test_bind_ipv4_reuseaddr(); - test_set_reuseaddr_invalid_len(); - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" - ))] - { - test_bind_ipv4_nosigpipe(); - test_set_nosigpipe_invalid_len(); - } test_bind_twice(); test_bind_connected(); test_bind_listening(); @@ -67,83 +55,6 @@ fn main() { test_unblock_after_socket_close(); } -/// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket. -fn test_bind_ipv4_reuseaddr() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int).unwrap(); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - -/// Tests setting the `SO_REUSEADDR` socket option but with an invalid length. -fn test_set_reuseaddr_invalid_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Value should be of type `libc::c_int` which has size 4 bytes. - // By providing an u16 of size 2 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u16).unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - - // By providing an u64 of size 8 bytes the behavior differs between native hosts and Miri. - let result = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64); - match result { - Err(err) => { - // Check that this is the right error. - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); - } - Ok(_) => { - // Some native hosts just ignore too large inputs and only look at a prefix. - // On Miri we require an exact size. - assert!(!cfg!(miri)); - } - } -} - -#[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" -))] -/// Tests binding after the `SO_NOSIGPIPE` socket option has been set on the newly created socket. -/// That flag only exists on BSD-like OSes. -fn test_bind_ipv4_nosigpipe() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1 as libc::c_int).unwrap(); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } -} - -#[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly" -))] -/// Tests setting the `SO_NOSIGPIPE` socket option but with an invalid length. -fn test_set_nosigpipe_invalid_len() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Value should be of type `libc::c_int` which has size 4 bytes. - // By providing a u16 of size 2 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u16).unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); -} - /// Test that invoking `bind` on an already bound TCP socket /// returns EINVAL. fn test_bind_twice() { diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs index 2c9b09e059e47..244d5ba0b8c14 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -31,6 +31,18 @@ fn main() { test_bind_ipv4(); test_bind_ipv4_invalid_addr_len(); + test_bind_ipv4_reuseaddr(); + test_set_reuseaddr_invalid_len(); + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" + ))] + { + test_bind_ipv4_nosigpipe(); + test_set_nosigpipe_invalid_len(); + } test_bind_ipv6(); test_bind_wrong_address_family(); } @@ -106,6 +118,79 @@ fn test_bind_ipv4_invalid_addr_len() { } } +/// Tests binding after the `SO_REUSEADDR` socket option has been set on the newly created socket. +fn test_bind_ipv4_reuseaddr() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int).unwrap(); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +/// Tests setting the `SO_REUSEADDR` socket option but with an invalid length. +fn test_set_reuseaddr_invalid_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Value should be of type `libc::c_int` which has size 4 bytes. + // By providing an u16 of size 2 bytes we trigger an invalid length error. + let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u16).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + + // By providing an u64 of size 8 bytes the behavior differs between native hosts and Miri. + let result = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64); + match result { + Err(err) => { + // Check that this is the right error. + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + } + Ok(_) => { + // Some native hosts just ignore too large inputs and only look at a prefix. + // On Miri we require an exact size. + assert!(!cfg!(miri)); + } + } +} + +#[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] +/// Tests binding after the `SO_NOSIGPIPE` socket option has been set on the newly created socket. +/// That flag only exists on BSD-like OSes. +fn test_bind_ipv4_nosigpipe() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1 as libc::c_int).unwrap(); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } +} + +#[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] +/// Tests setting the `SO_NOSIGPIPE` socket option but with an invalid length. +fn test_set_nosigpipe_invalid_len() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Value should be of type `libc::c_int` which has size 4 bytes. + // By providing a u16 of size 2 bytes we trigger an invalid length error. + let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1u16).unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); +} + /// Test binding a newly created socket to an IPv6 address. fn test_bind_ipv6() { let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; diff --git a/src/tools/miri/tests/pass/shims/socket-udp.rs b/src/tools/miri/tests/pass/shims/socket-udp.rs index b6ffa09fff419..e3e3fd1ac110f 100644 --- a/src/tools/miri/tests/pass/shims/socket-udp.rs +++ b/src/tools/miri/tests/pass/shims/socket-udp.rs @@ -3,11 +3,15 @@ //@run-native use std::net::UdpSocket; +use std::time::Duration; fn main() { test_create_ipv4(); test_create_ipv6(); test_connect(); + test_sockopt_ttl(); + test_sockopt_read_timeout(); + test_sockopt_write_timeout(); } fn test_create_ipv4() { @@ -23,3 +27,68 @@ fn test_connect() { let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); socket.connect("127.0.1.1:8080").unwrap() } + +// Test setting and reading the TTL socket option. +fn test_sockopt_ttl() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + socket.set_ttl(16).unwrap(); + // TODO: Once UDP sockets support `getsockopt`. + // assert_eq!(socket.ttl().unwrap(), 16); +} + +/// Test setting and reading the SNDTIMEO socket option. +/// This also tests that a read won't block indefinitely +/// when the read timeout is set to [`Some`] duration. +fn test_sockopt_read_timeout() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + + // By default, reads on blocking sockets should block indefinitely. + // TODO: Once UDP sockets support `getsockopt`. + // assert_eq!(socket.read_timeout().unwrap(), None); + + let short_read_timeout = Some(Duration::from_millis(40)); + socket.set_read_timeout(short_read_timeout).unwrap(); + // TODO: Once UDP sockets support `getsockopt`. + // assert_eq!(socket.read_timeout().unwrap(), short_read_timeout); + + /* + TODO: Once UDP sockets support reading. + + let mut buffer = [0u8; 128]; + // This should not block indefinitely and instead return EAGAIN/EWOULDBLOCK. + let err = socket.read(&mut buffer).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::WouldBlock); + */ +} + +/// Test setting and reading the RCVTIMEO socket option. +/// This also tests that a write won't block indefinitely when +/// the write timeout is set to [`Some`] duration. +fn test_sockopt_write_timeout() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + + // By default, writes on blocking sockets should block indefinitely. + // TODO: Once UDP sockets support `getsockopt`. + // assert_eq!(socket.write_timeout().unwrap(), None); + + let short_write_timeout = Some(Duration::from_millis(40)); + socket.set_write_timeout(short_write_timeout).unwrap(); + // TODO: Once UDP sockets support `getsockopt`. + // assert_eq!(socket.write_timeout().unwrap(), short_write_timeout); + + /* + TODO: Once UDP sockets support writing. + + let fill_buffer = [1u8; 1024]; + loop { + match socket.write_all(&fill_buffer) { + Ok(_) => { /* continue to fill up buffer */ } + // When we get an EAGAIN/EWOULDBLOCK when writing into a blocking socket, + // we know it's because of the write timeout exceeding because the write + // buffer is full. + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } + */ +} From e9fb3f9a8b1374cc315dda87a6215a6c90b83099 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:05:55 +0200 Subject: [PATCH 53/80] Add basic `getsockopt` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 30 ++++++++ .../miri/tests/pass-dep/libc/socket-tcp.rs | 68 +------------------ src/tools/miri/tests/pass-dep/libc/socket.rs | 68 +++++++++++++++++++ src/tools/miri/tests/pass/shims/socket-udp.rs | 15 ++-- 4 files changed, 104 insertions(+), 77 deletions(-) diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index 42c26dee5408c..3e75c83b640a7 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -195,6 +195,24 @@ impl UnixSocketFileDescription for UdpSocket { ); } + fn getsockopt<'tcx>( + self: FileDescriptionRef, + level: i32, + option: i32, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result, IoError>> { + if level == ecx.eval_libc_i32("SOL_SOCKET") { + ecx.get_generic_socket_option(option, self) + } else if level == ecx.eval_libc_i32("IPPROTO_IP") { + ecx.get_ip_socket_option(option, socket2::SockRef::from(&self.socket)) + } else { + throw_unsup_format!( + "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \ + are allowed" + ) + } + } + fn set_read_timeout<'tcx>( self: FileDescriptionRef, timeout: Option, @@ -203,6 +221,12 @@ impl UnixSocketFileDescription for UdpSocket { interp_ok(()) } + fn get_read_timeout<'tcx>( + self: FileDescriptionRef, + ) -> InterpResult<'tcx, Option> { + interp_ok(self.read_timeout.get()) + } + fn set_write_timeout<'tcx>( self: FileDescriptionRef, timeout: Option, @@ -211,6 +235,12 @@ impl UnixSocketFileDescription for UdpSocket { interp_ok(()) } + fn get_write_timeout<'tcx>( + self: FileDescriptionRef, + ) -> InterpResult<'tcx, Option> { + interp_ok(self.write_timeout.get()) + } + fn with_socket_ref(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result where Self: Sized, diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index d0187d12cc00a..b73a5fb80fe8e 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -9,7 +9,7 @@ mod utils; use std::io::ErrorKind; use std::time::{Duration, Instant}; -use std::{ptr, slice, thread}; +use std::{ptr, thread}; use libc_utils::*; @@ -46,8 +46,6 @@ fn main() { test_shutdown_readable_after_write_close(); test_shutdown_writable_after_read_close(); - test_getsockopt_truncate(); - test_sockopt_sndtimeo(); test_sockopt_rcvtimeo(); test_sockopt_reuseaddr(); @@ -735,70 +733,6 @@ fn test_shutdown_writable_after_read_close() { server_thread.join().unwrap(); } -/// Test that the value gets silently truncated when a too small -/// length is provided and that the length gets reduced when the value -/// is smaller than the provided length. -fn test_getsockopt_truncate() { - let (sockfd, _) = net::make_listener_ipv4().unwrap(); - - // Set the read timeout for the socket. - // We use a multiple of 4ms for the `usec` since Linux seems to do rounding. - let new_timeout = libc::timeval { tv_sec: 123, tv_usec: 40_000 }; - net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, new_timeout).unwrap(); - - let mut option_value = std::mem::MaybeUninit::<[u8; 5]>::zeroed(); - // The actual `timeval` length is more than 5 bytes. - let mut short_option_len = 5 as libc::socklen_t; - assert!(short_option_len < size_of::() as libc::socklen_t); - - let timeout = - net::getsockopt::(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO).unwrap(); - // Ensure that we get the same value back as we just set. - assert_eq!(timeout.tv_sec, new_timeout.tv_sec); - assert_eq!(timeout.tv_usec, new_timeout.tv_usec); - - errno_result(unsafe { - libc::getsockopt( - sockfd, - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - option_value.as_mut_ptr().cast(), - &mut short_option_len, - ) - }) - .unwrap(); - // Ensure that the size wasn't changed. - assert_eq!(short_option_len, 5); - let truncated_timeout = unsafe { option_value.assume_init() }; - - unsafe { - let timeout_ptr = (&new_timeout) as *const libc::timeval as *const u8; - // Assert that the value was silently truncated. - assert_eq!(&truncated_timeout, slice::from_raw_parts(timeout_ptr, 5)); - } - - let mut option_value = std::mem::MaybeUninit::::zeroed(); - // The actual length is smaller than this. - let mut long_option_len = (size_of::() + 2) as libc::socklen_t; - - errno_result(unsafe { - libc::getsockopt( - sockfd, - libc::SOL_SOCKET, - libc::SO_RCVTIMEO, - option_value.as_mut_ptr().cast(), - &mut long_option_len, - ) - }) - .unwrap(); - // Ensure that the size was shortened to the actual length. - assert_eq!(long_option_len, size_of::() as libc::socklen_t); - // The returned timeout should be the same value as we just set. - let untruncated_timeout = unsafe { option_value.assume_init() }; - assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec); - assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec); -} - /// Test setting and getting the SO_SNDTIMEO socket option. /// Also test that writes don't block indefinitely when we /// have a nonzero timeout. diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs index 244d5ba0b8c14..07301b85a22ba 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -8,6 +8,8 @@ mod libc_utils; #[path = "../../utils/mod.rs"] mod utils; +use std::slice; + use libc_utils::*; // This file contains tests which are executed with different socket types (e.g., TCP, UDP, ...). @@ -45,6 +47,8 @@ fn main() { } test_bind_ipv6(); test_bind_wrong_address_family(); + + test_getsockopt_truncate(); } /// Test creating a socket and then closing it afterwards. @@ -228,3 +232,67 @@ fn test_bind_wrong_address_family() { assert_eq!(err.raw_os_error(), Some(libc::EAFNOSUPPORT)) } } + +/// Test that the value gets silently truncated when a too small +/// length is provided and that the length gets reduced when the value +/// is smaller than the provided length. +fn test_getsockopt_truncate() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + // Set the read timeout for the socket. + // We use a multiple of 4ms for the `usec` since Linux seems to do rounding. + let new_timeout = libc::timeval { tv_sec: 123, tv_usec: 40_000 }; + net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, new_timeout).unwrap(); + + let mut option_value = std::mem::MaybeUninit::<[u8; 5]>::zeroed(); + // The actual `timeval` length is more than 5 bytes. + let mut short_option_len = 5 as libc::socklen_t; + assert!(short_option_len < size_of::() as libc::socklen_t); + + let timeout = + net::getsockopt::(sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO).unwrap(); + // Ensure that we get the same value back as we just set. + assert_eq!(timeout.tv_sec, new_timeout.tv_sec); + assert_eq!(timeout.tv_usec, new_timeout.tv_usec); + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + option_value.as_mut_ptr().cast(), + &mut short_option_len, + ) + }) + .unwrap(); + // Ensure that the size wasn't changed. + assert_eq!(short_option_len, 5); + let truncated_timeout = unsafe { option_value.assume_init() }; + + unsafe { + let timeout_ptr = (&new_timeout) as *const libc::timeval as *const u8; + // Assert that the value was silently truncated. + assert_eq!(&truncated_timeout, slice::from_raw_parts(timeout_ptr, 5)); + } + + let mut option_value = std::mem::MaybeUninit::::zeroed(); + // The actual length is smaller than this. + let mut long_option_len = (size_of::() + 2) as libc::socklen_t; + + errno_result(unsafe { + libc::getsockopt( + sockfd, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + option_value.as_mut_ptr().cast(), + &mut long_option_len, + ) + }) + .unwrap(); + // Ensure that the size was shortened to the actual length. + assert_eq!(long_option_len, size_of::() as libc::socklen_t); + // The returned timeout should be the same value as we just set. + let untruncated_timeout = unsafe { option_value.assume_init() }; + assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec); + assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec); +} diff --git a/src/tools/miri/tests/pass/shims/socket-udp.rs b/src/tools/miri/tests/pass/shims/socket-udp.rs index e3e3fd1ac110f..bd4e8aee28321 100644 --- a/src/tools/miri/tests/pass/shims/socket-udp.rs +++ b/src/tools/miri/tests/pass/shims/socket-udp.rs @@ -32,8 +32,7 @@ fn test_connect() { fn test_sockopt_ttl() { let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); socket.set_ttl(16).unwrap(); - // TODO: Once UDP sockets support `getsockopt`. - // assert_eq!(socket.ttl().unwrap(), 16); + assert_eq!(socket.ttl().unwrap(), 16); } /// Test setting and reading the SNDTIMEO socket option. @@ -43,13 +42,11 @@ fn test_sockopt_read_timeout() { let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); // By default, reads on blocking sockets should block indefinitely. - // TODO: Once UDP sockets support `getsockopt`. - // assert_eq!(socket.read_timeout().unwrap(), None); + assert_eq!(socket.read_timeout().unwrap(), None); let short_read_timeout = Some(Duration::from_millis(40)); socket.set_read_timeout(short_read_timeout).unwrap(); - // TODO: Once UDP sockets support `getsockopt`. - // assert_eq!(socket.read_timeout().unwrap(), short_read_timeout); + assert_eq!(socket.read_timeout().unwrap(), short_read_timeout); /* TODO: Once UDP sockets support reading. @@ -68,13 +65,11 @@ fn test_sockopt_write_timeout() { let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); // By default, writes on blocking sockets should block indefinitely. - // TODO: Once UDP sockets support `getsockopt`. - // assert_eq!(socket.write_timeout().unwrap(), None); + assert_eq!(socket.write_timeout().unwrap(), None); let short_write_timeout = Some(Duration::from_millis(40)); socket.set_write_timeout(short_write_timeout).unwrap(); - // TODO: Once UDP sockets support `getsockopt`. - // assert_eq!(socket.write_timeout().unwrap(), short_write_timeout); + assert_eq!(socket.write_timeout().unwrap(), short_write_timeout); /* TODO: Once UDP sockets support writing. From ff92522776685c9420a0ce304fc3182a5895b155 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:02:36 +0200 Subject: [PATCH 54/80] Add `getsockname` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 47 +++++- .../miri/tests/pass-dep/libc/socket-tcp.rs | 138 ------------------ .../miri/tests/pass-dep/libc/socket-udp.rs | 42 ++++++ src/tools/miri/tests/pass-dep/libc/socket.rs | 122 ++++++++++++++++ 4 files changed, 209 insertions(+), 140 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/socket-udp.rs diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index 3e75c83b640a7..caefe6f5602e3 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -1,6 +1,6 @@ use std::cell::Cell; use std::io; -use std::net::SocketAddr; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::time::Duration; use rustc_target::spec::Os; @@ -19,6 +19,8 @@ pub(super) struct UdpSocket { socket: mio::net::UdpSocket, /// Whether this fd is non-blocking or not. is_non_block: Cell, + /// Whether the socket is implicitly or explicitly bound to an address. + is_bound: Cell, /// Read timeout of the socket. [`None`] means that reads can block indefinitely. /// The timeout is applied to the monotonic clock (the Unix specification doesn't /// specify which clock to use, but the monotonic clock is more common for @@ -61,6 +63,7 @@ impl UdpSocket { family, socket, is_non_block: Cell::new(is_non_block), + is_bound: Cell::new(false), read_timeout: Cell::new(None), write_timeout: Cell::new(None), }) @@ -155,6 +158,9 @@ impl UnixSocketFileDescription for UdpSocket { return interp_ok(Err(IoError::HostError(e))); } + // The socket has been explicitly bound to a local address. + self.is_bound.set(true); + interp_ok(Ok(())) } @@ -167,7 +173,16 @@ impl UnixSocketFileDescription for UdpSocket { ) -> InterpResult<'tcx> { assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); - finish.call(ecx, self.socket.connect(address).map_err(IoError::HostError)) + if let Err(e) = self.socket.connect(address) { + return finish.call(ecx, Err(IoError::HostError(e))); + } + + // POSIX specifies that unbound sockets get bound to an unassigned + // local address after `connect` has been invoked on them. + // We thus treat the socket as implicitly bound. + self.is_bound.set(true); + + finish.call(ecx, Ok(())) } fn setsockopt<'tcx>( @@ -213,6 +228,34 @@ impl UnixSocketFileDescription for UdpSocket { } } + fn getsockname<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + if !self.is_bound.get() { + // Since Windows returns EINVAL when invoking `getsockname` on + // a socket which hasn't been bound yet, we need to manually + // return an unspecified address here. + + let address = if self.family == socket2::Domain::IPV4 { + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, /* port */ 0)) + } else { + SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::UNSPECIFIED, + /* port */ 0, + /* flowinfo */ 0, + /* scope_id */ 0, + )) + }; + return interp_ok(Ok(address)); + } + + interp_ok(self.socket.local_addr().map_err(IoError::HostError)) + } + fn set_read_timeout<'tcx>( self: FileDescriptionRef, timeout: Option, diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs index b73a5fb80fe8e..566420ac0d66d 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp.rs @@ -32,12 +32,7 @@ fn main() { test_readv(); test_writev(); - test_getsockname_ipv4(); - test_getsockname_ipv4_random_port(); - test_getsockname_ipv4_unbound(); test_getsockname_ipv4_connect(); - test_getsockname_ipv6(); - test_getsockname_ipv6_unbound(); test_getpeername_ipv4(); test_getpeername_ipv6(); @@ -434,83 +429,6 @@ fn test_writev() { assert_eq!(&buffer, TEST_BYTES); } -/// Test the `getsockname` syscall on an IPv4 socket which is bound. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to. -fn test_getsockname_ipv4() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 6789); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - assert_eq!(addr.sin_port, sock_addr.sin_port); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - -/// Test the `getsockname` syscall on an IPv4 socket which is bound -/// but the port was zero. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to but the port should be non-zero. -fn test_getsockname_ipv4_random_port() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - // Use zero-port to let the OS choose a free port to bind to. - let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - // The bound port must not be the zero port. - assert!(sock_addr.sin_port > 0); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - -/// Test the `getsockname` syscall on an IPv4 socket which is not bound. -/// The `getsockname` syscall should return 0.0.0.0:0 -fn test_getsockname_ipv4_unbound() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - let (_, sock_addr) = - net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - // Libc representation of an unspecified IPv4 address with zero port. - let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); - - assert_eq!(addr.sin_family, sock_addr.sin_family); - assert_eq!(addr.sin_port, sock_addr.sin_port); - assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); -} - /// Test the `getsockname` syscall on a connected IPv4 socket. fn test_getsockname_ipv4_connect() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); @@ -535,62 +453,6 @@ fn test_getsockname_ipv4_connect() { assert!(sock_addr.sin_port > 0); } -/// Test the `getsockname` syscall on an IPv6 socket which is bound. -/// The `getsockname` syscall should return the same address as to -/// which the socket was bound to. -fn test_getsockname_ipv6() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); - unsafe { - errno_check(libc::bind( - sockfd, - (&addr as *const libc::sockaddr_in6).cast::(), - size_of::() as libc::socklen_t, - )); - } - - unsafe { - errno_check(libc::listen(sockfd, 16)); - } - - let (_, sock_addr) = - net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - assert_eq!(addr.sin6_family, sock_addr.sin6_family); - assert_eq!(addr.sin6_port, sock_addr.sin6_port); - assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); - assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); - assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); -} - -/// Test the `getsockname` syscall on an IPv6 socket which is not bound. -/// The `getsockname` syscall should return [::]:0 with flowinfo and scope id -/// of zero. -fn test_getsockname_ipv6_unbound() { - let sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() }; - - let (_, sock_addr) = - net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) - .unwrap(); - - // Libc representation of an unspecified IPv6 address with zero port, flowinfo and scope id. - let addr = net::sock_addr_full_ipv6( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - /* port */ 0, - /* flowinfo */ 0, - /* scope_id */ 0, - ); - - assert_eq!(addr.sin6_family, sock_addr.sin6_family); - assert_eq!(addr.sin6_port, sock_addr.sin6_port); - assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); - assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); - assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); -} - /// Test the `getpeername` syscall on an IPv4 socket. /// For a connected socket, the `getpeername` syscall should /// return the same address as the socket was connected to. diff --git a/src/tools/miri/tests/pass-dep/libc/socket-udp.rs b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs new file mode 100644 index 0000000000000..1272bfd3a1844 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs @@ -0,0 +1,42 @@ +//@ignore-target: windows # No libc socket on Windows +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; +#[path = "../../utils/mod.rs"] +mod utils; + +use libc_utils::*; + +fn main() { + test_getsockname_ipv4_connect(); +} + +/// Test the `getsockname` syscall on a IPv4 UDP socket for which the default +/// destination address has been set using `connect` but which has not been +/// explicitly bound to a local address. +/// The socket should be implicitly bound, resulting in a non-unspecified address +/// with a non-zero port being returned. +fn test_getsockname_ipv4_connect() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + + // Destiniation address of the UDP socket. + // There is no deeper meaning behind choice of the address and the port. + let addr = net::sock_addr_ipv4([127, 0, 1, 3], 7564); + + net::connect_ipv4(sockfd, addr).unwrap(); + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // We want to ensure that the local address is not the unspecified address. + // Because the bound address could be of any local interface, we cannot + // test for localhost. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + assert!(sock_addr.sin_port > 0); +} diff --git a/src/tools/miri/tests/pass-dep/libc/socket.rs b/src/tools/miri/tests/pass-dep/libc/socket.rs index 07301b85a22ba..a8ed32066d2b7 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket.rs @@ -49,6 +49,12 @@ fn main() { test_bind_wrong_address_family(); test_getsockopt_truncate(); + + test_getsockname_ipv4(); + test_getsockname_ipv4_random_port(); + test_getsockname_ipv4_unbound(); + test_getsockname_ipv6(); + test_getsockname_ipv6_unbound(); } /// Test creating a socket and then closing it afterwards. @@ -296,3 +302,119 @@ fn test_getsockopt_truncate() { assert_eq!(untruncated_timeout.tv_sec, new_timeout.tv_sec); assert_eq!(untruncated_timeout.tv_usec, new_timeout.tv_usec); } + +/// Test the `getsockname` syscall on an IPv4 socket which is bound. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to. +fn test_getsockname_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 6789); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_eq!(addr.sin_port, sock_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv4 socket which is bound +/// but the port was zero. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to but the port should be non-zero. +fn test_getsockname_ipv4_random_port() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + // Use zero-port to let the OS choose a free port to bind to. + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 0); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + // The bound port must not be the zero port. + assert!(sock_addr.sin_port > 0); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv4 socket which is not bound. +/// The `getsockname` syscall should return 0.0.0.0:0 +fn test_getsockname_ipv4_unbound() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, SOCKET_TYPE, 0)).unwrap() }; + + let (_, sock_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // Libc representation of an unspecified IPv4 address with zero port. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_eq!(addr.sin_port, sock_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); +} + +/// Test the `getsockname` syscall on an IPv6 socket which is bound. +/// The `getsockname` syscall should return the same address as to +/// which the socket was bound to. +fn test_getsockname_ipv6() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); + unsafe { + errno_check(libc::bind( + sockfd, + (&addr as *const libc::sockaddr_in6).cast::(), + size_of::() as libc::socklen_t, + )); + } + + let (_, sock_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin6_family, sock_addr.sin6_family); + assert_eq!(addr.sin6_port, sock_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); +} + +/// Test the `getsockname` syscall on an IPv6 socket which is not bound. +/// The `getsockname` syscall should return [::]:0 with flowinfo and scope id +/// of zero. +fn test_getsockname_ipv6_unbound() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET6, SOCKET_TYPE, 0)).unwrap() }; + + let (_, sock_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getsockname(sockfd, storage, len) }) + .unwrap(); + + // Libc representation of an unspecified IPv6 address with zero port, flowinfo and scope id. + let addr = net::sock_addr_full_ipv6( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + /* port */ 0, + /* flowinfo */ 0, + /* scope_id */ 0, + ); + + assert_eq!(addr.sin6_family, sock_addr.sin6_family); + assert_eq!(addr.sin6_port, sock_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, sock_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, sock_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr); +} From 9c9b03a9153472031e6347db8c355fd2cdcc1b23 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:18:13 +0200 Subject: [PATCH 55/80] Add `getpeername` support for UDP sockets --- src/tools/miri/src/shims/unix/udp_socket.rs | 11 ++++ .../miri/tests/pass-dep/libc/socket-udp.rs | 58 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index caefe6f5602e3..d41e799fc5158 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -256,6 +256,17 @@ impl UnixSocketFileDescription for UdpSocket { interp_ok(self.socket.local_addr().map_err(IoError::HostError)) } + fn getpeername<'tcx>( + self: FileDescriptionRef, + communicate_allowed: bool, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); + + finish.call(ecx, self.socket.peer_addr().map_err(IoError::HostError)) + } + fn set_read_timeout<'tcx>( self: FileDescriptionRef, timeout: Option, diff --git a/src/tools/miri/tests/pass-dep/libc/socket-udp.rs b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs index 1272bfd3a1844..6bdfb2e72b3cc 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-udp.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-udp.rs @@ -11,6 +11,9 @@ use libc_utils::*; fn main() { test_getsockname_ipv4_connect(); + + test_getpeername_ipv4(); + test_getpeername_ipv6(); } /// Test the `getsockname` syscall on a IPv4 UDP socket for which the default @@ -40,3 +43,58 @@ fn test_getsockname_ipv4_connect() { assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); assert!(sock_addr.sin_port > 0); } + +/// Test `getpeername` on an IPv4 socket. +/// For a socket whose default destination has been set using `connect`, +/// this should return the address of the specified default destination. +/// Otherwise, errno should be set to ENOTCONN. +fn test_getpeername_ipv4() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + + // When no default destination has been set using `connect`, + // `getpeername` should fail and set errno to ENOTCONN. + let err = net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::ENOTCONN)); + + let addr = net::sock_addr_ipv4(net::IPV4_LOCALHOST, 1234); + // Explicitly set default destination address. + net::connect_ipv4(sockfd, addr).unwrap(); + + let (_, dest_addr) = + net::sockname_ipv4(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin_family, dest_addr.sin_family); + assert_eq!(addr.sin_port, dest_addr.sin_port); + assert_eq!(addr.sin_addr.s_addr, dest_addr.sin_addr.s_addr); +} + +/// Test `getpeername` on an IPv6 socket. +/// For a socket whose default destination has been set using `connect`, +/// this should return the address of the specified default destination. +/// Otherwise, errno should be set to ENOTCONN. +fn test_getpeername_ipv6() { + let sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0)).unwrap() }; + + // When no default destination has been set using `connect`, + // `getpeername` should fail and set errno to ENOTCONN. + let err = net::sockname_ipv6(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::ENOTCONN)); + + let addr = net::sock_addr_ipv6(net::IPV6_LOCALHOST, 1234); + // Explicitly set default destination address. + net::connect_ipv6(sockfd, addr).unwrap(); + + let (_, dest_addr) = + net::sockname_ipv6(|storage, len| unsafe { libc::getpeername(sockfd, storage, len) }) + .unwrap(); + + assert_eq!(addr.sin6_family, dest_addr.sin6_family); + assert_eq!(addr.sin6_port, dest_addr.sin6_port); + assert_eq!(addr.sin6_flowinfo, dest_addr.sin6_flowinfo); + assert_eq!(addr.sin6_scope_id, dest_addr.sin6_scope_id); + assert_eq!(addr.sin6_addr.s6_addr, dest_addr.sin6_addr.s6_addr); +} From bff372edc3357d1040ba527a48ff88e953974dbb Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:36:08 +0200 Subject: [PATCH 56/80] Integrate UDP socket into readiness management system --- src/tools/miri/src/shims/unix/socket.rs | 4 +- src/tools/miri/src/shims/unix/tcp_socket.rs | 12 +++- src/tools/miri/src/shims/unix/udp_socket.rs | 70 +++++++++++++++---- .../libc/socket-udp-no-blocking-epoll.rs | 31 ++++++++ 4 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 0456f0928045e..e237617b296d4 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -275,7 +275,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - TcpSocket::new(family, is_non_block).map(|s| this.machine.fds.new_ref(s).as_unix()) + TcpSocket::new(family, is_non_block, this).map(|fd| fd.as_unix()) } else if flags == this.eval_libc_i32("SOCK_DGRAM") { if ![0, this.eval_libc_i32("IPPROTO_UDP")].contains(&protocol) { throw_unsup_format!( @@ -284,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - UdpSocket::new(family, is_non_block).map(|s| this.machine.fds.new_ref(s).as_unix()) + UdpSocket::new(family, is_non_block, this).map(|fd| fd.as_unix()) } else { throw_unsup_format!( "socket: type {:#x} is unsupported, only SOCK_STREAM, \ diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 58517ff56ef2a..72857903d505b 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -117,7 +117,11 @@ pub(super) struct TcpSocket { } impl TcpSocket { - pub fn new(family: socket2::Domain, is_non_block: bool) -> io::Result { + pub fn new<'tcx>( + family: socket2::Domain, + is_non_block: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> io::Result> { let socket = socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP))?; // The underlying host socket needs to be non-blocking. The actual @@ -131,7 +135,7 @@ impl TcpSocket { // does have a static initial readiness until `connect` or `listen` is // invoked on it. - Ok(TcpSocket { + let tcp_socket = TcpSocket { family, state: RefCell::new(SocketState::Initial(socket)), is_non_block: Cell::new(is_non_block), @@ -141,7 +145,9 @@ impl TcpSocket { read_timeout: Cell::new(None), write_timeout: Cell::new(None), watched: ReadinessWatched::default(), - }) + }; + + Ok(ecx.machine.fds.new_ref(tcp_socket)) } } diff --git a/src/tools/miri/src/shims/unix/udp_socket.rs b/src/tools/miri/src/shims/unix/udp_socket.rs index d41e799fc5158..13a1e3a38f1df 100644 --- a/src/tools/miri/src/shims/unix/udp_socket.rs +++ b/src/tools/miri/src/shims/unix/udp_socket.rs @@ -1,8 +1,9 @@ -use std::cell::Cell; +use std::cell::{Cell, RefCell, RefMut}; use std::io; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::time::Duration; +use mio::event::Source; use rustc_target::spec::Os; use crate::shims::files::{FileDescription, FileDescriptionRef}; @@ -16,11 +17,13 @@ pub(super) struct UdpSocket { /// of the same family. family: socket2::Domain, /// The underlying host socket. - socket: mio::net::UdpSocket, + socket: RefCell, /// Whether this fd is non-blocking or not. is_non_block: Cell, /// Whether the socket is implicitly or explicitly bound to an address. is_bound: Cell, + /// The current blocking I/O readiness of the file description. + io_readiness: RefCell, /// Read timeout of the socket. [`None`] means that reads can block indefinitely. /// The timeout is applied to the monotonic clock (the Unix specification doesn't /// specify which clock to use, but the monotonic clock is more common for @@ -33,10 +36,16 @@ pub(super) struct UdpSocket { /// for relative timeouts). /// This is ignored when the socket is non-blocking. write_timeout: Cell>, + /// State for being watched by epoll. + watched: ReadinessWatched, } impl UdpSocket { - pub fn new(family: socket2::Domain, is_non_block: bool) -> io::Result { + pub fn new<'tcx>( + family: socket2::Domain, + is_non_block: bool, + ecx: &mut MiriInterpCx<'tcx>, + ) -> io::Result> { let socket = socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))?; // The underlying host socket needs to be non-blocking. The actual @@ -57,16 +66,27 @@ impl UdpSocket { _ => unreachable!("unsupported host platform"), }; - // TODO: Add the underlying host socket to the blocking I/O manager. - - Ok(UdpSocket { + let udp_socket = UdpSocket { family, - socket, + socket: RefCell::new(socket), is_non_block: Cell::new(is_non_block), is_bound: Cell::new(false), + // We can set the default readiness to the empty readiness + // since the socket will be registered to the blocking I/O + // directly after its creation. + io_readiness: RefCell::new(Readiness::EMPTY), read_timeout: Cell::new(None), write_timeout: Cell::new(None), - }) + watched: ReadinessWatched::default(), + }; + + let fd = ecx.machine.fds.new_ref(udp_socket); + + // Add the UDP socket to the blocking I/O manager as it's always + // backed by a mio UdpSocket. + ecx.machine.blocking_io.register(fd.clone()); + + Ok(fd) } } @@ -78,6 +98,14 @@ impl FileDescription for UdpSocket { fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) + } + + fn readiness(&self) -> Readiness { + *self.io_readiness.borrow() + } } impl UnixFileDescription for UdpSocket { @@ -173,7 +201,7 @@ impl UnixSocketFileDescription for UdpSocket { ) -> InterpResult<'tcx> { assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); - if let Err(e) = self.socket.connect(address) { + if let Err(e) = self.socket.borrow().connect(address) { return finish.call(ecx, Err(IoError::HostError(e))); } @@ -196,11 +224,12 @@ impl UnixSocketFileDescription for UdpSocket { if level == ecx.eval_libc_i32("SOL_SOCKET") { return ecx.set_generic_socket_option(option, value_ptr, value_len, self); } else if level == ecx.eval_libc_i32("IPPROTO_IP") { + let socket = self.socket.borrow(); return ecx.set_ip_socket_option( option, value_ptr, value_len, - socket2::SockRef::from(&self.socket), + socket2::SockRef::from(&*socket), ); } @@ -219,7 +248,8 @@ impl UnixSocketFileDescription for UdpSocket { if level == ecx.eval_libc_i32("SOL_SOCKET") { ecx.get_generic_socket_option(option, self) } else if level == ecx.eval_libc_i32("IPPROTO_IP") { - ecx.get_ip_socket_option(option, socket2::SockRef::from(&self.socket)) + let socket = self.socket.borrow(); + ecx.get_ip_socket_option(option, socket2::SockRef::from(&*socket)) } else { throw_unsup_format!( "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \ @@ -253,7 +283,7 @@ impl UnixSocketFileDescription for UdpSocket { return interp_ok(Ok(address)); } - interp_ok(self.socket.local_addr().map_err(IoError::HostError)) + interp_ok(self.socket.borrow().local_addr().map_err(IoError::HostError)) } fn getpeername<'tcx>( @@ -264,7 +294,7 @@ impl UnixSocketFileDescription for UdpSocket { ) -> InterpResult<'tcx> { assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!"); - finish.call(ecx, self.socket.peer_addr().map_err(IoError::HostError)) + finish.call(ecx, self.socket.borrow().peer_addr().map_err(IoError::HostError)) } fn set_read_timeout<'tcx>( @@ -299,6 +329,18 @@ impl UnixSocketFileDescription for UdpSocket { where Self: Sized, { - Ok(cb(socket2::SockRef::from(&self.socket))) + let socket = self.socket.borrow(); + Ok(cb(socket2::SockRef::from(&*socket))) + } +} + +impl SourceFileDescription for UdpSocket { + fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> { + let mut socket = self.socket.borrow_mut(); + f(&mut *socket) + } + + fn get_readiness_mut(&self) -> RefMut<'_, Readiness> { + self.io_readiness.borrow_mut() } } diff --git a/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs new file mode 100644 index 0000000000000..24cb05d9ffaf5 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/socket-udp-no-blocking-epoll.rs @@ -0,0 +1,31 @@ +//@only-target: linux android illumos +//@compile-flags: -Zmiri-disable-isolation +//@run-native + +#[path = "../../utils/libc.rs"] +mod libc_utils; + +use libc_utils::epoll::*; +use libc_utils::*; + +fn main() { + test_epoll_udp_socket(); +} + +/// Test creating a non-blocking UDP socket and adding it to an epoll instance. +/// The socket should be writable. +fn test_epoll_udp_socket() { + let sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + unsafe { + // Change socket to be non-blocking. + errno_check(libc::fcntl(sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Add socket with READABLE and WRITABLE interest to epoll. + epoll_ctl_add(epfd, sockfd, EPOLLIN | EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); + + // The socket should always be writable. + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: sockfd }]); +} From fd4473cb1c5a5e10fa7e65e1a13e3d13ecedfab2 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Fri, 23 Oct 2026 00:17:14 +0200 Subject: [PATCH 57/80] Allow successful write after short write in non-blocking TCP send test --- .../libc/socket-tcp-no-blocking-epoll.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs index 628f7f9493567..3032578c62449 100644 --- a/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/socket-tcp-no-blocking-epoll.rs @@ -300,16 +300,12 @@ fn test_send_nonblock() { // When we didn't have a short write we should still be able to write more. // Ensure the socket is still writable. let readiness = current_epoll_readiness(client_sockfd, EPOLLOUT | EPOLLET); - if cfg!(miri) { - // With Miri we keep the writable readiness until EWOULDBLOCK is returned. - assert_eq!(readiness, EPOLLOUT); - } else { - // On native Linux hosts, the writable readiness is removed when the buffer - // is "almost" full. We can't emulate this with Miri. - // The buffer must not be "almost" full at the first write. - let is_not_first_write = total_written > fill_buf.len(); - assert!(readiness == EPOLLOUT || (is_not_first_write && readiness == 0)); - } + // When a buffer is fairly full, Linux can clear EPOLLOUT even if there is some + // space left. Similarly, Linux can also produce short writes (causing Miri + // to clear EPOLLOUT) even if the next write would work again. + // So, except after the first write, we cannot expect the socket to be writable here. + let is_not_first_write = total_written > fill_buf.len(); + assert!(readiness == EPOLLOUT || (is_not_first_write && readiness == 0)); } } Err(err) if err.kind() == ErrorKind::WouldBlock => break, From c9bdc9a82b7e814323b55bfa4726bd0f267865fd Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 23 Oct 2026 10:49:34 +0200 Subject: [PATCH 58/80] ensure that readdir returns race-free d_type --- src/tools/miri/src/shims/unix/fs.rs | 73 ++++++++++-------------- src/tools/miri/tests/pass-dep/libc/fs.rs | 48 ++++++++++++---- 2 files changed, 69 insertions(+), 52 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 5c67d99affa21..fb035e3951784 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{self, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; +use std::fs::{self, DirBuilder, File, FileTimes, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{self, Path}; use std::time::SystemTime; @@ -373,39 +373,15 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(0) } - fn file_type_to_d_type(&self, file_type: std::io::Result) -> InterpResult<'tcx, i32> { - #[cfg(unix)] - use std::os::unix::fs::FileTypeExt; - + #[cfg(windows)] + fn file_type_to_d_type(&self, file_type: fs::FileType) -> InterpResult<'tcx, i32> { let this = self.eval_context_ref(); - match file_type { - Ok(file_type) => { - match () { - _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()), - _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()), - _ if file_type.is_symlink() => - interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()), - // Certain file types are only supported when the host is a Unix system. - #[cfg(unix)] - _ if file_type.is_block_device() => - interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_char_device() => - interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_fifo() => - interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()), - #[cfg(unix)] - _ if file_type.is_socket() => - interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()), - // Fallback - _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), - } - } - Err(_) => { - // Fallback on error - interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()) - } + match () { + _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()), + _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()), + _ if file_type.is_symlink() => interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()), + // Fallback + _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), } } @@ -416,15 +392,28 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); interp_ok(match entry { Either::Left(dir_entry) => { - DirEntry { - name: dir_entry.file_name(), - d_type: this.file_type_to_d_type(dir_entry.file_type())?, - // If the host is a Unix system, fill in the inode number with its real value. - // If not, use 0 as a fallback value. - #[cfg(unix)] - ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry), - #[cfg(not(unix))] - ino: 0u64, + cfg_select! { + unix => { + use std::os::unix::fs::DirEntryExt; + DirEntry { + name: dir_entry.file_name(), + d_type: { + // We cannot rely on `dir_entry.file_type`, it might use a separate + // syscall based on `file_name` which is racy. + // It's always okay to return DT_UNKNOWN instead. + this.eval_libc("DT_UNKNOWN").to_u8()?.into() + }, + ino: dir_entry.ino(), + } + } + windows => { + DirEntry { + name: dir_entry.file_name(), + // `DirEntry::file_type` is always race-free and never fails on Windows. + d_type: this.file_type_to_d_type(dir_entry.file_type().unwrap())?, + ino: 0u64, + } + } } } Either::Right(special) => diff --git a/src/tools/miri/tests/pass-dep/libc/fs.rs b/src/tools/miri/tests/pass-dep/libc/fs.rs index a5a60de75f6b0..3862fb42bd489 100644 --- a/src/tools/miri/tests/pass-dep/libc/fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/fs.rs @@ -1596,11 +1596,11 @@ fn test_readdir() { let dir_path = utils::prepare("miri_test_libc_readdir"); fs::create_dir(&dir_path).ok(); - // Create test files - let file1 = dir_path.join("file1.txt"); - let file2 = dir_path.join("file2.txt"); - fs::write(&file1, b"content1").unwrap(); - fs::write(&file2, b"content2").unwrap(); + // Create test files & dir + fs::write(dir_path.join("file.txt"), b"content").unwrap(); + fs::create_dir(dir_path.join("dir")).unwrap(); + fs::symlink("file.txt", dir_path.join("link1")).unwrap(); + fs::symlink("dir", dir_path.join("link2")).unwrap(); let c_path = utils::into_c_string(&dir_path); @@ -1616,11 +1616,26 @@ fn test_readdir() { let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; let name = CStr::from_ptr(name_ptr); let name_str = name.to_string_lossy(); + + // Check d_type field, if it exists. + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + let type_ = (*entry_ptr).d_type; + if type_ != libc::DT_UNKNOWN { + match &*name_str { + "." | ".." | "dir" => assert_eq!(type_, libc::DT_DIR), + "file.txt" => assert_eq!(type_, libc::DT_REG), + "link1" | "link2" => assert_eq!(type_, libc::DT_LNK), + _ => panic!("unexpected filename {name_str:?}"), + } + } + } + entries.push(name_str.into_owned()); } errno_check(libc::closedir(dirp)); entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); + assert_eq!(&entries, &[".", "..", "dir", "file.txt", "link1", "link2"]); } // On Linux, also test `readdir64`. @@ -1639,16 +1654,29 @@ fn test_readdir() { let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; let name = CStr::from_ptr(name_ptr); let name_str = name.to_string_lossy(); + + // Check d_type field, if it exists. + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + let type_ = (*entry_ptr).d_type; + if type_ != libc::DT_UNKNOWN { + match &*name_str { + "." | ".." | "dir" => assert_eq!(type_, libc::DT_DIR), + "file.txt" => assert_eq!(type_, libc::DT_REG), + "link1" | "link2" => assert_eq!(type_, libc::DT_LNK), + _ => panic!("unexpected filename {name_str:?}"), + } + } + } + entries.push(name_str.into_owned()); } errno_check(libc::closedir(dirp)); entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); + assert_eq!(&entries, &[".", "..", "dir", "file.txt", "link1", "link2"]); } - fs::remove_file(&file1).unwrap(); - fs::remove_file(&file2).unwrap(); - fs::remove_dir(&dir_path).unwrap(); + fs::remove_dir_all(&dir_path).unwrap(); } fn test_dirfd() { From f65e06b0198e1034f77eeef17b5e1de592b79f64 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 23 Oct 2026 18:27:10 +0200 Subject: [PATCH 59/80] Fix sysroot path for ui tests --- compiler/rustc_codegen_gcc/build_system/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index ac076836579ac..9a4e98d3bb24d 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -140,7 +140,7 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Record the sysroot sources under the path the `rust-src` component uses, which is where // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests // print the build path where they expect `$SRC_DIR`. - let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); + let sysroot_source_dir = lib_path.join("rustlib/src/rust"); rustflags.push_str(&format!( " --remap-path-prefix={library_dir}={sysroot_source_dir}", library_dir = std::path::absolute(&library_dir) From 41be5c0ed3e3374e814e2fd959d43c1b6f15236e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 21 Oct 2026 15:41:55 +0200 Subject: [PATCH 60/80] Make const strings readonly and remove the pointer indirection --- compiler/rustc_codegen_gcc/src/common.rs | 10 ++++---- .../rustc_codegen_gcc/tests/asm/const_str.rs | 24 +++++++++++++++++++ .../tests/failing-ui-tests.txt | 2 -- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 compiler/rustc_codegen_gcc/tests/asm/const_str.rs diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 948d2df6b92a5..895bc629d63ce 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -28,12 +28,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.const_uint(self.type_u16(), i as u64) } - fn global_string(&self, string: &str) -> LValue<'gcc> { - // FIXME(antoyo): handle non-null-terminated strings. - let string = self.context.new_string_literal(string); + fn global_const_string(&self, string: &str) -> LValue<'gcc> { + // The bytes of a Rust string are not NUL-terminated and can contain NULs. + let string = bytes_in_context(self, string.as_bytes()); let sym = self.generate_local_symbol_name("str"); let global = self.declare_private_global(&sym, self.val_ty(string)); global.global_set_initializer_rvalue(string); + #[cfg(feature = "master")] + global.global_set_readonly(); global // FIXME(antoyo): set linkage. } @@ -245,7 +247,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) { let mut const_str_cache = self.const_str_cache.borrow_mut(); let str_global = const_str_cache.get(s).copied().unwrap_or_else(|| { - let g = self.global_string(s); + let g = self.global_const_string(s); const_str_cache.insert(s.to_owned(), g); g }); diff --git a/compiler/rustc_codegen_gcc/tests/asm/const_str.rs b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs new file mode 100644 index 0000000000000..65969a713c996 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/const_str.rs @@ -0,0 +1,24 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=2 + +#![crate_type = "lib"] +#![allow(invalid_value)] + +// The panic messages of the `assert_zero_valid` and `assert_inhabited` intrinsics are built with +// `const_str`, which passes the address of its global as the string data. The global must +// therefore hold the bytes themselves (not a pointer to a string literal), without a trailing +// NUL so that its size matches the length passed along, and it must be read-only. + +// CHECK-LABEL: "zeroed_ref": +// CHECK: mov esi, 57 +// CHECK: lea rdi, "[[REF_STR:str\.[0-9]+]]"[rip] +#[unsafe(no_mangle)] +pub fn zeroed_ref() -> &'static u8 { + unsafe { std::mem::zeroed() } +} + +// CHECK: .section .rodata.[[REF_STR]],"a" +// CHECK: .size "[[REF_STR]]", 57 +// CHECK-NEXT: "[[REF_STR]]": +// CHECK-NEXT: .ascii "attempted to zero-initialize type `&u8`, which is invalid" diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index a18eaf272fd07..84a0bbd93f07f 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -1,6 +1,5 @@ tests/ui/asm/may_unwind.rs tests/ui/asm/x86_64/may_unwind.rs -tests/ui/intrinsics/panic-uninitialized-zeroed.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs tests/ui/process/println-with-broken-pipe.rs @@ -20,7 +19,6 @@ tests/ui/explicit-tail-calls/become-indirect-return.rs tests/ui/explicit-tail-calls/indirect.rs tests/ui/abi/rust-tail-cc.rs tests/ui/abi/rust-preserve-none-cc.rs -tests/ui/extern/extern-types-field-offset.rs tests/ui/scalable-vectors/debuginfo-does-not-spill.rs tests/ui/scalable-vectors/debuginfo-no-opt-llvm-ice.rs tests/ui/scalable-vectors/transmute.rs From 2452094904eae4e6928ad99c51e4b9ea664f3286 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 20 Oct 2026 12:10:56 -0400 Subject: [PATCH 61/80] Fix to use target features from a target spec --- compiler/rustc_codegen_gcc/src/attributes.rs | 17 +-- compiler/rustc_codegen_gcc/src/gcc_util.rs | 105 +++++++++++++++---- compiler/rustc_codegen_gcc/src/lib.rs | 7 +- 3 files changed, 93 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index b6b0352d2516c..925c600aee936 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -20,7 +20,7 @@ use crate::base; use crate::context::CodegenCx; #[cfg(feature = "master")] use crate::gcc_util::to_gcc_aarch64_extension; -use crate::gcc_util::to_gcc_features; +use crate::gcc_util::{to_gcc_features, to_gcc_target_attribute_feature}; /// Checks if the function `instance` is recursively inline. /// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive. @@ -192,20 +192,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } let target_features = function_features .iter() - .filter_map(|feature| { - // FIXME(antoyo): support soft-float. - if feature.contains("soft-float") { - return None; - } - - if feature.starts_with('-') { - Some(format!("no{}", feature)) - } else if let Some(stripped) = feature.strip_prefix('+') { - Some(stripped.to_string()) - } else { - Some(feature.to_string()) - } - }) + .filter_map(|feature| to_gcc_target_attribute_feature(feature)) .collect::>() .join(","); if !target_features.is_empty() { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 5ecc971830f20..b0442cdfb11cc 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -20,7 +20,10 @@ fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`, /// `--target` and similar). -pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { +/// +/// If `for_cfg` is `true`, only the features implied by `--target` are included, to configure the +/// target info that populates [`rustc_codegen_ssa::TargetConfig`]. +pub(crate) fn global_gcc_features(sess: &EarlySession, for_cfg: bool) -> Vec { // Features that come earlier are overridden by conflicting features later in the string. // Typically we'll want more explicit settings to override the implicit ones, so: // @@ -57,6 +60,12 @@ pub(crate) fn global_gcc_features(sess: &EarlySession) -> Vec { // Features implied by an implicit or explicit `--target`. target_features::target_spec_to_backend_features(sess, &mut extend_backend_features); + // `internal_target_features` applies -Ctarget-features itself, and GCC has no options for the + // features implied by other flags (e.g. retpoline), which would break the target info. + if for_cfg { + return features; + } + // -Ctarget-features target_features::flag_to_backend_features(sess, extend_backend_features); @@ -267,25 +276,7 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context.add_command_line_option("-mavx"); } - if sess.target.arch == Arch::AArch64 { - // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are - // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. - let outline_atomics = sess - .global_backend_features - .iter() - .rev() - .find_map(|feature| match feature.as_str() { - "outline-atomics" | "+outline-atomics" => Some(true), - "-outline-atomics" => Some(false), - _ => None, - }) - .unwrap_or(false); - context.add_command_line_option(if outline_atomics { - "-moutline-atomics" - } else { - "-mno-outline-atomics" - }); - } + add_feature_options(&context, &sess.target.arch, &sess.global_backend_features); for arg in &sess.opts.cg.llvm_args { context.add_command_line_option(arg); @@ -387,6 +378,80 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { context } +/// Add the options for the global `features` that are not applied with function attributes. +fn add_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + if *arch == Arch::AArch64 { + // Unlike LLVM, GCC enables outline atomics by default, but their libgcc helpers are + // missing on targets without this feature, like the Linux kernel. Only AArch64 has it. + let outline_atomics = + feature_setting(features, "outline-atomics") == FeatureSetting::Enabled; + context.add_command_line_option(if outline_atomics { + "-moutline-atomics" + } else { + "-mno-outline-atomics" + }); + + // Rust's `neon` covers both FP and SIMD, so without it (e.g. on softfloat targets like the + // Linux kernel) GCC must not touch those registers at all. + if feature_setting(features, "neon") == FeatureSetting::Disabled { + context.add_command_line_option("-mgeneral-regs-only"); + } + } +} + +/// Configure the target info `context` with the global `features`, like codegen does. +#[cfg(feature = "master")] +pub fn add_target_info_feature_options(context: &Context<'_>, arch: &Arch, features: &[String]) { + add_feature_options(context, arch, features); + // Codegen applies these as function attributes, which the target info does not see. Of the + // arches using these attributes, libgccjit only has target info for x86, not PowerPC. + if matches!(arch, Arch::X86 | Arch::X86_64) { + for feature in + features.iter().filter_map(|feature| to_gcc_target_attribute_feature(feature)) + { + context.add_command_line_option(format!("-m{}", feature)); + } + } +} + +/// The spelling of a global `feature` in a GCC `target` attribute, e.g. `no-sse` for `-sse`. +pub fn to_gcc_target_attribute_feature(feature: &str) -> Option { + // FIXME(antoyo): support soft-float. + if feature.contains("soft-float") { + return None; + } + + if feature.starts_with('-') { + Some(format!("no{}", feature)) + } else if let Some(stripped) = feature.strip_prefix('+') { + Some(stripped.to_string()) + } else { + Some(feature.to_string()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum FeatureSetting { + Enabled, + Disabled, + /// No global feature mentions it. + Unspecified, +} + +/// The last setting of `feature` in `features`. +fn feature_setting(features: &[String], feature: &str) -> FeatureSetting { + features + .iter() + .rev() + .find_map(|global_feature| match global_feature.strip_prefix('-') { + Some(disabled) if disabled == feature => Some(FeatureSetting::Disabled), + Some(_) => None, + None => (global_feature.strip_prefix('+').unwrap_or(global_feature) == feature) + .then_some(FeatureSetting::Enabled), + }) + .unwrap_or(FeatureSetting::Unspecified) +} + pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { match relocation_model { rustc_target::spec::RelocModel::Static => { diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 0e44966d9b85d..d1d46db68ea67 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -238,7 +238,7 @@ impl CodegenBackend for GccCodegenBackend { panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } } - let global_backend_features = gcc_util::global_gcc_features(sess); + let global_backend_features = gcc_util::global_gcc_features(sess, false); #[cfg(feature = "master")] { @@ -251,6 +251,11 @@ impl CodegenBackend for GccCodegenBackend { if target_cpu != "generic" { context.add_command_line_option(format!("-march={}", target_cpu)); } + gcc_util::add_target_info_feature_options( + &context, + &sess.target.arch, + &gcc_util::global_gcc_features(sess, true), + ); // libgccjit always passes -fPIC, which some targets reject (e.g. MIPS without -mabicalls). gcc_util::add_pic_option(&context, sess.relocation_model()); From e034607236cef544bef110f5f2dd7d7a85348f6a Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 25 Jul 2026 18:50:17 +0200 Subject: [PATCH 62/80] specialize str.escape_default().{to_string, collect::}() and Extend for String too --- library/alloc/src/lib.rs | 2 + library/alloc/src/string.rs | 178 ++++++++++++++++++++++++++- library/alloctests/benches/str.rs | 4 + library/alloctests/benches/string.rs | 12 ++ library/alloctests/tests/string.rs | 11 ++ library/core/src/str/iter.rs | 12 ++ 6 files changed, 213 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 6c2d5d68d787c..aefe43aa13ef2 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -92,6 +92,7 @@ #![feature(allocator_ext)] #![feature(array_into_iter_constructors)] #![feature(ascii_char)] +#![feature(ascii_char_variants)] #![feature(async_fn_traits)] #![feature(async_iterator)] #![feature(borrowed_buf_init)] @@ -156,6 +157,7 @@ #![feature(pattern)] #![feature(pin_coerce_unsized_trait)] #![feature(ptr_alignment_type)] +#![feature(ptr_cast_array)] #![feature(ptr_metadata)] #![feature(random)] #![feature(raw_os_error_ty)] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index bb8301e53d34f..95c661649373b 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -42,11 +42,15 @@ #![stable(feature = "rust1", since = "1.0.0")] +#[cfg(not(no_global_oom_handling))] +use core::ascii; use core::error::Error; use core::iter::FusedIterator; #[cfg(not(no_global_oom_handling))] use core::iter::from_fn; #[cfg(not(no_global_oom_handling))] +use core::mem::MaybeUninit; +#[cfg(not(no_global_oom_handling))] use core::num::Saturating; #[cfg(not(no_global_oom_handling))] use core::ops::Add; @@ -2448,12 +2452,35 @@ impl Clone for String { #[stable(feature = "rust1", since = "1.0.0")] impl FromIterator for String { fn from_iter>(iter: I) -> String { - let mut buf = String::new(); - buf.extend(iter); + FromCharIterSpec::to_string_spec(iter.into_iter()) + } +} + +#[cfg(not(no_global_oom_handling))] +trait FromCharIterSpec: Iterator { + fn to_string_spec(self) -> String; +} + +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for T +where + T: Iterator, +{ + default fn to_string_spec(self) -> String { + let mut buf = String::with_capacity(self.size_hint().0); + buf.extend(self); buf } } +#[cfg(not(no_global_oom_handling))] +impl FromCharIterSpec for core::str::EscapeDefault<'_> { + fn to_string_spec(self) -> String { + // go through the ToString specialization + self.to_string() + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "string_from_iter_by_ref", since = "1.17.0")] impl<'a> FromIterator<&'a char> for String { @@ -2549,10 +2576,7 @@ impl<'a> FromIterator<&'a core::ascii::Char> for String { #[stable(feature = "rust1", since = "1.0.0")] impl Extend for String { fn extend>(&mut self, iter: I) { - let iterator = iter.into_iter(); - let (lower_bound, _) = iterator.size_hint(); - self.reserve(lower_bound); - iterator.for_each(move |c| self.push(c)); + SpecExtendChars::spec_extend_into(iter, self); } #[inline] @@ -2566,6 +2590,28 @@ impl Extend for String { } } +#[cfg(not(no_global_oom_handling))] +trait SpecExtendChars { + fn spec_extend_into(self, s: &mut String); +} + +#[cfg(not(no_global_oom_handling))] +impl> SpecExtendChars for T { + default fn spec_extend_into(self, target: &mut String) { + let iterator = self.into_iter(); + let (lower_bound, _) = iterator.size_hint(); + target.reserve(lower_bound); + iterator.for_each(move |c| target.push(c)); + } +} + +#[cfg(not(no_global_oom_handling))] +impl SpecExtendChars for core::str::EscapeDefault<'_> { + fn spec_extend_into(self, target: &mut String) { + target.extend_from_escape_default(self); + } +} + #[cfg(not(no_global_oom_handling))] #[stable(feature = "extend_ref", since = "1.2.0")] impl<'a> Extend<&'a char> for String { @@ -3160,6 +3206,126 @@ impl SpecToString for fmt::Arguments<'_> { } } +#[cfg(not(no_global_oom_handling))] +impl SpecToString for core::str::EscapeDefault<'_> { + fn spec_to_string(&self) -> String { + let mut s = String::new(); + s.extend_from_escape_default(self.clone()); + s + } +} + +#[cfg(not(no_global_oom_handling))] +impl String { + #[inline] + fn extend_from_escape_default(&mut self, escaper: core::str::EscapeDefault<'_>) { + let (front, middle, tail) = escaper.into_parts(); + let lengths = front.as_ref().map_or_default(|f| f.size_hint().0) + + middle.as_ref().map_or_default(|m| m.size_hint().0) + + tail.as_ref().map_or_default(|t| t.size_hint().0); + self.reserve(lengths); + + if let Some(front) = front { + self.extend(front); + } + if let Some(middle) = middle { + /// Ensures a spare capacity of N bytes and then passes that as MaybeUninit + /// slice to a closure that returns the bytes written, then + /// updates the length. + /// + /// # Safety: + /// + /// The caller must initialize the number of bytes returned by the closure. + #[inline] + unsafe fn with_spare( + bytes: &mut Vec, + then: impl FnOnce(&mut [MaybeUninit; N]) -> usize, + ) { + const { + assert!(core::mem::size_of::() == 1); + } + bytes.reserve(N); + let old_len = bytes.len(); + // SAFETY: We reserved enough space for the requested number of bytes. + unsafe { + let spare: &mut [MaybeUninit; N] = bytes + .spare_capacity_mut() + .as_mut_ptr() + .cast::() + .cast_uninit() + .cast_array::() + .as_mut_unchecked(); + let written = then(spare); + bytes.set_len(old_len + written); + } + } + + for c in middle { + match c { + '\'' | '\"' | '\\' => { + self.push('\\'); + self.push(c); + } + '\x20'..='\x7e' => self.push(c), + '\t' => { + self.push('\\'); + self.push('t'); + } + '\r' => { + self.push('\\'); + self.push('r'); + } + '\n' => { + self.push('\\'); + self.push('n'); + } + c => { + // SAFETY: We only let the function write ascii chars, so it remains valid utf8. + // and it returns the number of bytes written, so so with_spare ensures the rest for us. + unsafe { + with_spare::<10, ascii::Char>(self.as_mut_vec(), |spare| { + escape_unicode_into(spare, c) + }); + } + } + } + } + } + + if let Some(tail) = tail { + self.extend(tail); + } + } +} + +#[cfg(not(no_global_oom_handling))] +#[inline] +// adapted from core::escape::escape_unicode, but it's left-aligned instead of right-aligned. +fn escape_unicode_into(output: &mut [MaybeUninit; 10], c: char) -> usize { + const HEX_DIGITS: [ascii::Char; 16] = *b"0123456789abcdef".as_ascii().unwrap(); + + let c = c as u32; + // OR-ing `1` ensures that for `c == 0` the code computes that + // one digit should be printed. + let prefix_skip = (c | 1).leading_zeros() as usize / 4 - 2; + + // Always write all digits, but for shorter representations the unused + // digits get overwritten by later writes. + output[3usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 20) & 15) as usize]); + output[4usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 16) & 15) as usize]); + output[5usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 12) & 15) as usize]); + output[6usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 8) & 15) as usize]); + output[7usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[((c >> 4) & 15) as usize]); + output[8usize.saturating_sub(prefix_skip)].write(HEX_DIGITS[(c & 15) as usize]); + + output[0].write(ascii::Char::ReverseSolidus); + output[1].write(ascii::Char::SmallU); + output[2].write(ascii::Char::LeftCurlyBracket); + output[9 - prefix_skip].write(ascii::Char::RightCurlyBracket); + + 10 - prefix_skip +} + #[stable(feature = "rust1", since = "1.0.0")] impl AsRef for String { #[inline] diff --git a/library/alloctests/benches/str.rs b/library/alloctests/benches/str.rs index 98c7c5413caef..68bdfbed8c8b5 100644 --- a/library/alloctests/benches/str.rs +++ b/library/alloctests/benches/str.rs @@ -1,5 +1,9 @@ use test::{Bencher, black_box}; +#[allow(unused)] // this is a symlink to core benches, we don't use everything here +#[path = "../../coretests/benches/str/corpora.rs"] +pub(super) mod corpora; + #[bench] fn char_iterator(b: &mut Bencher) { let s = "ศไทย中华Việt Nam; Mary had a little lamb, Little lamb"; diff --git a/library/alloctests/benches/string.rs b/library/alloctests/benches/string.rs index 0bbec12e4fdc6..75caf722d723d 100644 --- a/library/alloctests/benches/string.rs +++ b/library/alloctests/benches/string.rs @@ -159,3 +159,15 @@ fn bench_insert_str_long(b: &mut Bencher) { x }) } + +#[bench] +fn bench_from_escape_default_ascii(b: &mut Bencher) { + let s = super::str::corpora::en::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} + +#[bench] +fn bench_from_escape_default_multibyte(b: &mut Bencher) { + let s = super::str::corpora::emoji::LARGE; + b.iter(|| black_box(s).escape_default().to_string()) +} diff --git a/library/alloctests/tests/string.rs b/library/alloctests/tests/string.rs index 08eb1855a4824..9c632b4e3a2f9 100644 --- a/library/alloctests/tests/string.rs +++ b/library/alloctests/tests/string.rs @@ -956,3 +956,14 @@ fn test_str_concat() { let s: String = format!("{a}{b}"); assert_eq!(s.as_bytes()[9], 'd' as u8); } + +// alloc has a specialization for EscapeDefault's ToString impl, which isn't covered by core tests. +#[test] +fn test_from_escaped_str() { + assert_eq!("abcABC012".escape_default().to_string(), "abcABC012"); + assert_eq!("\t\n\"".escape_default().to_string(), r#"\t\n\""#); + assert_eq!("\u{10FFFF}".escape_default().to_string(), "\\u{10ffff}"); + assert_eq!("\u{0}".escape_default().to_string(), "\\u{0}"); + assert_eq!("\u{1}".escape_default().to_string(), "\\u{1}"); + assert_eq!("\u{100}".escape_default().to_string(), "\\u{100}"); +} diff --git a/library/core/src/str/iter.rs b/library/core/src/str/iter.rs index 60b9e81aaeff9..657d894d38878 100644 --- a/library/core/src/str/iter.rs +++ b/library/core/src/str/iter.rs @@ -1589,6 +1589,18 @@ pub struct EscapeDefault<'a> { pub(super) inner: FlatMap, char_mod::EscapeDefault, CharEscapeDefault>, } +impl<'a> EscapeDefault<'a> { + /// Disassembles the iterator into its internal state. + /// Used by alloc for an optimized to_string impl. + #[unstable(feature = "std_internals", issue = "none")] + pub fn into_parts( + self, + ) -> (Option>, Option>, Option>) + { + self.inner.into_parts() + } +} + /// The return type of [`str::escape_unicode`]. #[stable(feature = "str_escape", since = "1.34.0")] #[derive(Clone, Debug)] From 195573d6fa578eec2d8cc05b6060e3b7eff1547f Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 25 Jul 2026 18:51:08 +0200 Subject: [PATCH 63/80] partially revert "Optimize escape_string_symbol()" This reverts commit bfe8ccb68d88b5cc2007d8cfbc3f365abfae4fd0. --- compiler/rustc_ast/src/util/literal.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index b5d8fc821ae0e..2b264a6843837 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -1,6 +1,5 @@ //! Code related to parsing literals. -use std::fmt::Write as _; use std::{ascii, fmt, str}; use rustc_literal_escaper::{ @@ -38,19 +37,8 @@ pub fn escape_string_symbol(symbol: Symbol) -> Symbol { let mut escaped = String::with_capacity(s.len() + 1); escaped.push_str(prefix); - // Don't use escape_default() here, because using it is slower than escaping manually. - for c in suffix.chars() { - match c { - '\t' => escaped.push_str("\\t"), - '\r' => escaped.push_str("\\r"), - '\n' => escaped.push_str("\\n"), - '\\' => escaped.push_str("\\\\"), - '\'' => escaped.push_str("\\'"), - '\"' => escaped.push_str("\\\""), - '\x20'..='\x7e' => escaped.push(c), - c => write!(escaped, "\\u{{{:x}}}", c as u32).unwrap(), - } - } + escaped.extend(suffix.escape_default()); + Symbol::intern(&escaped) } From 678690c1b500e641b75c7046f67e5ef83a3369c6 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 24 Oct 2026 04:29:51 +0000 Subject: [PATCH 64/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 9cd159cfa5b56207cfecf70fd8bd213558243c23. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index fde81e3cc035b..3a20250c1292e 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -7189d39d9ce936881b8132c6addeaf3e295cd455 +9cd159cfa5b56207cfecf70fd8bd213558243c23 From 3872943b3bbbcd1ec47e3b0ee9e30f49405f514e Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 24 Oct 2026 04:32:16 +0000 Subject: [PATCH 65/80] fmt --- .../miri/tests/pass/function_calls/abi_compat.rs | 11 +---------- .../tests/pass/intrinsics/fmuladd_nondeterministic.rs | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index 70509b2827270..e78c49457280f 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -98,16 +98,7 @@ fn test_abi_cfi_encoding() { pub x: i32, pub y: i32, } - test_abi_compat( - CfiEncoded1 { - x: 2, - y: 6, - }, - CfiEncoded2 { - x: 2, - y: 6, - }, - ); + test_abi_compat(CfiEncoded1 { x: 2, y: 6 }, CfiEncoded2 { x: 2, y: 6 }); } fn main() { diff --git a/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs index 21c47e73e8c1b..877016ab41704 100644 --- a/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs +++ b/src/tools/miri/tests/pass/intrinsics/fmuladd_nondeterministic.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics, portable_simd)] -use std::intrinsics::simd::simd_relaxed_fma; use std::intrinsics::fmuladd; +use std::intrinsics::simd::simd_relaxed_fma; use std::simd::prelude::*; #[path = "../../utils/mod.rs"] From 8fac7ef59f0702b308fffbc1500ed37f8ae01b1d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 24 Oct 2026 10:02:04 +0200 Subject: [PATCH 66/80] update lockfile --- Cargo.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 26b1570995407..ec38d26d32838 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1318,6 +1318,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "ena" version = "0.14.4" @@ -2551,6 +2562,7 @@ dependencies = [ "chrono-tz", "colored", "directories", + "emmy_dap_types", "genmc-sys", "getrandom 0.4.3", "ipc-channel", From bfb9ed34f16a8eff227c83e5cc723981344fc029 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 24 Oct 2026 10:15:06 +0200 Subject: [PATCH 67/80] bootstrap: make priroda check and test again --- src/bootstrap/src/core/build_steps/check.rs | 3 +-- src/bootstrap/src/core/build_steps/test.rs | 5 +++-- .../src/core/builder/cli_paths/snapshots/x_check.snap | 3 --- .../snapshots/x_check_compiletest_include_default_paths.snap | 3 --- .../src/core/builder/cli_paths/snapshots/x_fix.snap | 3 --- src/bootstrap/src/core/builder/mod.rs | 1 - src/bootstrap/src/core/builder/tests.rs | 1 - src/tools/miri/.github/workflows/ci.yml | 4 +--- 8 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index aebd7fee1f231..fc7d0a386a60f 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -856,10 +856,9 @@ tool_check_step!(Clippy { path: "src/tools/clippy", mode: Mode::ToolRustcPrivate tool_check_step!(Miri { path: "src/tools/miri", mode: Mode::ToolRustcPrivate, - enable_features: ["check_only"], + enable_features: ["check_only", "priroda"], }); tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri", mode: Mode::ToolRustcPrivate }); -tool_check_step!(Priroda { path: "src/tools/miri/priroda", mode: Mode::ToolRustcPrivate }); tool_check_step!(Rustfmt { path: "src/tools/rustfmt", mode: Mode::ToolRustcPrivate }); tool_check_step!(RustAnalyzer { path: "src/tools/rust-analyzer", diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f329f61c100c8..21ad727defe1f 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -915,10 +915,11 @@ impl CommandLineStep for Priroda { Mode::ToolRustcPrivate, host, Kind::Test, - "src/tools/miri/priroda", + "src/tools/miri", SourceType::InTree, - &[], + &["priroda".into()], ); + cargo.args(["--test", "priroda"]); cargo.add_rustc_lib_path(builder); diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap index d5754846441a2..8f9abbf0e3f5a 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check.snap @@ -103,9 +103,6 @@ expression: check [Check] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Check] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Check] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap index cb48844356717..526aa31cc74af 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_check_compiletest_include_default_paths.snap @@ -103,9 +103,6 @@ expression: check compiletest --include-default-paths [Check] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Check] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Check] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap index 8ed386a4f219a..01f0ef3f05d67 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_fix.snap @@ -103,9 +103,6 @@ expression: fix [Fix] check::CargoMiri targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miri/cargo-miri}) -[Fix] check::Priroda - targets: [x86_64-unknown-linux-gnu] - - Set({src/tools/miri/priroda}) [Fix] check::MiroptTestTools targets: [x86_64-unknown-linux-gnu] - Set({src/tools/miropt-test-tools}) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index cc0b32d7bb863..1359733089eee 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -853,7 +853,6 @@ impl<'a> Builder<'a> { check::Clippy, check::Miri, check::CargoMiri, - check::Priroda, check::MiroptTestTools, check::Rustfmt, check::RustAnalyzer, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index bebf17d84618a..ecdb057fd43b7 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1664,7 +1664,6 @@ mod snapshot { [check] rustc 1 -> Clippy 2 [check] rustc 1 -> Miri 2 [check] rustc 1 -> CargoMiri 2 - [check] rustc 1 -> Priroda 2 [check] rustc 1 -> Rustfmt 2 [check] rustc 1 -> RustAnalyzer 2 [check] rustc 1 -> TestFloatParse 2 diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index dd6d684dd5959..b4be293e4ab45 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -183,9 +183,7 @@ jobs: - name: check build run: | cd ../rust # ./x does not seem to like being invoked from elsewhere - # FIXME: skipping the priroda check as it is temporarily broken while - # we reorganize the Miri crate. - ./x check miri cargo-miri + ./x check src/tools/miri # This job is intentionally separate from `test` so that the Priroda test # suite is exercised on its own (it is gated behind the `priroda` feature of From cbf6e4a2b8441e17aa6c74aa2918a5be332dd7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 24 Oct 2026 16:45:30 +0200 Subject: [PATCH 68/80] Do not checkout Cargo for bootstrap tests --- src/bootstrap/src/core/build_steps/test.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f329f61c100c8..563c38f52fb7a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4155,9 +4155,6 @@ impl CommandLineStep for Bootstrap { let build_compiler = builder.compiler(0, host); let record_failed_tests = builder.ensure(SetupFailedTestsFile); - // Some tests require cargo submodule to be present. - builder.sess.require_submodule("src/tools/cargo", None); - let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, From 59a2d735f5aba67ab7f0010d2f9355b0a3fe0cc7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 23 Oct 2026 21:32:28 +0200 Subject: [PATCH 69/80] Fix path setup for panic messages --- .../build_system/src/build.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index 9a4e98d3bb24d..cc87c660520d8 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -6,7 +6,8 @@ use std::path::{Path, PathBuf}; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ - create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, walk_dir, + create_dir, get_sysroot_dir, run_command, run_command_with_output_and_env, rustc_version_info, + walk_dir, }; #[derive(Default)] @@ -137,17 +138,16 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); - // Record the sysroot sources under the path the `rust-src` component uses, which is where - // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests - // print the build path where they expect `$SRC_DIR`. - let sysroot_source_dir = lib_path.join("rustlib/src/rust"); + // Remap both source spans and embedded panic locations to the standard virtual path. + let sysroot_source_dir = start_dir.join("sysroot_src"); + let rustc_commit = rustc_version_info(env.get("RUSTC").map(String::as_str))? + .commit_hash + .ok_or_else(|| "Couldn't retrieve rustc commit hash".to_string())?; + // Cargo passes workspace-relative filenames, including those used by panic locations. + // Also: the `==` is intentional! The first `=` is "FROM" and the second `=` is for "TO". + rustflags.push_str(&format!(" --remap-path-prefix==/rustc/{rustc_commit}/library")); rustflags.push_str(&format!( - " --remap-path-prefix={library_dir}={sysroot_source_dir}", - library_dir = std::path::absolute(&library_dir) - .map_err(|error| format!( - "Failed to get the absolute path of the sysroot sources: {error:?}" - ))? - .display(), + " --remap-path-prefix={sysroot_source_dir}=/rustc/{rustc_commit}", sysroot_source_dir = std::path::absolute(&sysroot_source_dir) .map_err(|error| format!( "Failed to get the absolute path of the sysroot sources: {error:?}" From bd872a031ecd117e1d1385e0678eb0d2c71e3446 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 24 Oct 2026 17:08:16 +0200 Subject: [PATCH 70/80] Remove non-failing ui test from the failing ui tests list --- compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index 84a0bbd93f07f..e35d9916247f1 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -11,7 +11,6 @@ tests/ui/process/nofile-limit.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs tests/ui/statics/const_generics.rs -tests/ui/thir-print/offset_of.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs From e02f4fd1871478349435d3279ba54caccd8f069f Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Sat, 24 Oct 2026 11:12:54 -0400 Subject: [PATCH 71/80] Prepare for merging from rust-lang/rust This updates the rust-toolchain.toml file to nightly-2026-10-24 (48f4b05538b8eca412137042d9647a61e692c6b3). --- compiler/rustc_codegen_gcc/rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_gcc/rust-toolchain.toml b/compiler/rustc_codegen_gcc/rust-toolchain.toml index 7d0a982195aa0..5750754596ba2 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain.toml +++ b/compiler/rustc_codegen_gcc/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-10-19" +channel = "nightly-2026-10-24" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] From b1cdfffc9e5f7876d45c4b349e6cf791a2dc9f29 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 15 Sep 2026 14:14:20 +0200 Subject: [PATCH 72/80] Enable `--generate-link-to-definition` rustdoc option on std docs --- library/std/src/lib.rs | 13 ++ library/std/src/os/darwin/fs.rs | 94 ++++++-- library/std/src/os/linux/process.rs | 36 ++- library/std/src/os/wasi/fs.rs | 24 +- library/std/src/os/windows/ffi.rs | 8 +- library/std/src/os/windows/fs.rs | 108 ++++++--- library/std/src/os/windows/io/handle.rs | 124 ++++++---- library/std/src/os/windows/io/raw.rs | 92 +++++--- library/std/src/os/windows/io/socket.rs | 106 +++++---- library/std/src/os/windows/net/addr.rs | 26 ++- library/std/src/os/windows/net/listener.rs | 16 +- library/std/src/os/windows/net/stream.rs | 40 +++- library/std/src/os/windows/process.rs | 256 +++++++++++++-------- library/std/src/os/windows/thread.rs | 8 +- library/std/src/sys/process/unix/unix.rs | 12 +- src/bootstrap/src/core/build_steps/doc.rs | 9 +- 16 files changed, 648 insertions(+), 324 deletions(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 8ed3a46bf3189..69bdc3256a9b7 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -449,6 +449,19 @@ since = "1.90.0" )] +// Macro used when rendering with `rustdoc` to replace code with `loop {}`. +#[allow(unused_macros)] +macro_rules! no_code { + ($($code:tt)*) => { + #[cfg(not(doc))] + { + $($code)* + } + #[cfg(doc)] + loop {} + } +} + // The Rust prelude // The compiler expects the prelude definition to be defined before its use statement. pub mod prelude; diff --git a/library/std/src/os/darwin/fs.rs b/library/std/src/os/darwin/fs.rs index b41391f19dccf..5349892dbd1c1 100644 --- a/library/std/src/os/darwin/fs.rs +++ b/library/std/src/os/darwin/fs.rs @@ -89,70 +89,114 @@ impl MetadataExt for Metadata { #[allow(deprecated)] #[cfg(any(doc, target_os = "macos", target_os = "ios"))] fn as_raw_stat(&self) -> &super::raw::stat { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const super::raw::stat) } + no_code! { + unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const super::raw::stat) } + } } fn st_dev(&self) -> u64 { - self.as_inner().as_inner().st_dev as u64 + no_code! { + self.as_inner().as_inner().st_dev as u64 + } } fn st_ino(&self) -> u64 { - self.as_inner().as_inner().st_ino as u64 + no_code! { + self.as_inner().as_inner().st_ino as u64 + } } fn st_mode(&self) -> u32 { - self.as_inner().as_inner().st_mode as u32 + no_code! { + self.as_inner().as_inner().st_mode as u32 + } } fn st_nlink(&self) -> u64 { - self.as_inner().as_inner().st_nlink as u64 + no_code! { + self.as_inner().as_inner().st_nlink as u64 + } } fn st_uid(&self) -> u32 { - self.as_inner().as_inner().st_uid as u32 + no_code! { + self.as_inner().as_inner().st_uid as u32 + } } fn st_gid(&self) -> u32 { - self.as_inner().as_inner().st_gid as u32 + no_code! { + self.as_inner().as_inner().st_gid as u32 + } } fn st_rdev(&self) -> u64 { - self.as_inner().as_inner().st_rdev as u64 + no_code! { + self.as_inner().as_inner().st_rdev as u64 + } } fn st_size(&self) -> u64 { - self.as_inner().as_inner().st_size as u64 + no_code! { + self.as_inner().as_inner().st_size as u64 + } } fn st_atime(&self) -> i64 { - self.as_inner().as_inner().st_atime as i64 + no_code! { + self.as_inner().as_inner().st_atime as i64 + } } fn st_atime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_atime_nsec as i64 + no_code! { + self.as_inner().as_inner().st_atime_nsec as i64 + } } fn st_mtime(&self) -> i64 { - self.as_inner().as_inner().st_mtime as i64 + no_code! { + self.as_inner().as_inner().st_mtime as i64 + } } fn st_mtime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_mtime_nsec as i64 + no_code! { + self.as_inner().as_inner().st_mtime_nsec as i64 + } } fn st_ctime(&self) -> i64 { - self.as_inner().as_inner().st_ctime as i64 + no_code! { + self.as_inner().as_inner().st_ctime as i64 + } } fn st_ctime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_ctime_nsec as i64 + no_code! { + self.as_inner().as_inner().st_ctime_nsec as i64 + } } fn st_birthtime(&self) -> i64 { - self.as_inner().as_inner().st_birthtime as i64 + no_code! { + self.as_inner().as_inner().st_birthtime as i64 + } } fn st_birthtime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_birthtime_nsec as i64 + no_code! { + self.as_inner().as_inner().st_birthtime_nsec as i64 + } } fn st_blksize(&self) -> u64 { - self.as_inner().as_inner().st_blksize as u64 + no_code! { + self.as_inner().as_inner().st_blksize as u64 + } } fn st_blocks(&self) -> u64 { - self.as_inner().as_inner().st_blocks as u64 + no_code! { + self.as_inner().as_inner().st_blocks as u64 + } } fn st_gen(&self) -> u32 { - self.as_inner().as_inner().st_gen as u32 + no_code! { + self.as_inner().as_inner().st_gen as u32 + } } fn st_flags(&self) -> u32 { - self.as_inner().as_inner().st_flags as u32 + no_code! { + self.as_inner().as_inner().st_flags as u32 + } } fn st_lspare(&self) -> u32 { - self.as_inner().as_inner().st_lspare as u32 + no_code! { + self.as_inner().as_inner().st_lspare as u32 + } } #[cfg(target_os = "macos")] fn st_qspare(&self) -> [u64; 2] { @@ -172,7 +216,9 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { fn set_created(mut self, t: SystemTime) -> Self { - self.as_inner_mut().set_created(t.into_inner()); - self + no_code! { + self.as_inner_mut().set_created(t.into_inner()); + self + } } } diff --git a/library/std/src/os/linux/process.rs b/library/std/src/os/linux/process.rs index e0ddaa85cb82e..26f2057b6d942 100644 --- a/library/std/src/os/linux/process.rs +++ b/library/std/src/os/linux/process.rs @@ -62,7 +62,9 @@ impl PidFd { /// /// [`Child::kill`]: process::Child::kill pub fn kill(&self) -> Result<()> { - self.inner.kill() + no_code! { + self.inner.kill() + } } /// Waits for the child to exit completely, returning the status that it exited with. @@ -75,7 +77,9 @@ impl PidFd { /// /// [`Child::wait`]: process::Child::wait pub fn wait(&self) -> Result { - self.inner.wait().map(FromInner::from_inner) + no_code! { + self.inner.wait().map(FromInner::from_inner) + } } /// Attempts to collect the exit status of the child if it has already exited. @@ -85,7 +89,9 @@ impl PidFd { /// /// [`Child::try_wait`]: process::Child::try_wait pub fn try_wait(&self) -> Result> { - Ok(self.inner.try_wait()?.map(FromInner::from_inner)) + no_code! { + Ok(self.inner.try_wait()?.map(FromInner::from_inner)) + } } } @@ -111,37 +117,49 @@ impl IntoInner for PidFd { impl AsRawFd for PidFd { #[inline] fn as_raw_fd(&self) -> RawFd { - self.as_inner().as_inner().as_raw_fd() + no_code! { + self.as_inner().as_inner().as_raw_fd() + } } } impl FromRawFd for PidFd { unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self::from_inner(InnerPidFd::from_raw_fd(fd)) + no_code! { + Self::from_inner(InnerPidFd::from_raw_fd(fd)) + } } } impl IntoRawFd for PidFd { fn into_raw_fd(self) -> RawFd { - self.into_inner().into_inner().into_raw_fd() + no_code! { + self.into_inner().into_inner().into_raw_fd() + } } } impl AsFd for PidFd { fn as_fd(&self) -> BorrowedFd<'_> { - self.as_inner().as_inner().as_fd() + no_code! { + self.as_inner().as_inner().as_fd() + } } } impl From for PidFd { fn from(fd: OwnedFd) -> Self { - Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) + no_code! { + Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) + } } } impl From for OwnedFd { fn from(pid_fd: PidFd) -> Self { - pid_fd.into_inner().into_inner().into_inner() + no_code! { + pid_fd.into_inner().into_inner().into_inner() + } } } diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 369e5d4b0fb9d..96a18ff934ff0 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -334,13 +334,19 @@ pub trait MetadataExt { impl MetadataExt for fs::Metadata { fn dev(&self) -> u64 { - self.as_inner().as_inner().st_dev + no_code! { + self.as_inner().as_inner().st_dev + } } fn ino(&self) -> u64 { - self.as_inner().as_inner().st_ino + no_code! { + self.as_inner().as_inner().st_ino + } } fn nlink(&self) -> u64 { - self.as_inner().as_inner().st_nlink + no_code! { + self.as_inner().as_inner().st_nlink + } } } @@ -359,13 +365,19 @@ pub trait FileTypeExt { impl FileTypeExt for fs::FileType { fn is_block_device(&self) -> bool { - self.as_inner().is(libc::S_IFBLK) + no_code! { + self.as_inner().is(libc::S_IFBLK) + } } fn is_char_device(&self) -> bool { - self.as_inner().is(libc::S_IFCHR) + no_code! { + self.as_inner().is(libc::S_IFCHR) + } } fn is_socket(&self) -> bool { - self.as_inner().is(libc::S_IFSOCK) + no_code! { + self.as_inner().is(libc::S_IFSOCK) + } } } diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs index b27a116f60388..d21dace2346ce 100644 --- a/library/std/src/os/windows/ffi.rs +++ b/library/std/src/os/windows/ffi.rs @@ -89,7 +89,9 @@ pub impl(self) trait OsStringExt { #[stable(feature = "rust1", since = "1.0.0")] impl OsStringExt for OsString { fn from_wide(wide: &[u16]) -> OsString { - FromInner::from_inner(Buf { inner: Wtf8Buf::from_wide(wide) }) + no_code! { + FromInner::from_inner(Buf { inner: Wtf8Buf::from_wide(wide) }) + } } } @@ -126,7 +128,9 @@ pub impl(self) trait OsStrExt { impl OsStrExt for OsStr { #[inline] fn encode_wide(&self) -> EncodeWide<'_> { - EncodeWide { inner: self.as_inner().inner.encode_wide() } + no_code! { + EncodeWide { inner: self.as_inner().inner.encode_wide() } + } } } diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 865cc90e83156..ec60272e8c89d 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -494,28 +494,38 @@ pub trait OpenOptionsExt { #[stable(feature = "open_options_ext", since = "1.10.0")] impl OpenOptionsExt for OpenOptions { fn access_mode(&mut self, access: u32) -> &mut OpenOptions { - self.as_inner_mut().access_mode(access); - self + no_code! { + self.as_inner_mut().access_mode(access); + self + } } fn share_mode(&mut self, share: u32) -> &mut OpenOptions { - self.as_inner_mut().share_mode(share); - self + no_code! { + self.as_inner_mut().share_mode(share); + self + } } fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions { - self.as_inner_mut().custom_flags(flags); - self + no_code! { + self.as_inner_mut().custom_flags(flags); + self + } } fn attributes(&mut self, attributes: u32) -> &mut OpenOptions { - self.as_inner_mut().attributes(attributes); - self + no_code! { + self.as_inner_mut().attributes(attributes); + self + } } fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions { - self.as_inner_mut().security_qos_flags(flags); - self + no_code! { + self.as_inner_mut().security_qos_flags(flags); + self + } } } @@ -537,13 +547,17 @@ pub impl(self) trait OpenOptionsExt2 { #[unstable(feature = "windows_freeze_file_times", issue = "149715")] impl OpenOptionsExt2 for OpenOptions { fn freeze_last_access_time(&mut self, freeze: bool) -> &mut Self { - self.as_inner_mut().freeze_last_access_time(freeze); - self + no_code! { + self.as_inner_mut().freeze_last_access_time(freeze); + self + } } fn freeze_last_write_time(&mut self, freeze: bool) -> &mut Self { - self.as_inner_mut().freeze_last_write_time(freeze); - self + no_code! { + self.as_inner_mut().freeze_last_write_time(freeze); + self + } } } @@ -595,7 +609,9 @@ pub impl(self) trait PermissionsExt { #[unstable(feature = "windows_permissions_ext", issue = "152956")] impl PermissionsExt for fs::Permissions { fn file_attributes(&self) -> u32 { - self.as_inner().file_attributes() + no_code! { + self.as_inner().file_attributes() + } } fn set_file_attributes(&mut self, mask: u32) { @@ -806,31 +822,49 @@ pub trait MetadataExt { #[stable(feature = "metadata_ext", since = "1.1.0")] impl MetadataExt for Metadata { fn file_attributes(&self) -> u32 { - self.as_inner().attrs() + no_code! { + self.as_inner().attrs() + } } fn creation_time(&self) -> u64 { - self.as_inner().created_u64() + no_code! { + self.as_inner().created_u64() + } } fn last_access_time(&self) -> u64 { - self.as_inner().accessed_u64() + no_code! { + self.as_inner().accessed_u64() + } } fn last_write_time(&self) -> u64 { - self.as_inner().modified_u64() + no_code! { + self.as_inner().modified_u64() + } } fn file_size(&self) -> u64 { - self.as_inner().size() + no_code! { + self.as_inner().size() + } } fn volume_serial_number(&self) -> Option { - self.as_inner().volume_serial_number() + no_code! { + self.as_inner().volume_serial_number() + } } fn number_of_links(&self) -> Option { - self.as_inner().number_of_links() + no_code! { + self.as_inner().number_of_links() + } } fn file_index(&self) -> Option { - self.as_inner().file_index() + no_code! { + self.as_inner().file_index() + } } fn change_time(&self) -> Option { - self.as_inner().changed_u64() + no_code! { + self.as_inner().changed_u64() + } } } @@ -850,10 +884,14 @@ pub impl(self) trait FileTypeExt { #[stable(feature = "windows_file_type_ext", since = "1.64.0")] impl FileTypeExt for fs::FileType { fn is_symlink_dir(&self) -> bool { - self.as_inner().is_symlink_dir() + no_code! { + self.as_inner().is_symlink_dir() + } } fn is_symlink_file(&self) -> bool { - self.as_inner().is_symlink_file() + no_code! { + self.as_inner().is_symlink_file() + } } } @@ -868,8 +906,10 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { fn set_created(mut self, t: SystemTime) -> Self { - self.as_inner_mut().set_created(t.into_inner()); - self + no_code! { + self.as_inner_mut().set_created(t.into_inner()); + self + } } } @@ -912,7 +952,9 @@ impl FileTimesExt for fs::FileTimes { #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_file")] pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) + no_code! { + sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) + } } /// Creates a new symlink to a directory on the filesystem. @@ -954,7 +996,9 @@ pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io: #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_dir")] pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) + no_code! { + sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) + } } /// Creates a junction point. @@ -966,5 +1010,7 @@ pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io:: /// If either path is not a local file path then this will fail. #[unstable(feature = "junction_point", issue = "121709")] pub fn junction_point, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - sys::fs::junction_point(original.as_ref(), link.as_ref()) + no_code! { + sys::fs::junction_point(original.as_ref(), link.as_ref()) + } } diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index 01bd36155b110..b901c5e8bec19 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -173,9 +173,11 @@ impl TryFrom for OwnedHandle { impl Drop for HandleOrNull { #[inline] fn drop(&mut self) { - if self.is_valid() { - unsafe { - let _ = sys::c::CloseHandle(self.0); + no_code! { + if self.is_valid() { + unsafe { + let _ = sys::c::CloseHandle(self.0); + } } } } @@ -195,7 +197,9 @@ impl BorrowedHandle<'_> { /// object as the existing `BorrowedHandle` instance. #[stable(feature = "io_safety", since = "1.63.0")] pub fn try_clone_to_owned(&self) -> io::Result { - self.duplicate(0, false, sys::c::DUPLICATE_SAME_ACCESS) + no_code! { + self.duplicate(0, false, sys::c::DUPLICATE_SAME_ACCESS) + } } pub(crate) fn duplicate( @@ -204,30 +208,32 @@ impl BorrowedHandle<'_> { inherit: bool, options: u32, ) -> io::Result { - let handle = self.as_raw_handle(); - - // `Stdin`, `Stdout`, and `Stderr` can all hold null handles, such as - // in a process with a detached console. `DuplicateHandle` would fail - // if we passed it a null handle, but we can treat null as a valid - // handle which doesn't do any I/O, and allow it to be duplicated. - if handle.is_null() { - return unsafe { Ok(OwnedHandle::from_raw_handle(handle)) }; - } + no_code! { + let handle = self.as_raw_handle(); + + // `Stdin`, `Stdout`, and `Stderr` can all hold null handles, such as + // in a process with a detached console. `DuplicateHandle` would fail + // if we passed it a null handle, but we can treat null as a valid + // handle which doesn't do any I/O, and allow it to be duplicated. + if handle.is_null() { + return unsafe { Ok(OwnedHandle::from_raw_handle(handle)) }; + } - let mut ret = ptr::null_mut(); - cvt(unsafe { - let cur_proc = sys::c::GetCurrentProcess(); - sys::c::DuplicateHandle( - cur_proc, - handle, - cur_proc, - &mut ret, - access, - inherit as sys::c::BOOL, - options, - ) - })?; - unsafe { Ok(OwnedHandle::from_raw_handle(ret)) } + let mut ret = ptr::null_mut(); + cvt(unsafe { + let cur_proc = sys::c::GetCurrentProcess(); + sys::c::DuplicateHandle( + cur_proc, + handle, + cur_proc, + &mut ret, + access, + inherit as sys::c::BOOL, + options, + ) + })?; + unsafe { Ok(OwnedHandle::from_raw_handle(ret)) } + } } } @@ -251,9 +257,11 @@ impl TryFrom for OwnedHandle { impl Drop for HandleOrInvalid { #[inline] fn drop(&mut self) { - if self.is_valid() { - unsafe { - let _ = sys::c::CloseHandle(self.0); + no_code! { + if self.is_valid() { + unsafe { + let _ = sys::c::CloseHandle(self.0); + } } } } @@ -471,7 +479,9 @@ impl AsHandle for &mut T { impl AsHandle for crate::sync::Arc { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - (**self).as_handle() + no_code! { + (**self).as_handle() + } } } @@ -479,7 +489,9 @@ impl AsHandle for crate::sync::Arc { impl AsHandle for crate::rc::Rc { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - (**self).as_handle() + no_code! { + (**self).as_handle() + } } } @@ -487,7 +499,9 @@ impl AsHandle for crate::rc::Rc { impl AsHandle for crate::rc::UniqueRc { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - (**self).as_handle() + no_code! { + (**self).as_handle() + } } } @@ -495,7 +509,9 @@ impl AsHandle for crate::rc::UniqueRc { impl AsHandle for Box { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - (**self).as_handle() + no_code! { + (**self).as_handle() + } } } @@ -522,7 +538,9 @@ impl AsHandle for OwnedHandle { impl AsHandle for fs::File { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - self.as_inner().as_handle() + no_code! { + self.as_inner().as_handle() + } } } @@ -531,7 +549,9 @@ impl From for OwnedHandle { /// Takes ownership of a [`File`](fs::File)'s underlying file handle. #[inline] fn from(file: fs::File) -> OwnedHandle { - file.into_inner().into_inner().into_inner() + no_code! { + file.into_inner().into_inner().into_inner() + } } } @@ -540,7 +560,9 @@ impl From for fs::File { /// Returns a [`File`](fs::File) that takes ownership of the given handle. #[inline] fn from(owned: OwnedHandle) -> Self { - Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) + no_code! { + Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) + } } } @@ -655,48 +677,62 @@ impl AsHandle for crate::thread::JoinHandle { impl From> for OwnedHandle { #[inline] fn from(join_handle: crate::thread::JoinHandle) -> OwnedHandle { - join_handle.into_inner().into_handle().into_inner() + no_code! { + join_handle.into_inner().into_handle().into_inner() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeReader { fn as_handle(&self) -> BorrowedHandle<'_> { - self.0.as_handle() + no_code! { + self.0.as_handle() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { fn from(pipe: io::PipeReader) -> Self { - pipe.into_inner().into_inner() + no_code! { + pipe.into_inner().into_inner() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeWriter { fn as_handle(&self) -> BorrowedHandle<'_> { - self.0.as_handle() + no_code! { + self.0.as_handle() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { fn from(pipe: io::PipeWriter) -> Self { - pipe.into_inner().into_inner() + no_code! { + pipe.into_inner().into_inner() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeReader { fn from(owned_handle: OwnedHandle) -> Self { - Self::from_inner(FromInner::from_inner(owned_handle)) + no_code! { + Self::from_inner(FromInner::from_inner(owned_handle)) + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeWriter { fn from(owned_handle: OwnedHandle) -> Self { - Self::from_inner(FromInner::from_inner(owned_handle)) + no_code! { + Self::from_inner(FromInner::from_inner(owned_handle)) + } } } diff --git a/library/std/src/os/windows/io/raw.rs b/library/std/src/os/windows/io/raw.rs index d050c28832517..bd1601fc5c5fd 100644 --- a/library/std/src/os/windows/io/raw.rs +++ b/library/std/src/os/windows/io/raw.rs @@ -94,7 +94,9 @@ pub trait IntoRawHandle { impl AsRawHandle for fs::File { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().as_raw_handle() as RawHandle + no_code! { + self.as_inner().as_raw_handle() as RawHandle + } } } @@ -156,11 +158,13 @@ pub(super) fn stdio_handle(raw: RawHandle) -> RawHandle { impl FromRawHandle for fs::File { #[inline] unsafe fn from_raw_handle(handle: RawHandle) -> fs::File { - unsafe { - let handle = handle as sys::c::HANDLE; - fs::File::from_inner(sys::fs::File::from_inner(FromInner::from_inner( - OwnedHandle::from_raw_handle(handle), - ))) + no_code! { + unsafe { + let handle = handle as sys::c::HANDLE; + fs::File::from_inner(sys::fs::File::from_inner(FromInner::from_inner( + OwnedHandle::from_raw_handle(handle), + ))) + } } } } @@ -169,7 +173,9 @@ impl FromRawHandle for fs::File { impl IntoRawHandle for fs::File { #[inline] fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_raw_handle() as *mut _ + } } } @@ -238,21 +244,27 @@ pub trait IntoRawSocket { impl AsRawSocket for net::TcpStream { #[inline] fn as_raw_socket(&self) -> RawSocket { - self.as_inner().socket().as_raw_socket() + no_code! { + self.as_inner().socket().as_raw_socket() + } } } #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::TcpListener { #[inline] fn as_raw_socket(&self) -> RawSocket { - self.as_inner().socket().as_raw_socket() + no_code! { + self.as_inner().socket().as_raw_socket() + } } } #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::UdpSocket { #[inline] fn as_raw_socket(&self) -> RawSocket { - self.as_inner().socket().as_raw_socket() + no_code! { + self.as_inner().socket().as_raw_socket() + } } } @@ -260,9 +272,11 @@ impl AsRawSocket for net::UdpSocket { impl FromRawSocket for net::TcpStream { #[inline] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpStream { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::TcpStream::from_inner(sys::net::TcpStream::from_inner(sock)) + no_code! { + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::TcpStream::from_inner(sys::net::TcpStream::from_inner(sock)) + } } } } @@ -270,9 +284,11 @@ impl FromRawSocket for net::TcpStream { impl FromRawSocket for net::TcpListener { #[inline] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpListener { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::TcpListener::from_inner(sys::net::TcpListener::from_inner(sock)) + no_code! { + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::TcpListener::from_inner(sys::net::TcpListener::from_inner(sock)) + } } } } @@ -280,9 +296,11 @@ impl FromRawSocket for net::TcpListener { impl FromRawSocket for net::UdpSocket { #[inline] unsafe fn from_raw_socket(sock: RawSocket) -> net::UdpSocket { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::UdpSocket::from_inner(sys::net::UdpSocket::from_inner(sock)) + no_code! { + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::UdpSocket::from_inner(sys::net::UdpSocket::from_inner(sock)) + } } } } @@ -291,7 +309,9 @@ impl FromRawSocket for net::UdpSocket { impl IntoRawSocket for net::TcpStream { #[inline] fn into_raw_socket(self) -> RawSocket { - self.into_inner().into_socket().into_inner().into_raw_socket() + no_code! { + self.into_inner().into_socket().into_inner().into_raw_socket() + } } } @@ -299,7 +319,9 @@ impl IntoRawSocket for net::TcpStream { impl IntoRawSocket for net::TcpListener { #[inline] fn into_raw_socket(self) -> RawSocket { - self.into_inner().into_socket().into_inner().into_raw_socket() + no_code! { + self.into_inner().into_socket().into_inner().into_raw_socket() + } } } @@ -307,48 +329,62 @@ impl IntoRawSocket for net::TcpListener { impl IntoRawSocket for net::UdpSocket { #[inline] fn into_raw_socket(self) -> RawSocket { - self.into_inner().into_socket().into_inner().into_raw_socket() + no_code! { + self.into_inner().into_socket().into_inner().into_raw_socket() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeReader { fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() + no_code! { + self.0.as_raw_handle() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeReader { unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { - unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } + no_code! { + unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeReader { fn into_raw_handle(self) -> RawHandle { - self.0.into_raw_handle() + no_code! { + self.0.into_raw_handle() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeWriter { fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() + no_code! { + self.0.as_raw_handle() + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeWriter { unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { - unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } + no_code! { + unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } + } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeWriter { fn into_raw_handle(self) -> RawHandle { - self.0.into_raw_handle() + no_code! { + self.0.into_raw_handle() + } } } diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index d625a9ac52730..dd4476bac4bd0 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -79,14 +79,16 @@ impl OwnedSocket { #[allow(implicit_provenance_casts)] #[cfg(not(target_vendor = "uwp"))] pub(crate) fn set_no_inherit(&self) -> io::Result<()> { - cvt(unsafe { - sys::c::SetHandleInformation( - self.as_raw_socket() as sys::c::HANDLE, - sys::c::HANDLE_FLAG_INHERIT, - 0, - ) - }) - .map(drop) + no_code! { + cvt(unsafe { + sys::c::SetHandleInformation( + self.as_raw_socket() as sys::c::HANDLE, + sys::c::HANDLE_FLAG_INHERIT, + 0, + ) + }) + .map(drop) + } } #[cfg(target_vendor = "uwp")] @@ -100,35 +102,16 @@ impl BorrowedSocket<'_> { /// object as the existing `BorrowedSocket` instance. #[stable(feature = "io_safety", since = "1.63.0")] pub fn try_clone_to_owned(&self) -> io::Result { - let mut info = unsafe { mem::zeroed::() }; - let result = unsafe { - sys::c::WSADuplicateSocketW( - self.as_raw_socket() as sys::c::SOCKET, - sys::c::GetCurrentProcessId(), - &mut info, - ) - }; - sys::net::cvt(result)?; - let socket = unsafe { - sys::c::WSASocketW( - info.iAddressFamily, - info.iSocketType, - info.iProtocol, - &info, - 0, - sys::c::WSA_FLAG_OVERLAPPED | sys::c::WSA_FLAG_NO_HANDLE_INHERIT, - ) - }; - - if socket != sys::c::INVALID_SOCKET { - unsafe { Ok(OwnedSocket::from_raw_socket(socket as RawSocket)) } - } else { - let error = unsafe { sys::c::WSAGetLastError() }; - - if error != sys::c::WSAEPROTOTYPE && error != sys::c::WSAEINVAL { - return Err(io::Error::from_raw_os_error(error)); - } - + no_code! { + let mut info = unsafe { mem::zeroed::() }; + let result = unsafe { + sys::c::WSADuplicateSocketW( + self.as_raw_socket() as sys::c::SOCKET, + sys::c::GetCurrentProcessId(), + &mut info, + ) + }; + sys::net::cvt(result)?; let socket = unsafe { sys::c::WSASocketW( info.iAddressFamily, @@ -136,18 +119,39 @@ impl BorrowedSocket<'_> { info.iProtocol, &info, 0, - sys::c::WSA_FLAG_OVERLAPPED, + sys::c::WSA_FLAG_OVERLAPPED | sys::c::WSA_FLAG_NO_HANDLE_INHERIT, ) }; - if socket == sys::c::INVALID_SOCKET { - return Err(last_error()); - } - - unsafe { - let socket = OwnedSocket::from_raw_socket(socket as RawSocket); - socket.set_no_inherit()?; - Ok(socket) + if socket != sys::c::INVALID_SOCKET { + unsafe { Ok(OwnedSocket::from_raw_socket(socket as RawSocket)) } + } else { + let error = unsafe { sys::c::WSAGetLastError() }; + + if error != sys::c::WSAEPROTOTYPE && error != sys::c::WSAEINVAL { + return Err(io::Error::from_raw_os_error(error)); + } + + let socket = unsafe { + sys::c::WSASocketW( + info.iAddressFamily, + info.iSocketType, + info.iProtocol, + &info, + 0, + sys::c::WSA_FLAG_OVERLAPPED, + ) + }; + + if socket == sys::c::INVALID_SOCKET { + return Err(last_error()); + } + + unsafe { + let socket = OwnedSocket::from_raw_socket(socket as RawSocket); + socket.set_no_inherit()?; + Ok(socket) + } } } } @@ -155,7 +159,9 @@ impl BorrowedSocket<'_> { /// Returns the last error from the Windows socket interface. fn last_error() -> io::Error { - io::Error::from_raw_os_error(unsafe { sys::c::WSAGetLastError() }) + no_code! { + io::Error::from_raw_os_error(unsafe { sys::c::WSAGetLastError() }) + } } #[stable(feature = "io_safety", since = "1.63.0")] @@ -195,8 +201,10 @@ impl FromRawSocket for OwnedSocket { impl Drop for OwnedSocket { #[inline] fn drop(&mut self) { - unsafe { - let _ = sys::c::closesocket(self.socket.as_inner() as sys::c::SOCKET); + no_code! { + unsafe { + let _ = sys::c::closesocket(self.socket.as_inner() as sys::c::SOCKET); + } } } } diff --git a/library/std/src/os/windows/net/addr.rs b/library/std/src/os/windows/net/addr.rs index 5f6b93e56e723..614579762573c 100644 --- a/library/std/src/os/windows/net/addr.rs +++ b/library/std/src/os/windows/net/addr.rs @@ -131,20 +131,24 @@ impl SocketAddr { where P: AsRef, { - sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) + no_code! { + sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) + } } fn address(&self) -> AddressKind<'_> { - let len = self.len as usize - SUN_PATH_OFFSET; - let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; + no_code! { + let len = self.len as usize - SUN_PATH_OFFSET; + let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; - if len == 0 { - AddressKind::Unnamed - } else if self.addr.sun_path[0] == 0 { - AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) - } else { - AddressKind::Pathname(unsafe { - OsStr::from_encoded_bytes_unchecked(&path[..len - 1]).as_ref() - }) + if len == 0 { + AddressKind::Unnamed + } else if self.addr.sun_path[0] == 0 { + AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) + } else { + AddressKind::Pathname(unsafe { + OsStr::from_encoded_bytes_unchecked(&path[..len - 1]).as_ref() + }) + } } } diff --git a/library/std/src/os/windows/net/listener.rs b/library/std/src/os/windows/net/listener.rs index 19f5254e08bf9..5749747b3adee 100644 --- a/library/std/src/os/windows/net/listener.rs +++ b/library/std/src/os/windows/net/listener.rs @@ -164,7 +164,9 @@ impl UnixListener { /// } /// ``` pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + no_code! { + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + } } /// Creates a new independently owned handle to the underlying socket. @@ -326,21 +328,27 @@ impl<'a> Iterator for Incoming<'a> { impl AsRawSocket for UnixListener { #[inline] fn as_raw_socket(&self) -> RawSocket { - self.0.as_raw_socket() + no_code! { + self.0.as_raw_socket() + } } } impl FromRawSocket for UnixListener { #[inline] unsafe fn from_raw_socket(sock: RawSocket) -> Self { - UnixListener(unsafe { Socket::from_raw_socket(sock) }) + no_code! { + UnixListener(unsafe { Socket::from_raw_socket(sock) }) + } } } impl IntoRawSocket for UnixListener { #[inline] fn into_raw_socket(self) -> RawSocket { - self.0.into_raw_socket() + no_code! { + self.0.into_raw_socket() + } } } diff --git a/library/std/src/os/windows/net/stream.rs b/library/std/src/os/windows/net/stream.rs index c0f32e75411e9..1df01b8b527d5 100644 --- a/library/std/src/os/windows/net/stream.rs +++ b/library/std/src/os/windows/net/stream.rs @@ -127,7 +127,9 @@ impl UnixStream { /// } /// ``` pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + no_code! { + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) + } } /// Returns the socket address of the remote half of this connection. @@ -146,7 +148,9 @@ impl UnixStream { /// } /// ``` pub fn peer_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) + no_code! { + SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) + } } /// Returns the read timeout of this socket. @@ -167,7 +171,9 @@ impl UnixStream { /// } /// ``` pub fn read_timeout(&self) -> io::Result> { - self.0.timeout(SO_RCVTIMEO) + no_code! { + self.0.timeout(SO_RCVTIMEO) + } } /// Moves the socket into or out of nonblocking mode. @@ -231,7 +237,9 @@ impl UnixStream { /// } /// ``` pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { - self.0.set_timeout(dur, SO_RCVTIMEO) + no_code! { + self.0.set_timeout(dur, SO_RCVTIMEO) + } } /// Sets the write timeout for the socket. @@ -277,7 +285,9 @@ impl UnixStream { /// } /// ``` pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { - self.0.set_timeout(dur, SO_SNDTIMEO) + no_code! { + self.0.set_timeout(dur, SO_SNDTIMEO) + } } /// Shuts down the read, write, or both halves of this connection. @@ -368,7 +378,9 @@ impl UnixStream { /// } /// ``` pub fn write_timeout(&self) -> io::Result> { - self.0.timeout(SO_SNDTIMEO) + no_code! { + self.0.timeout(SO_SNDTIMEO) + } } } @@ -413,26 +425,34 @@ impl<'a> io::Write for &'a UnixStream { impl AsSocket for UnixStream { #[inline] fn as_socket(&self) -> BorrowedSocket<'_> { - self.0.as_socket() + no_code! { + self.0.as_socket() + } } } impl AsRawSocket for UnixStream { #[inline] fn as_raw_socket(&self) -> RawSocket { - self.0.as_raw_socket() + no_code! { + self.0.as_raw_socket() + } } } impl FromRawSocket for UnixStream { #[inline] unsafe fn from_raw_socket(sock: RawSocket) -> Self { - unsafe { UnixStream(Socket::from_raw_socket(sock)) } + no_code! { + unsafe { UnixStream(Socket::from_raw_socket(sock)) } + } } } impl IntoRawSocket for UnixStream { fn into_raw_socket(self) -> RawSocket { - self.0.into_raw_socket() + no_code! { + self.0.into_raw_socket() + } } } diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index f4b60f2ed8eca..568bd45f01da3 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -15,9 +15,11 @@ use crate::{io, marker, process, ptr, sys}; #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { unsafe fn from_raw_handle(handle: RawHandle) -> process::Stdio { - let handle = unsafe { sys::handle::Handle::from_raw_handle(handle as *mut _) }; - let io = sys::process::Stdio::Handle(handle); - process::Stdio::from_inner(io) + no_code! { + let handle = unsafe { sys::handle::Handle::from_raw_handle(handle as *mut _) }; + let io = sys::process::Stdio::Handle(handle); + process::Stdio::from_inner(io) + } } } @@ -26,9 +28,11 @@ impl From for process::Stdio { /// Takes ownership of a handle and returns a [`Stdio`](process::Stdio) /// that can attach a stream to it. fn from(handle: OwnedHandle) -> process::Stdio { - let handle = sys::handle::Handle::from_inner(handle); - let io = sys::process::Stdio::Handle(handle); - process::Stdio::from_inner(io) + no_code! { + let handle = sys::handle::Handle::from_inner(handle); + let io = sys::process::Stdio::Handle(handle); + process::Stdio::from_inner(io) + } } } @@ -36,7 +40,9 @@ impl From for process::Stdio { impl AsRawHandle for process::Child { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().handle().as_raw_handle() as *mut _ + no_code! { + self.as_inner().handle().as_raw_handle() as *mut _ + } } } @@ -44,14 +50,18 @@ impl AsRawHandle for process::Child { impl AsHandle for process::Child { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { - self.as_inner().handle().as_handle() + no_code! { + self.as_inner().handle().as_handle() + } } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::Child { fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_handle().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_handle().into_raw_handle() as *mut _ + } } } @@ -59,7 +69,9 @@ impl IntoRawHandle for process::Child { impl From for OwnedHandle { /// Takes ownership of a [`Child`](process::Child)'s process handle. fn from(child: process::Child) -> OwnedHandle { - child.into_inner().into_handle().into_inner() + no_code! { + child.into_inner().into_handle().into_inner() + } } } @@ -67,7 +79,9 @@ impl From for OwnedHandle { impl AsRawHandle for process::ChildStdin { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().handle().as_raw_handle() as *mut _ + no_code! { + self.as_inner().handle().as_raw_handle() as *mut _ + } } } @@ -75,7 +89,9 @@ impl AsRawHandle for process::ChildStdin { impl AsRawHandle for process::ChildStdout { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().handle().as_raw_handle() as *mut _ + no_code! { + self.as_inner().handle().as_raw_handle() as *mut _ + } } } @@ -83,28 +99,36 @@ impl AsRawHandle for process::ChildStdout { impl AsRawHandle for process::ChildStderr { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().handle().as_raw_handle() as *mut _ + no_code! { + self.as_inner().handle().as_raw_handle() as *mut _ + } } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdin { fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_handle().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_handle().into_raw_handle() as *mut _ + } } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdout { fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_handle().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_handle().into_raw_handle() as *mut _ + } } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStderr { fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_handle().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_handle().into_raw_handle() as *mut _ + } } } @@ -115,9 +139,11 @@ impl IntoRawHandle for process::ChildStderr { #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdin { fn from(handle: OwnedHandle) -> process::ChildStdin { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStdin::from_inner(pipe) + no_code! { + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStdin::from_inner(pipe) + } } } @@ -128,9 +154,11 @@ impl From for process::ChildStdin { #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdout { fn from(handle: OwnedHandle) -> process::ChildStdout { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStdout::from_inner(pipe) + no_code! { + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStdout::from_inner(pipe) + } } } @@ -141,9 +169,11 @@ impl From for process::ChildStdout { #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStderr { fn from(handle: OwnedHandle) -> process::ChildStderr { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStderr::from_inner(pipe) + no_code! { + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStderr::from_inner(pipe) + } } } @@ -159,7 +189,9 @@ pub impl(self) trait ExitStatusExt { #[stable(feature = "exit_status_from", since = "1.12.0")] impl ExitStatusExt for process::ExitStatus { fn from_raw(raw: u32) -> Self { - process::ExitStatus::from_inner(From::from(raw)) + no_code! { + process::ExitStatus::from_inner(From::from(raw)) + } } } @@ -391,28 +423,38 @@ pub impl(self) trait CommandExt { #[stable(feature = "windows_process_extensions", since = "1.16.0")] impl CommandExt for process::Command { fn creation_flags(&mut self, flags: u32) -> &mut process::Command { - self.as_inner_mut().creation_flags(flags); - self + no_code! { + self.as_inner_mut().creation_flags(flags); + self + } } fn desktop>(&mut self, desktop: S) -> &mut process::Command { - self.as_inner_mut().desktop(desktop.as_ref()); - self + no_code! { + self.as_inner_mut().desktop(desktop.as_ref()); + self + } } fn show_window(&mut self, cmd_show: u16) -> &mut process::Command { - self.as_inner_mut().show_window(Some(cmd_show)); - self + no_code! { + self.as_inner_mut().show_window(Some(cmd_show)); + self + } } fn force_quotes(&mut self, enabled: bool) -> &mut process::Command { - self.as_inner_mut().force_quotes(enabled); - self + no_code! { + self.as_inner_mut().force_quotes(enabled); + self + } } fn raw_arg>(&mut self, raw_text: S) -> &mut process::Command { - self.as_inner_mut().raw_arg(raw_text.as_ref()); - self + no_code! { + self.as_inner_mut().raw_arg(raw_text.as_ref()); + self + } } fn async_pipes(&mut self, always_async: bool) -> &mut process::Command { @@ -428,29 +470,39 @@ impl CommandExt for process::Command { &mut self, attribute_list: &ProcThreadAttributeList<'_>, ) -> io::Result { - self.as_inner_mut() - .spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list)) - .map(process::Child::from_inner) + no_code! { + self.as_inner_mut() + .spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list)) + .map(process::Child::from_inner) + } } fn startupinfo_fullscreen(&mut self, enabled: bool) -> &mut process::Command { - self.as_inner_mut().startupinfo_fullscreen(enabled); - self + no_code! { + self.as_inner_mut().startupinfo_fullscreen(enabled); + self + } } fn startupinfo_untrusted_source(&mut self, enabled: bool) -> &mut process::Command { - self.as_inner_mut().startupinfo_untrusted_source(enabled); - self + no_code! { + self.as_inner_mut().startupinfo_untrusted_source(enabled); + self + } } fn startupinfo_force_feedback(&mut self, enabled: Option) -> &mut process::Command { - self.as_inner_mut().startupinfo_force_feedback(enabled); - self + no_code! { + self.as_inner_mut().startupinfo_force_feedback(enabled); + self + } } fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command { - self.as_inner_mut().inherit_handles(inherit_handles); - self + no_code! { + self.as_inner_mut().inherit_handles(inherit_handles); + self + } } } @@ -464,7 +516,9 @@ pub impl(self) trait ChildExt { #[stable(feature = "windows_process_extensions_main_thread_handle", since = "1.100.0")] impl ChildExt for process::Child { fn main_thread_handle(&self) -> BorrowedHandle<'_> { - self.handle.main_thread_handle() + no_code! { + self.handle.main_thread_handle() + } } } @@ -484,7 +538,9 @@ pub impl(self) trait ExitCodeExt { #[stable(feature = "windows_process_exit_code_from", since = "CURRENT_RUSTC_VERSION")] impl ExitCodeExt for process::ExitCode { fn from_raw(raw: u32) -> Self { - process::ExitCode::from_inner(From::from(raw)) + no_code! { + process::ExitCode::from_inner(From::from(raw)) + } } } @@ -521,9 +577,11 @@ impl<'a> Drop for ProcThreadAttributeList<'a> { /// /// [1]: fn drop(&mut self) { - let lp_attribute_list = - self.attribute_list.as_mut_ptr().cast::(); - unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) } + no_code! { + let lp_attribute_list = + self.attribute_list.as_mut_ptr().cast::(); + unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) } + } } } @@ -667,58 +725,60 @@ impl<'a> ProcThreadAttributeListBuilder<'a> { /// Returns an error if the maximum number of attributes is exceeded /// or if there is an I/O error during initialization. pub fn finish(&self) -> io::Result> { - // To initialize our ProcThreadAttributeList, we need to determine - // how many bytes to allocate for it. The Windows API simplifies this - // process by allowing us to call `InitializeProcThreadAttributeList` - // with a null pointer to retrieve the required size. - let mut required_size = 0; - let Ok(attribute_count) = self.attributes.len().try_into() else { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "maximum number of ProcThreadAttributes exceeded", - )); - }; - unsafe { - sys::c::InitializeProcThreadAttributeList( - ptr::null_mut(), - attribute_count, - 0, - &mut required_size, - ) - }; - - let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice(); - - // Once we've allocated the necessary memory, it's safe to invoke - // `InitializeProcThreadAttributeList` to properly initialize the list. - sys::cvt(unsafe { - sys::c::InitializeProcThreadAttributeList( - attribute_list.as_mut_ptr().cast::(), - attribute_count, - 0, - &mut required_size, - ) - })?; - - // # Add our attributes to the buffer. - // It's theoretically possible for the attribute count to exceed a u32 - // value. Therefore, we ensure that we don't add more attributes than - // the buffer was initialized for. - for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) { + no_code! { + // To initialize our ProcThreadAttributeList, we need to determine + // how many bytes to allocate for it. The Windows API simplifies this + // process by allowing us to call `InitializeProcThreadAttributeList` + // with a null pointer to retrieve the required size. + let mut required_size = 0; + let Ok(attribute_count) = self.attributes.len().try_into() else { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "maximum number of ProcThreadAttributes exceeded", + )); + }; + unsafe { + sys::c::InitializeProcThreadAttributeList( + ptr::null_mut(), + attribute_count, + 0, + &mut required_size, + ) + }; + + let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice(); + + // Once we've allocated the necessary memory, it's safe to invoke + // `InitializeProcThreadAttributeList` to properly initialize the list. sys::cvt(unsafe { - sys::c::UpdateProcThreadAttribute( + sys::c::InitializeProcThreadAttributeList( attribute_list.as_mut_ptr().cast::(), + attribute_count, 0, - attribute, - value.ptr, - value.size, - ptr::null_mut(), - ptr::null_mut(), + &mut required_size, ) })?; - } - Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData }) + // # Add our attributes to the buffer. + // It's theoretically possible for the attribute count to exceed a u32 + // value. Therefore, we ensure that we don't add more attributes than + // the buffer was initialized for. + for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) { + sys::cvt(unsafe { + sys::c::UpdateProcThreadAttribute( + attribute_list.as_mut_ptr().cast::(), + 0, + attribute, + value.ptr, + value.size, + ptr::null_mut(), + ptr::null_mut(), + ) + })?; + } + + Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData }) + } } } diff --git a/library/std/src/os/windows/thread.rs b/library/std/src/os/windows/thread.rs index 1cdee44de4b72..137afc6af1a4c 100644 --- a/library/std/src/os/windows/thread.rs +++ b/library/std/src/os/windows/thread.rs @@ -12,7 +12,9 @@ use crate::thread; impl AsRawHandle for thread::JoinHandle { #[inline] fn as_raw_handle(&self) -> RawHandle { - self.as_inner().handle().as_raw_handle() as *mut _ + no_code! { + self.as_inner().handle().as_raw_handle() as *mut _ + } } } @@ -20,6 +22,8 @@ impl AsRawHandle for thread::JoinHandle { impl IntoRawHandle for thread::JoinHandle { #[inline] fn into_raw_handle(self) -> RawHandle { - self.into_inner().into_handle().into_raw_handle() as *mut _ + no_code! { + self.into_inner().into_handle().into_raw_handle() as *mut _ + } } } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index aa47fcb3360c1..5c709b47e626d 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -1315,11 +1315,13 @@ mod linux_child_ext { } fn into_pidfd(mut self) -> Result { - self.handle - .pidfd - .take() - .map(>::from_inner) - .ok_or_else(|| self) + no_code! { + self.handle + .pidfd + .take() + .map(>::from_inner) + .ok_or_else(|| self) + } } } } diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 34e9b354bddb2..ebc1a0fd3da89 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -710,7 +710,14 @@ impl CommandLineStep for Std { .expect("non-utf8 paths are unsupported"); let mut extra_args = match self.format { DocumentationFormat::Html => { - vec!["--markdown-css", "rust.css", "--markdown-no-toc", "--index-page", &index_page] + vec![ + "--markdown-css", + "rust.css", + "--markdown-no-toc", + "--index-page", + &index_page, + "--generate-link-to-definition", + ] } DocumentationFormat::Json => vec![], }; From 2bd0528e21ed64c5ce1206b8c7a3d6379cc8fdc2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Oct 2026 22:28:26 +0200 Subject: [PATCH 73/80] Create new builtin rustc macro `rustdoc_skip_body` and replace the `std::no_code` macro --- compiler/rustc_builtin_macros/src/lib.rs | 2 + .../src/rustdoc_skip_body.rs | 93 ++++++ compiler/rustc_span/src/symbol.rs | 1 + library/std/src/lib.rs | 13 - library/std/src/macros.rs | 13 + library/std/src/os/darwin/fs.rs | 118 +++---- library/std/src/os/linux/process.rs | 46 ++- library/std/src/os/wasi/fs.rs | 31 +- library/std/src/os/windows/ffi.rs | 11 +- library/std/src/os/windows/fs.rs | 132 ++++---- library/std/src/os/windows/io/handle.rs | 143 ++++----- library/std/src/os/windows/io/raw.rs | 111 +++---- library/std/src/os/windows/io/socket.rs | 111 ++++--- library/std/src/os/windows/net/addr.rs | 30 +- library/std/src/os/windows/net/listener.rs | 22 +- library/std/src/os/windows/net/stream.rs | 52 ++-- library/std/src/os/windows/process.rs | 287 ++++++++---------- library/std/src/os/windows/thread.rs | 11 +- library/std/src/sys/process/unix/unix.rs | 14 +- 19 files changed, 601 insertions(+), 640 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 4c3b42eaa0b22..aaaf67e34af4e 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -46,6 +46,7 @@ mod iter; mod log_syntax; mod offload; mod pattern_type; +mod rustdoc_skip_body; mod source_util; mod test; mod test_binder_constraints; @@ -125,6 +126,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { eii_shared_macro: eii::eii_shared_macro, global_allocator: global_allocator::expand, offload_kernel: offload::expand_kernel, + rustdoc_skip_body: rustdoc_skip_body::expand, test: test::expand_test, test_case: test::expand_test_case, unsafe_eii: eii::unsafe_eii, diff --git a/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs b/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs new file mode 100644 index 0000000000000..7649fc7d72fe0 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rustdoc_skip_body.rs @@ -0,0 +1,93 @@ +//! The expansion from a function when the code would not compile with rustdoc: so if `cfg(doc)`, +//! expands to a `loop {}`. + +use rustc_ast::token::{self, Delimiter, IdentKind}; +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; +use rustc_ast::{ + AssocItemKind, AttrArgs, AttrItem, Attribute, DelimArgs, ExprKind, Fn, ItemKind, MetaItem, + Safety, +}; +use rustc_expand::base::*; +use rustc_span::{Ident, Span, sym}; +use thin_vec::ThinVec; + +pub(crate) fn expand( + cx: &mut ExtCtxt<'_>, + attr_path_sp: Span, + meta_item: &MetaItem, + mut item: Annotatable, +) -> Vec { + if !meta_item.is_word() { + cx.dcx().struct_span_err(attr_path_sp, "`rustdoc_skip_body` doesn't take arguments").emit(); + } + let mut is_expected_item = false; + match item { + Annotatable::Item(ref mut item) => { + if let ItemKind::Fn(ref mut fn_) = item.kind { + expand_macro(cx, attr_path_sp, fn_); + is_expected_item = true; + } + } + Annotatable::AssocItem(ref mut item, _) => { + if let AssocItemKind::Fn(ref mut fn_) = item.kind { + expand_macro(cx, attr_path_sp, fn_); + is_expected_item = true; + } + } + _ => {} + } + if !is_expected_item { + cx.dcx() + .struct_span_err(attr_path_sp, "`rustdoc_skip_body` can only be used on functions") + .emit(); + } + vec![item] +} + +fn cfg_not_doc(cx: &mut ExtCtxt<'_>, sp: Span) -> Attribute { + // `not(doc)` + let not_doc = TokenStream::new(vec![ + TokenTree::token_alone(token::Ident(sym::not, IdentKind::Normal), sp), + TokenTree::Delimited( + DelimSpan::from_single(sp), + DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), + Delimiter::Parenthesis, + TokenStream::token_alone(token::Ident(sym::doc, IdentKind::Normal), sp), + ), + ]); + + cx.attr_nested( + AttrItem { + unsafety: Safety::Default, + path: cx.path_ident(sp, Ident::new(sym::cfg, sp)), + args: AttrArgs::Delimited(DelimArgs { + dspan: DelimSpan::from_single(sp), + delim: Delimiter::Parenthesis, + tokens: not_doc, + }), + span: sp, + }, + sp, + ) +} + +fn expand_macro(cx: &mut ExtCtxt<'_>, sp: Span, fn_: &mut Fn) { + let Some(old_body) = fn_.body.take() else { + // No body... Well, no possibility to not compile I guess so all good. + return; + }; + let mut old_body = cx.expr_block(old_body); + // We add on the function's body `#[cfg(not(doc))]`. + old_body.attrs.push(cfg_not_doc(cx, sp)); + + // We create the `#[cfg(doc)] loop {}` "block". + let mut loop_expr = cx.expr(sp, ExprKind::Loop(cx.block(sp, ThinVec::new()), None, sp)); + loop_expr.attrs.push(cx.attr_nested_word(sym::cfg, sym::doc, sp)); + + let mut stmts = ThinVec::new(); + stmts.push(cx.stmt_expr(loop_expr)); + stmts.push(cx.stmt_expr(old_body)); + + // We create a new function body containing both blocks. + fn_.body = Some(cx.block(sp, stmts)); +} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 89deeb44cf1b2..fc46005938b69 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1913,6 +1913,7 @@ symbols! { rustdoc, rustdoc_internals, rustdoc_missing_doc_code_examples, + rustdoc_skip_body, rustfmt, rvalue_static_promotion, rvector, diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 69bdc3256a9b7..8ed3a46bf3189 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -449,19 +449,6 @@ since = "1.90.0" )] -// Macro used when rendering with `rustdoc` to replace code with `loop {}`. -#[allow(unused_macros)] -macro_rules! no_code { - ($($code:tt)*) => { - #[cfg(not(doc))] - { - $($code)* - } - #[cfg(doc)] - loop {} - } -} - // The Rust prelude // The compiler expects the prelude definition to be defined before its use statement. pub mod prelude; diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index ea574fc4146e7..7dcdd2c6246b1 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -461,3 +461,16 @@ macro_rules! hash_map { map }} } + +/// Attribute macro to replace a function's body with `loop {}` under rustdoc. +/// +/// This allows target-specific functions to be documented on other targets, +/// even when their bodies reference items unavailable on the target used to +/// build the documentation. Use this with `#[cfg(any(doc, ...))]` to include +/// such functions in documentation builds. +/// +/// The function's signature must still be valid on the documentation target. +#[rustc_builtin_macro] +pub(crate) macro rustdoc_skip_body($item:item) { + /* compiler built-in */ +} diff --git a/library/std/src/os/darwin/fs.rs b/library/std/src/os/darwin/fs.rs index 5349892dbd1c1..ece59e96c7819 100644 --- a/library/std/src/os/darwin/fs.rs +++ b/library/std/src/os/darwin/fs.rs @@ -4,6 +4,7 @@ #![stable(feature = "metadata_ext", since = "1.1.0")] use crate::fs::{self, Metadata}; +use crate::macros::rustdoc_skip_body; use crate::sys::{AsInner, AsInnerMut, IntoInner}; use crate::time::SystemTime; @@ -88,115 +89,93 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] #[cfg(any(doc, target_os = "macos", target_os = "ios"))] + #[rustdoc_skip_body] fn as_raw_stat(&self) -> &super::raw::stat { - no_code! { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const super::raw::stat) } - } + unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const super::raw::stat) } } + #[rustdoc_skip_body] fn st_dev(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_dev as u64 - } + self.as_inner().as_inner().st_dev as u64 } + #[rustdoc_skip_body] fn st_ino(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_ino as u64 - } + self.as_inner().as_inner().st_ino as u64 } + #[rustdoc_skip_body] fn st_mode(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_mode as u32 - } + self.as_inner().as_inner().st_mode as u32 } + #[rustdoc_skip_body] fn st_nlink(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_nlink as u64 - } + self.as_inner().as_inner().st_nlink as u64 } + #[rustdoc_skip_body] fn st_uid(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_uid as u32 - } + self.as_inner().as_inner().st_uid as u32 } + #[rustdoc_skip_body] fn st_gid(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_gid as u32 - } + self.as_inner().as_inner().st_gid as u32 } + #[rustdoc_skip_body] fn st_rdev(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_rdev as u64 - } + self.as_inner().as_inner().st_rdev as u64 } + #[rustdoc_skip_body] fn st_size(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_size as u64 - } + self.as_inner().as_inner().st_size as u64 } + #[rustdoc_skip_body] fn st_atime(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_atime as i64 - } + self.as_inner().as_inner().st_atime as i64 } + #[rustdoc_skip_body] fn st_atime_nsec(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_atime_nsec as i64 - } + self.as_inner().as_inner().st_atime_nsec as i64 } + #[rustdoc_skip_body] fn st_mtime(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_mtime as i64 - } + self.as_inner().as_inner().st_mtime as i64 } + #[rustdoc_skip_body] fn st_mtime_nsec(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_mtime_nsec as i64 - } + self.as_inner().as_inner().st_mtime_nsec as i64 } + #[rustdoc_skip_body] fn st_ctime(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_ctime as i64 - } + self.as_inner().as_inner().st_ctime as i64 } + #[rustdoc_skip_body] fn st_ctime_nsec(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_ctime_nsec as i64 - } + self.as_inner().as_inner().st_ctime_nsec as i64 } + #[rustdoc_skip_body] fn st_birthtime(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_birthtime as i64 - } + self.as_inner().as_inner().st_birthtime as i64 } + #[rustdoc_skip_body] fn st_birthtime_nsec(&self) -> i64 { - no_code! { - self.as_inner().as_inner().st_birthtime_nsec as i64 - } + self.as_inner().as_inner().st_birthtime_nsec as i64 } + #[rustdoc_skip_body] fn st_blksize(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_blksize as u64 - } + self.as_inner().as_inner().st_blksize as u64 } + #[rustdoc_skip_body] fn st_blocks(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_blocks as u64 - } + self.as_inner().as_inner().st_blocks as u64 } + #[rustdoc_skip_body] fn st_gen(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_gen as u32 - } + self.as_inner().as_inner().st_gen as u32 } + #[rustdoc_skip_body] fn st_flags(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_flags as u32 - } + self.as_inner().as_inner().st_flags as u32 } + #[rustdoc_skip_body] fn st_lspare(&self) -> u32 { - no_code! { - self.as_inner().as_inner().st_lspare as u32 - } + self.as_inner().as_inner().st_lspare as u32 } #[cfg(target_os = "macos")] fn st_qspare(&self) -> [u64; 2] { @@ -215,10 +194,9 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { + #[rustdoc_skip_body] fn set_created(mut self, t: SystemTime) -> Self { - no_code! { - self.as_inner_mut().set_created(t.into_inner()); - self - } + self.as_inner_mut().set_created(t.into_inner()); + self } } diff --git a/library/std/src/os/linux/process.rs b/library/std/src/os/linux/process.rs index 26f2057b6d942..26a4ab4b4c603 100644 --- a/library/std/src/os/linux/process.rs +++ b/library/std/src/os/linux/process.rs @@ -5,6 +5,7 @@ #![unstable(feature = "linux_pidfd", issue = "82971")] use crate::io::Result; +use crate::macros::rustdoc_skip_body; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use crate::process::{self, ExitStatus}; use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -61,10 +62,9 @@ impl PidFd { /// races. But doing so will return an Error. /// /// [`Child::kill`]: process::Child::kill + #[rustdoc_skip_body] pub fn kill(&self) -> Result<()> { - no_code! { - self.inner.kill() - } + self.inner.kill() } /// Waits for the child to exit completely, returning the status that it exited with. @@ -76,10 +76,9 @@ impl PidFd { /// will return an Error. /// /// [`Child::wait`]: process::Child::wait + #[rustdoc_skip_body] pub fn wait(&self) -> Result { - no_code! { - self.inner.wait().map(FromInner::from_inner) - } + self.inner.wait().map(FromInner::from_inner) } /// Attempts to collect the exit status of the child if it has already exited. @@ -88,10 +87,9 @@ impl PidFd { /// to reap a child will return an ExitStatus, further attempts will return an Error. /// /// [`Child::try_wait`]: process::Child::try_wait + #[rustdoc_skip_body] pub fn try_wait(&self) -> Result> { - no_code! { - Ok(self.inner.try_wait()?.map(FromInner::from_inner)) - } + Ok(self.inner.try_wait()?.map(FromInner::from_inner)) } } @@ -116,50 +114,44 @@ impl IntoInner for PidFd { impl AsRawFd for PidFd { #[inline] + #[rustdoc_skip_body] fn as_raw_fd(&self) -> RawFd { - no_code! { - self.as_inner().as_inner().as_raw_fd() - } + self.as_inner().as_inner().as_raw_fd() } } impl FromRawFd for PidFd { + #[rustdoc_skip_body] unsafe fn from_raw_fd(fd: RawFd) -> Self { - no_code! { - Self::from_inner(InnerPidFd::from_raw_fd(fd)) - } + Self::from_inner(InnerPidFd::from_raw_fd(fd)) } } impl IntoRawFd for PidFd { + #[rustdoc_skip_body] fn into_raw_fd(self) -> RawFd { - no_code! { - self.into_inner().into_inner().into_raw_fd() - } + self.into_inner().into_inner().into_raw_fd() } } impl AsFd for PidFd { + #[rustdoc_skip_body] fn as_fd(&self) -> BorrowedFd<'_> { - no_code! { - self.as_inner().as_inner().as_fd() - } + self.as_inner().as_inner().as_fd() } } impl From for PidFd { + #[rustdoc_skip_body] fn from(fd: OwnedFd) -> Self { - no_code! { - Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) - } + Self::from_inner(InnerPidFd::from_inner(FileDesc::from_inner(fd))) } } impl From for OwnedFd { + #[rustdoc_skip_body] fn from(pid_fd: PidFd) -> Self { - no_code! { - pid_fd.into_inner().into_inner().into_inner() - } + pid_fd.into_inner().into_inner().into_inner() } } diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 96a18ff934ff0..811ab7ea8480b 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -12,6 +12,7 @@ use io::{Read, Write}; use crate::ffi::OsStr; use crate::fs::{self, File, OpenOptions}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::macros::rustdoc_skip_body; #[cfg(target_env = "p1")] use crate::os::fd::AsRawFd; use crate::path::Path; @@ -333,20 +334,17 @@ pub trait MetadataExt { } impl MetadataExt for fs::Metadata { + #[rustdoc_skip_body] fn dev(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_dev - } + self.as_inner().as_inner().st_dev } + #[rustdoc_skip_body] fn ino(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_ino - } + self.as_inner().as_inner().st_ino } + #[rustdoc_skip_body] fn nlink(&self) -> u64 { - no_code! { - self.as_inner().as_inner().st_nlink - } + self.as_inner().as_inner().st_nlink } } @@ -364,20 +362,17 @@ pub trait FileTypeExt { } impl FileTypeExt for fs::FileType { + #[rustdoc_skip_body] fn is_block_device(&self) -> bool { - no_code! { - self.as_inner().is(libc::S_IFBLK) - } + self.as_inner().is(libc::S_IFBLK) } + #[rustdoc_skip_body] fn is_char_device(&self) -> bool { - no_code! { - self.as_inner().is(libc::S_IFCHR) - } + self.as_inner().is(libc::S_IFCHR) } + #[rustdoc_skip_body] fn is_socket(&self) -> bool { - no_code! { - self.as_inner().is(libc::S_IFSOCK) - } + self.as_inner().is(libc::S_IFSOCK) } } diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs index d21dace2346ce..a4c6f1ad7df42 100644 --- a/library/std/src/os/windows/ffi.rs +++ b/library/std/src/os/windows/ffi.rs @@ -58,6 +58,7 @@ use alloc::wtf8::Wtf8Buf; use crate::ffi::{OsStr, OsString}; use crate::fmt; use crate::iter::FusedIterator; +use crate::macros::rustdoc_skip_body; use crate::sys::os_str::Buf; use crate::sys::{AsInner, FromInner}; @@ -88,10 +89,9 @@ pub impl(self) trait OsStringExt { #[stable(feature = "rust1", since = "1.0.0")] impl OsStringExt for OsString { + #[rustdoc_skip_body] fn from_wide(wide: &[u16]) -> OsString { - no_code! { - FromInner::from_inner(Buf { inner: Wtf8Buf::from_wide(wide) }) - } + FromInner::from_inner(Buf { inner: Wtf8Buf::from_wide(wide) }) } } @@ -127,10 +127,9 @@ pub impl(self) trait OsStrExt { #[stable(feature = "rust1", since = "1.0.0")] impl OsStrExt for OsStr { #[inline] + #[rustdoc_skip_body] fn encode_wide(&self) -> EncodeWide<'_> { - no_code! { - EncodeWide { inner: self.as_inner().inner.encode_wide() } - } + EncodeWide { inner: self.as_inner().inner.encode_wide() } } } diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index ec60272e8c89d..d931dd2476039 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -6,6 +6,7 @@ use crate::fs::{self, Metadata, OpenOptions, Permissions}; use crate::io::BorrowedCursor; +use crate::macros::rustdoc_skip_body; use crate::path::Path; use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; @@ -493,39 +494,34 @@ pub trait OpenOptionsExt { #[stable(feature = "open_options_ext", since = "1.10.0")] impl OpenOptionsExt for OpenOptions { + #[rustdoc_skip_body] fn access_mode(&mut self, access: u32) -> &mut OpenOptions { - no_code! { - self.as_inner_mut().access_mode(access); - self - } + self.as_inner_mut().access_mode(access); + self } + #[rustdoc_skip_body] fn share_mode(&mut self, share: u32) -> &mut OpenOptions { - no_code! { - self.as_inner_mut().share_mode(share); - self - } + self.as_inner_mut().share_mode(share); + self } + #[rustdoc_skip_body] fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions { - no_code! { - self.as_inner_mut().custom_flags(flags); - self - } + self.as_inner_mut().custom_flags(flags); + self } + #[rustdoc_skip_body] fn attributes(&mut self, attributes: u32) -> &mut OpenOptions { - no_code! { - self.as_inner_mut().attributes(attributes); - self - } + self.as_inner_mut().attributes(attributes); + self } + #[rustdoc_skip_body] fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions { - no_code! { - self.as_inner_mut().security_qos_flags(flags); - self - } + self.as_inner_mut().security_qos_flags(flags); + self } } @@ -546,18 +542,16 @@ pub impl(self) trait OpenOptionsExt2 { #[unstable(feature = "windows_freeze_file_times", issue = "149715")] impl OpenOptionsExt2 for OpenOptions { + #[rustdoc_skip_body] fn freeze_last_access_time(&mut self, freeze: bool) -> &mut Self { - no_code! { - self.as_inner_mut().freeze_last_access_time(freeze); - self - } + self.as_inner_mut().freeze_last_access_time(freeze); + self } + #[rustdoc_skip_body] fn freeze_last_write_time(&mut self, freeze: bool) -> &mut Self { - no_code! { - self.as_inner_mut().freeze_last_write_time(freeze); - self - } + self.as_inner_mut().freeze_last_write_time(freeze); + self } } @@ -608,10 +602,9 @@ pub impl(self) trait PermissionsExt { #[unstable(feature = "windows_permissions_ext", issue = "152956")] impl PermissionsExt for fs::Permissions { + #[rustdoc_skip_body] fn file_attributes(&self) -> u32 { - no_code! { - self.as_inner().file_attributes() - } + self.as_inner().file_attributes() } fn set_file_attributes(&mut self, mask: u32) { @@ -821,50 +814,41 @@ pub trait MetadataExt { #[stable(feature = "metadata_ext", since = "1.1.0")] impl MetadataExt for Metadata { + #[rustdoc_skip_body] fn file_attributes(&self) -> u32 { - no_code! { - self.as_inner().attrs() - } + self.as_inner().attrs() } + #[rustdoc_skip_body] fn creation_time(&self) -> u64 { - no_code! { - self.as_inner().created_u64() - } + self.as_inner().created_u64() } + #[rustdoc_skip_body] fn last_access_time(&self) -> u64 { - no_code! { - self.as_inner().accessed_u64() - } + self.as_inner().accessed_u64() } + #[rustdoc_skip_body] fn last_write_time(&self) -> u64 { - no_code! { - self.as_inner().modified_u64() - } + self.as_inner().modified_u64() } + #[rustdoc_skip_body] fn file_size(&self) -> u64 { - no_code! { - self.as_inner().size() - } + self.as_inner().size() } + #[rustdoc_skip_body] fn volume_serial_number(&self) -> Option { - no_code! { - self.as_inner().volume_serial_number() - } + self.as_inner().volume_serial_number() } + #[rustdoc_skip_body] fn number_of_links(&self) -> Option { - no_code! { - self.as_inner().number_of_links() - } + self.as_inner().number_of_links() } + #[rustdoc_skip_body] fn file_index(&self) -> Option { - no_code! { - self.as_inner().file_index() - } + self.as_inner().file_index() } + #[rustdoc_skip_body] fn change_time(&self) -> Option { - no_code! { - self.as_inner().changed_u64() - } + self.as_inner().changed_u64() } } @@ -883,15 +867,13 @@ pub impl(self) trait FileTypeExt { #[stable(feature = "windows_file_type_ext", since = "1.64.0")] impl FileTypeExt for fs::FileType { + #[rustdoc_skip_body] fn is_symlink_dir(&self) -> bool { - no_code! { - self.as_inner().is_symlink_dir() - } + self.as_inner().is_symlink_dir() } + #[rustdoc_skip_body] fn is_symlink_file(&self) -> bool { - no_code! { - self.as_inner().is_symlink_file() - } + self.as_inner().is_symlink_file() } } @@ -905,11 +887,10 @@ pub impl(self) trait FileTimesExt { #[stable(feature = "file_set_times", since = "1.75.0")] impl FileTimesExt for fs::FileTimes { + #[rustdoc_skip_body] fn set_created(mut self, t: SystemTime) -> Self { - no_code! { - self.as_inner_mut().set_created(t.into_inner()); - self - } + self.as_inner_mut().set_created(t.into_inner()); + self } } @@ -951,10 +932,9 @@ impl FileTimesExt for fs::FileTimes { #[doc(alias = "CreateSymbolicLink", alias = "CreateSymbolicLinkW")] #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_file")] +#[rustdoc_skip_body] pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - no_code! { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) - } + sys::fs::symlink_inner(original.as_ref(), link.as_ref(), false) } /// Creates a new symlink to a directory on the filesystem. @@ -995,10 +975,9 @@ pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io: #[doc(alias = "CreateSymbolicLink", alias = "CreateSymbolicLinkW")] #[stable(feature = "symlink", since = "1.1.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "fs_symlink_dir")] +#[rustdoc_skip_body] pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - no_code! { - sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) - } + sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) } /// Creates a junction point. @@ -1009,8 +988,7 @@ pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io:: /// /// If either path is not a local file path then this will fail. #[unstable(feature = "junction_point", issue = "121709")] +#[rustdoc_skip_body] pub fn junction_point, Q: AsRef>(original: P, link: Q) -> io::Result<()> { - no_code! { - sys::fs::junction_point(original.as_ref(), link.as_ref()) - } + sys::fs::junction_point(original.as_ref(), link.as_ref()) } diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index b901c5e8bec19..caeffaed3d381 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -4,6 +4,7 @@ use super::raw::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use crate::alloc::Allocator; +use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::ManuallyDrop; use crate::sys::{AsInner, FromInner, IntoInner, cvt}; @@ -172,12 +173,11 @@ impl TryFrom for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for HandleOrNull { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { - no_code! { - if self.is_valid() { - unsafe { - let _ = sys::c::CloseHandle(self.0); - } + if self.is_valid() { + unsafe { + let _ = sys::c::CloseHandle(self.0); } } } @@ -196,44 +196,42 @@ impl BorrowedHandle<'_> { /// Creates a new `OwnedHandle` instance that shares the same underlying /// object as the existing `BorrowedHandle` instance. #[stable(feature = "io_safety", since = "1.63.0")] + #[rustdoc_skip_body] pub fn try_clone_to_owned(&self) -> io::Result { - no_code! { - self.duplicate(0, false, sys::c::DUPLICATE_SAME_ACCESS) - } + self.duplicate(0, false, sys::c::DUPLICATE_SAME_ACCESS) } + #[rustdoc_skip_body] pub(crate) fn duplicate( &self, access: u32, inherit: bool, options: u32, ) -> io::Result { - no_code! { - let handle = self.as_raw_handle(); - - // `Stdin`, `Stdout`, and `Stderr` can all hold null handles, such as - // in a process with a detached console. `DuplicateHandle` would fail - // if we passed it a null handle, but we can treat null as a valid - // handle which doesn't do any I/O, and allow it to be duplicated. - if handle.is_null() { - return unsafe { Ok(OwnedHandle::from_raw_handle(handle)) }; - } - - let mut ret = ptr::null_mut(); - cvt(unsafe { - let cur_proc = sys::c::GetCurrentProcess(); - sys::c::DuplicateHandle( - cur_proc, - handle, - cur_proc, - &mut ret, - access, - inherit as sys::c::BOOL, - options, - ) - })?; - unsafe { Ok(OwnedHandle::from_raw_handle(ret)) } + let handle = self.as_raw_handle(); + + // `Stdin`, `Stdout`, and `Stderr` can all hold null handles, such as + // in a process with a detached console. `DuplicateHandle` would fail + // if we passed it a null handle, but we can treat null as a valid + // handle which doesn't do any I/O, and allow it to be duplicated. + if handle.is_null() { + return unsafe { Ok(OwnedHandle::from_raw_handle(handle)) }; } + + let mut ret = ptr::null_mut(); + cvt(unsafe { + let cur_proc = sys::c::GetCurrentProcess(); + sys::c::DuplicateHandle( + cur_proc, + handle, + cur_proc, + &mut ret, + access, + inherit as sys::c::BOOL, + options, + ) + })?; + unsafe { Ok(OwnedHandle::from_raw_handle(ret)) } } } @@ -256,12 +254,11 @@ impl TryFrom for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for HandleOrInvalid { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { - no_code! { - if self.is_valid() { - unsafe { - let _ = sys::c::CloseHandle(self.0); - } + if self.is_valid() { + unsafe { + let _ = sys::c::CloseHandle(self.0); } } } @@ -478,40 +475,36 @@ impl AsHandle for &mut T { /// ``` impl AsHandle for crate::sync::Arc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - (**self).as_handle() - } + (**self).as_handle() } } #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for crate::rc::Rc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - (**self).as_handle() - } + (**self).as_handle() } } #[unstable(feature = "unique_rc_arc", issue = "112566")] impl AsHandle for crate::rc::UniqueRc { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - (**self).as_handle() - } + (**self).as_handle() } } #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for Box { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - (**self).as_handle() - } + (**self).as_handle() } } @@ -537,10 +530,9 @@ impl AsHandle for OwnedHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl AsHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - self.as_inner().as_handle() - } + self.as_inner().as_handle() } } @@ -548,10 +540,9 @@ impl AsHandle for fs::File { impl From for OwnedHandle { /// Takes ownership of a [`File`](fs::File)'s underlying file handle. #[inline] + #[rustdoc_skip_body] fn from(file: fs::File) -> OwnedHandle { - no_code! { - file.into_inner().into_inner().into_inner() - } + file.into_inner().into_inner().into_inner() } } @@ -559,10 +550,9 @@ impl From for OwnedHandle { impl From for fs::File { /// Returns a [`File`](fs::File) that takes ownership of the given handle. #[inline] + #[rustdoc_skip_body] fn from(owned: OwnedHandle) -> Self { - no_code! { - Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) - } + Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned))) } } @@ -676,63 +666,56 @@ impl AsHandle for crate::thread::JoinHandle { #[stable(feature = "io_safety", since = "1.63.0")] impl From> for OwnedHandle { #[inline] + #[rustdoc_skip_body] fn from(join_handle: crate::thread::JoinHandle) -> OwnedHandle { - no_code! { - join_handle.into_inner().into_handle().into_inner() - } + join_handle.into_inner().into_handle().into_inner() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeReader { + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - self.0.as_handle() - } + self.0.as_handle() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { + #[rustdoc_skip_body] fn from(pipe: io::PipeReader) -> Self { - no_code! { - pipe.into_inner().into_inner() - } + pipe.into_inner().into_inner() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsHandle for io::PipeWriter { + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - self.0.as_handle() - } + self.0.as_handle() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { + #[rustdoc_skip_body] fn from(pipe: io::PipeWriter) -> Self { - no_code! { - pipe.into_inner().into_inner() - } + pipe.into_inner().into_inner() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeReader { + #[rustdoc_skip_body] fn from(owned_handle: OwnedHandle) -> Self { - no_code! { - Self::from_inner(FromInner::from_inner(owned_handle)) - } + Self::from_inner(FromInner::from_inner(owned_handle)) } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for io::PipeWriter { + #[rustdoc_skip_body] fn from(owned_handle: OwnedHandle) -> Self { - no_code! { - Self::from_inner(FromInner::from_inner(owned_handle)) - } + Self::from_inner(FromInner::from_inner(owned_handle)) } } diff --git a/library/std/src/os/windows/io/raw.rs b/library/std/src/os/windows/io/raw.rs index bd1601fc5c5fd..0bd74c76490b1 100644 --- a/library/std/src/os/windows/io/raw.rs +++ b/library/std/src/os/windows/io/raw.rs @@ -2,6 +2,7 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::macros::rustdoc_skip_body; #[cfg(doc)] use crate::os::windows::io::{AsHandle, AsSocket}; use crate::os::windows::io::{OwnedHandle, OwnedSocket}; @@ -93,10 +94,9 @@ pub trait IntoRawHandle { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().as_raw_handle() as RawHandle - } + self.as_inner().as_raw_handle() as RawHandle } } @@ -157,14 +157,13 @@ pub(super) fn stdio_handle(raw: RawHandle) -> RawHandle { #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_handle(handle: RawHandle) -> fs::File { - no_code! { - unsafe { - let handle = handle as sys::c::HANDLE; - fs::File::from_inner(sys::fs::File::from_inner(FromInner::from_inner( - OwnedHandle::from_raw_handle(handle), - ))) - } + unsafe { + let handle = handle as sys::c::HANDLE; + fs::File::from_inner(sys::fs::File::from_inner(FromInner::from_inner( + OwnedHandle::from_raw_handle(handle), + ))) } } } @@ -172,10 +171,9 @@ impl FromRawHandle for fs::File { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for fs::File { #[inline] + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_raw_handle() as *mut _ - } + self.into_inner().into_raw_handle() as *mut _ } } @@ -243,64 +241,58 @@ pub trait IntoRawSocket { #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { - no_code! { - self.as_inner().socket().as_raw_socket() - } + self.as_inner().socket().as_raw_socket() } } #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { - no_code! { - self.as_inner().socket().as_raw_socket() - } + self.as_inner().socket().as_raw_socket() } } #[stable(feature = "rust1", since = "1.0.0")] impl AsRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { - no_code! { - self.as_inner().socket().as_raw_socket() - } + self.as_inner().socket().as_raw_socket() } } #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpStream { - no_code! { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::TcpStream::from_inner(sys::net::TcpStream::from_inner(sock)) - } + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::TcpStream::from_inner(sys::net::TcpStream::from_inner(sock)) } } } #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpListener { - no_code! { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::TcpListener::from_inner(sys::net::TcpListener::from_inner(sock)) - } + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::TcpListener::from_inner(sys::net::TcpListener::from_inner(sock)) } } } #[stable(feature = "from_raw_os", since = "1.1.0")] impl FromRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> net::UdpSocket { - no_code! { - unsafe { - let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); - net::UdpSocket::from_inner(sys::net::UdpSocket::from_inner(sock)) - } + unsafe { + let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock)); + net::UdpSocket::from_inner(sys::net::UdpSocket::from_inner(sock)) } } } @@ -308,83 +300,74 @@ impl FromRawSocket for net::UdpSocket { #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::TcpStream { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { - no_code! { - self.into_inner().into_socket().into_inner().into_raw_socket() - } + self.into_inner().into_socket().into_inner().into_raw_socket() } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::TcpListener { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { - no_code! { - self.into_inner().into_socket().into_inner().into_raw_socket() - } + self.into_inner().into_socket().into_inner().into_raw_socket() } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawSocket for net::UdpSocket { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { - no_code! { - self.into_inner().into_socket().into_inner().into_raw_socket() - } + self.into_inner().into_socket().into_inner().into_raw_socket() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeReader { + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.0.as_raw_handle() - } + self.0.as_raw_handle() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeReader { + #[rustdoc_skip_body] unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { - no_code! { - unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } - } + unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeReader { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.0.into_raw_handle() - } + self.0.into_raw_handle() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl AsRawHandle for io::PipeWriter { + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.0.as_raw_handle() - } + self.0.as_raw_handle() } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl FromRawHandle for io::PipeWriter { + #[rustdoc_skip_body] unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { - no_code! { - unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } - } + unsafe { Self::from_inner(FromRawHandle::from_raw_handle(raw_handle)) } } } #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl IntoRawHandle for io::PipeWriter { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.0.into_raw_handle() - } + self.0.into_raw_handle() } } diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index dd4476bac4bd0..24038e3dd5624 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -4,6 +4,7 @@ use super::raw::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; use crate::alloc::Allocator; +use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::{self, ManuallyDrop}; #[cfg(not(target_vendor = "uwp"))] @@ -78,17 +79,16 @@ impl OwnedSocket { // FIXME(strict_provenance_magic): we defined RawSocket to be a u64 ;-; #[allow(implicit_provenance_casts)] #[cfg(not(target_vendor = "uwp"))] + #[rustdoc_skip_body] pub(crate) fn set_no_inherit(&self) -> io::Result<()> { - no_code! { - cvt(unsafe { - sys::c::SetHandleInformation( - self.as_raw_socket() as sys::c::HANDLE, - sys::c::HANDLE_FLAG_INHERIT, - 0, - ) - }) - .map(drop) - } + cvt(unsafe { + sys::c::SetHandleInformation( + self.as_raw_socket() as sys::c::HANDLE, + sys::c::HANDLE_FLAG_INHERIT, + 0, + ) + }) + .map(drop) } #[cfg(target_vendor = "uwp")] @@ -101,17 +101,37 @@ impl BorrowedSocket<'_> { /// Creates a new `OwnedSocket` instance that shares the same underlying /// object as the existing `BorrowedSocket` instance. #[stable(feature = "io_safety", since = "1.63.0")] + #[rustdoc_skip_body] pub fn try_clone_to_owned(&self) -> io::Result { - no_code! { - let mut info = unsafe { mem::zeroed::() }; - let result = unsafe { - sys::c::WSADuplicateSocketW( - self.as_raw_socket() as sys::c::SOCKET, - sys::c::GetCurrentProcessId(), - &mut info, - ) - }; - sys::net::cvt(result)?; + let mut info = unsafe { mem::zeroed::() }; + let result = unsafe { + sys::c::WSADuplicateSocketW( + self.as_raw_socket() as sys::c::SOCKET, + sys::c::GetCurrentProcessId(), + &mut info, + ) + }; + sys::net::cvt(result)?; + let socket = unsafe { + sys::c::WSASocketW( + info.iAddressFamily, + info.iSocketType, + info.iProtocol, + &info, + 0, + sys::c::WSA_FLAG_OVERLAPPED | sys::c::WSA_FLAG_NO_HANDLE_INHERIT, + ) + }; + + if socket != sys::c::INVALID_SOCKET { + unsafe { Ok(OwnedSocket::from_raw_socket(socket as RawSocket)) } + } else { + let error = unsafe { sys::c::WSAGetLastError() }; + + if error != sys::c::WSAEPROTOTYPE && error != sys::c::WSAEINVAL { + return Err(io::Error::from_raw_os_error(error)); + } + let socket = unsafe { sys::c::WSASocketW( info.iAddressFamily, @@ -119,49 +139,27 @@ impl BorrowedSocket<'_> { info.iProtocol, &info, 0, - sys::c::WSA_FLAG_OVERLAPPED | sys::c::WSA_FLAG_NO_HANDLE_INHERIT, + sys::c::WSA_FLAG_OVERLAPPED, ) }; - if socket != sys::c::INVALID_SOCKET { - unsafe { Ok(OwnedSocket::from_raw_socket(socket as RawSocket)) } - } else { - let error = unsafe { sys::c::WSAGetLastError() }; - - if error != sys::c::WSAEPROTOTYPE && error != sys::c::WSAEINVAL { - return Err(io::Error::from_raw_os_error(error)); - } - - let socket = unsafe { - sys::c::WSASocketW( - info.iAddressFamily, - info.iSocketType, - info.iProtocol, - &info, - 0, - sys::c::WSA_FLAG_OVERLAPPED, - ) - }; - - if socket == sys::c::INVALID_SOCKET { - return Err(last_error()); - } - - unsafe { - let socket = OwnedSocket::from_raw_socket(socket as RawSocket); - socket.set_no_inherit()?; - Ok(socket) - } + if socket == sys::c::INVALID_SOCKET { + return Err(last_error()); + } + + unsafe { + let socket = OwnedSocket::from_raw_socket(socket as RawSocket); + socket.set_no_inherit()?; + Ok(socket) } } } } /// Returns the last error from the Windows socket interface. +#[rustdoc_skip_body] fn last_error() -> io::Error { - no_code! { - io::Error::from_raw_os_error(unsafe { sys::c::WSAGetLastError() }) - } + io::Error::from_raw_os_error(unsafe { sys::c::WSAGetLastError() }) } #[stable(feature = "io_safety", since = "1.63.0")] @@ -200,11 +198,10 @@ impl FromRawSocket for OwnedSocket { #[stable(feature = "io_safety", since = "1.63.0")] impl Drop for OwnedSocket { #[inline] + #[rustdoc_skip_body] fn drop(&mut self) { - no_code! { - unsafe { - let _ = sys::c::closesocket(self.socket.as_inner() as sys::c::SOCKET); - } + unsafe { + let _ = sys::c::closesocket(self.socket.as_inner() as sys::c::SOCKET); } } } diff --git a/library/std/src/os/windows/net/addr.rs b/library/std/src/os/windows/net/addr.rs index 614579762573c..7ab856ae6ba44 100644 --- a/library/std/src/os/windows/net/addr.rs +++ b/library/std/src/os/windows/net/addr.rs @@ -1,6 +1,7 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] use crate::byte_str::ByteStr; use crate::ffi::OsStr; +use crate::macros::rustdoc_skip_body; use crate::path::Path; #[cfg(not(doc))] use crate::sys::c::{AF_UNIX, SOCKADDR, SOCKADDR_UN}; @@ -127,28 +128,27 @@ impl SocketAddr { /// /// assert!(SocketAddr::from_pathname("/path/with/\0/bytes").is_err()); /// ``` + #[rustdoc_skip_body] pub fn from_pathname

(path: P) -> io::Result where P: AsRef, { - no_code! { - sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) - } + sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len: len as _ }) } + + #[rustdoc_skip_body] fn address(&self) -> AddressKind<'_> { - no_code! { - let len = self.len as usize - SUN_PATH_OFFSET; - let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; + let len = self.len as usize - SUN_PATH_OFFSET; + let path = unsafe { mem::transmute::<&[i8], &[u8]>(&self.addr.sun_path) }; - if len == 0 { - AddressKind::Unnamed - } else if self.addr.sun_path[0] == 0 { - AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) - } else { - AddressKind::Pathname(unsafe { - OsStr::from_encoded_bytes_unchecked(&path[..len - 1]).as_ref() - }) - } + if len == 0 { + AddressKind::Unnamed + } else if self.addr.sun_path[0] == 0 { + AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) + } else { + AddressKind::Pathname(unsafe { + OsStr::from_encoded_bytes_unchecked(&path[..len - 1]).as_ref() + }) } } diff --git a/library/std/src/os/windows/net/listener.rs b/library/std/src/os/windows/net/listener.rs index 5749747b3adee..d3b9b50c17293 100644 --- a/library/std/src/os/windows/net/listener.rs +++ b/library/std/src/os/windows/net/listener.rs @@ -1,4 +1,6 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] + +use crate::macros::rustdoc_skip_body; use crate::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; use crate::os::windows::net::{SocketAddr, UnixStream}; use crate::path::Path; @@ -163,10 +165,9 @@ impl UnixListener { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - no_code! { - SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) - } + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) } /// Creates a new independently owned handle to the underlying socket. @@ -327,28 +328,25 @@ impl<'a> Iterator for Incoming<'a> { impl AsRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { - no_code! { - self.0.as_raw_socket() - } + self.0.as_raw_socket() } } impl FromRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> Self { - no_code! { - UnixListener(unsafe { Socket::from_raw_socket(sock) }) - } + UnixListener(unsafe { Socket::from_raw_socket(sock) }) } } impl IntoRawSocket for UnixListener { #[inline] + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { - no_code! { - self.0.into_raw_socket() - } + self.0.into_raw_socket() } } diff --git a/library/std/src/os/windows/net/stream.rs b/library/std/src/os/windows/net/stream.rs index 1df01b8b527d5..9a3360bfb565c 100644 --- a/library/std/src/os/windows/net/stream.rs +++ b/library/std/src/os/windows/net/stream.rs @@ -1,4 +1,6 @@ #![unstable(feature = "windows_unix_domain_sockets", issue = "150487")] + +use crate::macros::rustdoc_skip_body; use crate::net::Shutdown; use crate::os::windows::io::{ AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, RawSocket, @@ -126,10 +128,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - no_code! { - SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) - } + SocketAddr::new(|addr, len| unsafe { getsockname(self.0.as_raw(), addr, len) }) } /// Returns the socket address of the remote half of this connection. @@ -147,10 +148,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { - no_code! { - SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) - } + SocketAddr::new(|addr, len| unsafe { getpeername(self.0.as_raw(), addr, len) }) } /// Returns the read timeout of this socket. @@ -170,10 +170,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn read_timeout(&self) -> io::Result> { - no_code! { - self.0.timeout(SO_RCVTIMEO) - } + self.0.timeout(SO_RCVTIMEO) } /// Moves the socket into or out of nonblocking mode. @@ -236,10 +235,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { - no_code! { - self.0.set_timeout(dur, SO_RCVTIMEO) - } + self.0.set_timeout(dur, SO_RCVTIMEO) } /// Sets the write timeout for the socket. @@ -284,10 +282,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { - no_code! { - self.0.set_timeout(dur, SO_SNDTIMEO) - } + self.0.set_timeout(dur, SO_SNDTIMEO) } /// Shuts down the read, write, or both halves of this connection. @@ -377,10 +374,9 @@ impl UnixStream { /// Ok(()) /// } /// ``` + #[rustdoc_skip_body] pub fn write_timeout(&self) -> io::Result> { - no_code! { - self.0.timeout(SO_SNDTIMEO) - } + self.0.timeout(SO_SNDTIMEO) } } @@ -424,35 +420,31 @@ impl<'a> io::Write for &'a UnixStream { impl AsSocket for UnixStream { #[inline] + #[rustdoc_skip_body] fn as_socket(&self) -> BorrowedSocket<'_> { - no_code! { - self.0.as_socket() - } + self.0.as_socket() } } impl AsRawSocket for UnixStream { #[inline] + #[rustdoc_skip_body] fn as_raw_socket(&self) -> RawSocket { - no_code! { - self.0.as_raw_socket() - } + self.0.as_raw_socket() } } impl FromRawSocket for UnixStream { #[inline] + #[rustdoc_skip_body] unsafe fn from_raw_socket(sock: RawSocket) -> Self { - no_code! { - unsafe { UnixStream(Socket::from_raw_socket(sock)) } - } + unsafe { UnixStream(Socket::from_raw_socket(sock)) } } } impl IntoRawSocket for UnixStream { + #[rustdoc_skip_body] fn into_raw_socket(self) -> RawSocket { - no_code! { - self.0.into_raw_socket() - } + self.0.into_raw_socket() } } diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index 568bd45f01da3..5065026b21a46 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -5,6 +5,7 @@ #![stable(feature = "process_extensions", since = "1.2.0")] use crate::ffi::{OsStr, c_void}; +use crate::macros::rustdoc_skip_body; use crate::mem::MaybeUninit; use crate::os::windows::io::{ AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle, @@ -14,12 +15,11 @@ use crate::{io, marker, process, ptr, sys}; #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { + #[rustdoc_skip_body] unsafe fn from_raw_handle(handle: RawHandle) -> process::Stdio { - no_code! { - let handle = unsafe { sys::handle::Handle::from_raw_handle(handle as *mut _) }; - let io = sys::process::Stdio::Handle(handle); - process::Stdio::from_inner(io) - } + let handle = unsafe { sys::handle::Handle::from_raw_handle(handle as *mut _) }; + let io = sys::process::Stdio::Handle(handle); + process::Stdio::from_inner(io) } } @@ -27,108 +27,97 @@ impl FromRawHandle for process::Stdio { impl From for process::Stdio { /// Takes ownership of a handle and returns a [`Stdio`](process::Stdio) /// that can attach a stream to it. + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::Stdio { - no_code! { - let handle = sys::handle::Handle::from_inner(handle); - let io = sys::process::Stdio::Handle(handle); - process::Stdio::from_inner(io) - } + let handle = sys::handle::Handle::from_inner(handle); + let io = sys::process::Stdio::Handle(handle); + process::Stdio::from_inner(io) } } #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::Child { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().handle().as_raw_handle() as *mut _ - } + self.as_inner().handle().as_raw_handle() as *mut _ } } #[stable(feature = "io_safety", since = "1.63.0")] impl AsHandle for process::Child { #[inline] + #[rustdoc_skip_body] fn as_handle(&self) -> BorrowedHandle<'_> { - no_code! { - self.as_inner().handle().as_handle() - } + self.as_inner().handle().as_handle() } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::Child { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_handle().into_raw_handle() as *mut _ - } + self.into_inner().into_handle().into_raw_handle() as *mut _ } } #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedHandle { /// Takes ownership of a [`Child`](process::Child)'s process handle. + #[rustdoc_skip_body] fn from(child: process::Child) -> OwnedHandle { - no_code! { - child.into_inner().into_handle().into_inner() - } + child.into_inner().into_handle().into_inner() } } #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStdin { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().handle().as_raw_handle() as *mut _ - } + self.as_inner().handle().as_raw_handle() as *mut _ } } #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStdout { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().handle().as_raw_handle() as *mut _ - } + self.as_inner().handle().as_raw_handle() as *mut _ } } #[stable(feature = "process_extensions", since = "1.2.0")] impl AsRawHandle for process::ChildStderr { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().handle().as_raw_handle() as *mut _ - } + self.as_inner().handle().as_raw_handle() as *mut _ } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdin { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_handle().into_raw_handle() as *mut _ - } + self.into_inner().into_handle().into_raw_handle() as *mut _ } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStdout { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_handle().into_raw_handle() as *mut _ - } + self.into_inner().into_handle().into_raw_handle() as *mut _ } } #[stable(feature = "into_raw_os", since = "1.4.0")] impl IntoRawHandle for process::ChildStderr { + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_handle().into_raw_handle() as *mut _ - } + self.into_inner().into_handle().into_raw_handle() as *mut _ } } @@ -138,12 +127,11 @@ impl IntoRawHandle for process::ChildStderr { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdin { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStdin { - no_code! { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStdin::from_inner(pipe) - } + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStdin::from_inner(pipe) } } @@ -153,12 +141,11 @@ impl From for process::ChildStdin { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStdout { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStdout { - no_code! { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStdout::from_inner(pipe) - } + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStdout::from_inner(pipe) } } @@ -168,12 +155,11 @@ impl From for process::ChildStdout { /// writing from and to it is implemented using asynchronous APIs. #[stable(feature = "child_stream_from_fd", since = "1.74.0")] impl From for process::ChildStderr { + #[rustdoc_skip_body] fn from(handle: OwnedHandle) -> process::ChildStderr { - no_code! { - let handle = sys::handle::Handle::from_inner(handle); - let pipe = sys::process::ChildPipe::from_inner(handle); - process::ChildStderr::from_inner(pipe) - } + let handle = sys::handle::Handle::from_inner(handle); + let pipe = sys::process::ChildPipe::from_inner(handle); + process::ChildStderr::from_inner(pipe) } } @@ -188,10 +174,9 @@ pub impl(self) trait ExitStatusExt { #[stable(feature = "exit_status_from", since = "1.12.0")] impl ExitStatusExt for process::ExitStatus { + #[rustdoc_skip_body] fn from_raw(raw: u32) -> Self { - no_code! { - process::ExitStatus::from_inner(From::from(raw)) - } + process::ExitStatus::from_inner(From::from(raw)) } } @@ -422,39 +407,34 @@ pub impl(self) trait CommandExt { #[stable(feature = "windows_process_extensions", since = "1.16.0")] impl CommandExt for process::Command { + #[rustdoc_skip_body] fn creation_flags(&mut self, flags: u32) -> &mut process::Command { - no_code! { - self.as_inner_mut().creation_flags(flags); - self - } + self.as_inner_mut().creation_flags(flags); + self } + #[rustdoc_skip_body] fn desktop>(&mut self, desktop: S) -> &mut process::Command { - no_code! { - self.as_inner_mut().desktop(desktop.as_ref()); - self - } + self.as_inner_mut().desktop(desktop.as_ref()); + self } + #[rustdoc_skip_body] fn show_window(&mut self, cmd_show: u16) -> &mut process::Command { - no_code! { - self.as_inner_mut().show_window(Some(cmd_show)); - self - } + self.as_inner_mut().show_window(Some(cmd_show)); + self } + #[rustdoc_skip_body] fn force_quotes(&mut self, enabled: bool) -> &mut process::Command { - no_code! { - self.as_inner_mut().force_quotes(enabled); - self - } + self.as_inner_mut().force_quotes(enabled); + self } + #[rustdoc_skip_body] fn raw_arg>(&mut self, raw_text: S) -> &mut process::Command { - no_code! { - self.as_inner_mut().raw_arg(raw_text.as_ref()); - self - } + self.as_inner_mut().raw_arg(raw_text.as_ref()); + self } fn async_pipes(&mut self, always_async: bool) -> &mut process::Command { @@ -466,43 +446,38 @@ impl CommandExt for process::Command { self } + #[rustdoc_skip_body] fn spawn_with_attributes( &mut self, attribute_list: &ProcThreadAttributeList<'_>, ) -> io::Result { - no_code! { - self.as_inner_mut() - .spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list)) - .map(process::Child::from_inner) - } + self.as_inner_mut() + .spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list)) + .map(process::Child::from_inner) } + #[rustdoc_skip_body] fn startupinfo_fullscreen(&mut self, enabled: bool) -> &mut process::Command { - no_code! { - self.as_inner_mut().startupinfo_fullscreen(enabled); - self - } + self.as_inner_mut().startupinfo_fullscreen(enabled); + self } + #[rustdoc_skip_body] fn startupinfo_untrusted_source(&mut self, enabled: bool) -> &mut process::Command { - no_code! { - self.as_inner_mut().startupinfo_untrusted_source(enabled); - self - } + self.as_inner_mut().startupinfo_untrusted_source(enabled); + self } + #[rustdoc_skip_body] fn startupinfo_force_feedback(&mut self, enabled: Option) -> &mut process::Command { - no_code! { - self.as_inner_mut().startupinfo_force_feedback(enabled); - self - } + self.as_inner_mut().startupinfo_force_feedback(enabled); + self } + #[rustdoc_skip_body] fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command { - no_code! { - self.as_inner_mut().inherit_handles(inherit_handles); - self - } + self.as_inner_mut().inherit_handles(inherit_handles); + self } } @@ -515,10 +490,9 @@ pub impl(self) trait ChildExt { #[stable(feature = "windows_process_extensions_main_thread_handle", since = "1.100.0")] impl ChildExt for process::Child { + #[rustdoc_skip_body] fn main_thread_handle(&self) -> BorrowedHandle<'_> { - no_code! { - self.handle.main_thread_handle() - } + self.handle.main_thread_handle() } } @@ -537,10 +511,9 @@ pub impl(self) trait ExitCodeExt { #[stable(feature = "windows_process_exit_code_from", since = "CURRENT_RUSTC_VERSION")] impl ExitCodeExt for process::ExitCode { + #[rustdoc_skip_body] fn from_raw(raw: u32) -> Self { - no_code! { - process::ExitCode::from_inner(From::from(raw)) - } + process::ExitCode::from_inner(From::from(raw)) } } @@ -576,12 +549,11 @@ impl<'a> Drop for ProcThreadAttributeList<'a> { /// underlying attribute list. /// /// [1]: + #[rustdoc_skip_body] fn drop(&mut self) { - no_code! { - let lp_attribute_list = - self.attribute_list.as_mut_ptr().cast::(); - unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) } - } + let lp_attribute_list = + self.attribute_list.as_mut_ptr().cast::(); + unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) } } } @@ -724,61 +696,60 @@ impl<'a> ProcThreadAttributeListBuilder<'a> { /// /// Returns an error if the maximum number of attributes is exceeded /// or if there is an I/O error during initialization. + #[rustdoc_skip_body] pub fn finish(&self) -> io::Result> { - no_code! { - // To initialize our ProcThreadAttributeList, we need to determine - // how many bytes to allocate for it. The Windows API simplifies this - // process by allowing us to call `InitializeProcThreadAttributeList` - // with a null pointer to retrieve the required size. - let mut required_size = 0; - let Ok(attribute_count) = self.attributes.len().try_into() else { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "maximum number of ProcThreadAttributes exceeded", - )); - }; - unsafe { - sys::c::InitializeProcThreadAttributeList( - ptr::null_mut(), - attribute_count, - 0, - &mut required_size, - ) - }; - - let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice(); - - // Once we've allocated the necessary memory, it's safe to invoke - // `InitializeProcThreadAttributeList` to properly initialize the list. + // To initialize our ProcThreadAttributeList, we need to determine + // how many bytes to allocate for it. The Windows API simplifies this + // process by allowing us to call `InitializeProcThreadAttributeList` + // with a null pointer to retrieve the required size. + let mut required_size = 0; + let Ok(attribute_count) = self.attributes.len().try_into() else { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "maximum number of ProcThreadAttributes exceeded", + )); + }; + unsafe { + sys::c::InitializeProcThreadAttributeList( + ptr::null_mut(), + attribute_count, + 0, + &mut required_size, + ) + }; + + let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice(); + + // Once we've allocated the necessary memory, it's safe to invoke + // `InitializeProcThreadAttributeList` to properly initialize the list. + sys::cvt(unsafe { + sys::c::InitializeProcThreadAttributeList( + attribute_list.as_mut_ptr().cast::(), + attribute_count, + 0, + &mut required_size, + ) + })?; + + // # Add our attributes to the buffer. + // It's theoretically possible for the attribute count to exceed a u32 + // value. Therefore, we ensure that we don't add more attributes than + // the buffer was initialized for. + for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) { sys::cvt(unsafe { - sys::c::InitializeProcThreadAttributeList( + sys::c::UpdateProcThreadAttribute( attribute_list.as_mut_ptr().cast::(), - attribute_count, 0, - &mut required_size, + attribute, + value.ptr, + value.size, + ptr::null_mut(), + ptr::null_mut(), ) })?; - - // # Add our attributes to the buffer. - // It's theoretically possible for the attribute count to exceed a u32 - // value. Therefore, we ensure that we don't add more attributes than - // the buffer was initialized for. - for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) { - sys::cvt(unsafe { - sys::c::UpdateProcThreadAttribute( - attribute_list.as_mut_ptr().cast::(), - 0, - attribute, - value.ptr, - value.size, - ptr::null_mut(), - ptr::null_mut(), - ) - })?; - } - - Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData }) } + + Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData }) } } diff --git a/library/std/src/os/windows/thread.rs b/library/std/src/os/windows/thread.rs index 137afc6af1a4c..b53bd72f5d3e1 100644 --- a/library/std/src/os/windows/thread.rs +++ b/library/std/src/os/windows/thread.rs @@ -4,6 +4,7 @@ #![stable(feature = "thread_extensions", since = "1.9.0")] +use crate::macros::rustdoc_skip_body; use crate::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle}; use crate::sys::{AsInner, IntoInner}; use crate::thread; @@ -11,19 +12,17 @@ use crate::thread; #[stable(feature = "thread_extensions", since = "1.9.0")] impl AsRawHandle for thread::JoinHandle { #[inline] + #[rustdoc_skip_body] fn as_raw_handle(&self) -> RawHandle { - no_code! { - self.as_inner().handle().as_raw_handle() as *mut _ - } + self.as_inner().handle().as_raw_handle() as *mut _ } } #[stable(feature = "thread_extensions", since = "1.9.0")] impl IntoRawHandle for thread::JoinHandle { #[inline] + #[rustdoc_skip_body] fn into_raw_handle(self) -> RawHandle { - no_code! { - self.into_inner().into_handle().into_raw_handle() as *mut _ - } + self.into_inner().into_handle().into_raw_handle() as *mut _ } } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 5c709b47e626d..87835abf23fbe 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -1299,6 +1299,7 @@ impl ExitStatusError { #[cfg(target_os = "linux")] mod linux_child_ext { use crate::io::ErrorKind; + use crate::macros::rustdoc_skip_body; use crate::os::linux::process as os; use crate::sys::{FromInner, process as imp}; use crate::{io, mem}; @@ -1314,14 +1315,13 @@ mod linux_child_ext { .ok_or_else(|| io::const_error!(ErrorKind::Uncategorized, "no pidfd was created.")) } + #[rustdoc_skip_body] fn into_pidfd(mut self) -> Result { - no_code! { - self.handle - .pidfd - .take() - .map(>::from_inner) - .ok_or_else(|| self) - } + self.handle + .pidfd + .take() + .map(>::from_inner) + .ok_or_else(|| self) } } } From a9560153a3da1ad4db45df0af2dadeb9af05324e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 24 Oct 2026 18:04:09 +0200 Subject: [PATCH 74/80] temporarily disable a test in Miri --- library/std/tests/thread_local/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/tests/thread_local/tests.rs b/library/std/tests/thread_local/tests.rs index c2164a0bc2cff..35fc967e683b4 100644 --- a/library/std/tests/thread_local/tests.rs +++ b/library/std/tests/thread_local/tests.rs @@ -365,6 +365,7 @@ fn join_orders_after_tls_destructors() { // ** glibc: https://github.com/bminor/glibc/blob/e5893e6349541d871e8a25120bca014551d13ff5/nptl/nptl_deallocate_tsd.c#L59 // ** musl: https://github.com/kraj/musl/blob/1880359b54ff7dd9f5016002bfdae4b136007dde/src/thread/pthread_key_create.c#L87 #[test] +#[cfg_attr(miri, ignore)] // FIXME: fails in Miri ever since we support __cxa_thread_atexit_impl fn thread_current_in_dtor() { use std::thread::Builder; From cdcaab758eea35ec838bf513bd1a0147764c396b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 24 Oct 2026 19:52:44 +0200 Subject: [PATCH 75/80] Update gcc submodule --- src/gcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gcc b/src/gcc index 37ea7cd851f04..e1c6e07b095d0 160000 --- a/src/gcc +++ b/src/gcc @@ -1 +1 @@ -Subproject commit 37ea7cd851f04ee6a313c5067c9fde9328f876aa +Subproject commit e1c6e07b095d091177376a163da44039475e05e3 From bb4f948aa911bf400bc201a8a63817efa7ae72b1 Mon Sep 17 00:00:00 2001 From: Hanna Kruppe Date: Sat, 24 Oct 2026 19:08:53 +0200 Subject: [PATCH 76/80] cg_llvm: clarify Send / Sync story for modules * Remove unnecessary and unsound Sync impl for ModuleLlvm * Explain why the Send impl is fine * turn a OnceLock into OnceCell to avoid giving the impression Sync might be needed here --- .../rustc_codegen_llvm/src/back/llvm_backend.rs | 16 +++++++++------- compiler/rustc_codegen_ssa/src/traits/write.rs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 385b785233a75..c2fd8a79407f0 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -2,12 +2,12 @@ //! and related traits for the LLVM codegen backend. use std::any::Any; +use std::cell::OnceCell; use std::ffi::CStr; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::path::PathBuf; use std::ptr; -use std::sync::OnceLock; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::ThinModule; @@ -451,13 +451,15 @@ pub struct ModuleLlvm { /// Saved ThinLTO buffer for embedding in the object file. /// - /// This is currently a `OnceLock` to avoid aliasing problems when writing to it + /// This is currently a `OnceCell` to avoid aliasing problems when writing to it /// from code that also wants to pass around a shared ref to `ModuleCodegen`. - pub(crate) thin_lto_buffer: OnceLock>, + pub(crate) thin_lto_buffer: OnceCell>, } +// An LLVM context can only be used by one thread at a time. But we can move the entire context and +// all objects inside it to another thread (needed for parallel codegen). This is sound because +// `ModuleLlvm` owns the context and only allows borrowing LLVM objects tied to the context. unsafe impl Send for ModuleLlvm {} -unsafe impl Sync for ModuleLlvm {} /// Shared helper to call [`llvm::LLVMContextCreate`] and then configure /// [`llvm::LLVMContextSetDiscardValueNames`]. @@ -478,7 +480,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_target_machine(tcx, mod_name)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -491,7 +493,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(create_informational_target_machine(tcx.sess)), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } @@ -512,7 +514,7 @@ impl ModuleLlvm { llmod_raw, llcx_raw, tm: ManuallyDrop::new(tm), - thin_lto_buffer: OnceLock::new(), + thin_lto_buffer: OnceCell::new(), } } } diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index 4c0df97a41985..36bc0a68d882e 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -14,7 +14,7 @@ use crate::back::write::{ use crate::{CompiledModule, ModuleCodegen}; pub trait WriteBackendMethods: Clone + 'static { - type Module: Send + Sync; + type Module: Send; type TargetMachine; type ModuleBuffer: ModuleBufferMethods; type ThinData: Send + Sync; From 127a5d989f4bd10e31f40b421e501e56413fea4c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 19 Aug 2026 17:13:54 +0200 Subject: [PATCH 77/80] rustdoc: Correctly handle reexports when `--document-private-items` is used --- src/librustdoc/passes/strip_priv_imports.rs | 9 +++++++-- src/librustdoc/passes/strip_private.rs | 10 +++++++--- src/librustdoc/passes/stripper.rs | 2 ++ src/librustdoc/visit_ast.rs | 12 ++++++++++-- .../glob-private-document-private-items.rs | 16 ++++++++-------- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 9a432c3bad343..7c68f0091342f 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -12,6 +12,11 @@ pub(super) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) - } let is_json_output = cx.is_json_output(); - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(krate) + ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 54c8e9daedd17..e99e488ea227c 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -25,9 +25,13 @@ pub(super) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> is_json_output, tcx: cx.tcx, }; - krate = - ImportStripper { tcx: cx.tcx, is_json_output, document_hidden: cx.document_hidden() } - .fold_crate(stripper.fold_crate(krate)); + krate = ImportStripper { + tcx: cx.tcx, + is_json_output, + document_hidden: cx.document_hidden(), + document_private: cx.document_private(), + } + .fold_crate(stripper.fold_crate(krate)); } // strip all impls referencing private items diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 4c6d62917ab24..45bfb519cf4f5 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -268,6 +268,7 @@ pub(crate) struct ImportStripper<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, pub(crate) is_json_output: bool, pub(crate) document_hidden: bool, + pub(crate) document_private: bool, } impl ImportStripper<'_> { @@ -290,6 +291,7 @@ impl DocFolder for ImportStripper<'_> { debug!("ImportStripper: stripping {:?}", i.name); None } + clean::ImportItem(imp) if self.document_private => Some(self.fold_item_recur(i)), // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None, clean::ExternCrateItem { .. } | clean::ImportItem(..) if i.visibility(self.tcx) != Some(Visibility::Public) => diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index df55626ffb05d..33b0334e3fd5d 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -490,7 +490,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let tcx = self.cx.tcx; let def_id = item.owner_id.to_def_id(); - let is_pub = tcx.visibility(def_id).is_public(); + // Explanations: if the item is public, nothing more to check. If not, then we check if + // we're documenting private item. If we are, then we want all items, except if it's a + // `use`. In this case, we want to check if it's a reexport. + let is_pub = tcx.visibility(def_id).is_public() + || (self.cx.document_private() + && (!matches!(item.kind, hir::ItemKind::Use(..)) + || def_id.as_local().is_some_and(|def_id| { + tcx.visibility(def_id).is_accessible_from(CRATE_DEF_ID, tcx) + }))); if is_pub { self.store_path(item.owner_id.to_def_id()); @@ -592,7 +600,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. - if is_pub && self.inside_public_path { + if is_pub && (self.inside_public_path || self.cx.document_private()) { let please_inline = if let Some(res_did) = res.opt_def_id() && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG)) { diff --git a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs index 4ad36da843f80..601eb711ae33d 100644 --- a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs +++ b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs @@ -17,14 +17,14 @@ pub use mod1::*; //@ has foo/index.html //@ hasraw - "mod1" //@ hasraw - "Mod1Public" -//@ !hasraw - "Mod1Private" -//@ !hasraw - "mod2" +//@ hasraw - "Mod1Private" +//@ hasraw - "mod2" //@ hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Private" //@ has foo/struct.Mod1Public.html -//@ !has foo/struct.Mod1Private.html +//@ has foo/struct.Mod1Private.html //@ has foo/struct.Mod2Public.html -//@ !has foo/struct.Mod2Private.html +//@ has foo/struct.Mod2Private.html //@ has foo/mod1/index.html //@ hasraw - "mod2" @@ -43,6 +43,6 @@ pub use mod1::*; //@ has foo/mod1/mod2/struct.Mod2Public.html //@ has foo/mod1/mod2/struct.Mod2Private.html -//@ !has foo/mod2/index.html -//@ !has foo/mod2/struct.Mod2Public.html -//@ !has foo/mod2/struct.Mod2Private.html +//@ has foo/mod2/index.html +//@ has foo/mod2/struct.Mod2Public.html +//@ has foo/mod2/struct.Mod2Private.html From f87a2daa727df902a0ec452763076e921e92b370 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 19 Aug 2026 17:15:35 +0200 Subject: [PATCH 78/80] Add rustdoc-html regression test for reexports when `--document-private-items` is used --- .../document-private-reexports.rs | 52 +++++++++++++++++++ .../glob-private-document-private-items.rs | 8 +-- 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/rustdoc-html/inline_local/document-private-reexports.rs diff --git a/tests/rustdoc-html/inline_local/document-private-reexports.rs b/tests/rustdoc-html/inline_local/document-private-reexports.rs new file mode 100644 index 0000000000000..c0953e7678716 --- /dev/null +++ b/tests/rustdoc-html/inline_local/document-private-reexports.rs @@ -0,0 +1,52 @@ +// Test ensures that when we use the `--document-private-items` option, reexports +// work the same as for "public" items. +// Regression test for . + +//@ compile-flags: --document-private-items + +#![crate_name = "foo"] + +//@ has 'foo/index.html' +// The four reexports, the `not_hidden` and `foofoo` (private) modules. +//@ count - '//*[@class="item-table"]/dt' 6 +//@ has - '//*[@class="item-table"]/dt/a[@href="not_hidden/index.html"]' 'not_hidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Top.html"]' 'Top' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.NotHidden.html"]' 'NotHidden' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Hidden2.html"]' 'Hidden2' +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.X.html"]' 'X' +//@ has - '//*[@class="item-table"]/dt/a[@href="foofoo/index.html"]' 'foofoo' + +#[doc(hidden)] +pub(crate) struct TopHidden; + +#[doc(inline)] +pub(crate) use TopHidden as Top; + +#[doc(hidden)] +mod hidden { + pub(crate) struct NotHidden; +} + +pub(crate) use self::hidden::NotHidden; +// Even though there is no `pub` of any kind, we display it because we're at the +// crate root. +use self::hidden::NotHidden as X; + +mod not_hidden { + #[doc(hidden)] + pub(crate) struct Hidden; +} + +#[doc(inline)] +pub(crate) use self::not_hidden::Hidden as Hidden2; + +//@ has 'foo/foofoo/index.html' +//@ count - '//*[@class="item-table"]/dt' 1 +//@ has - '//*[@class="item-table"]/dt/a[@href="struct.Something.html"]' 'Something' +mod foofoo { + // Since there is no `pub` of any kind, we don't display it, even if there is `doc(inline)`. + #[doc(inline)] + use crate::not_hidden::Hidden; + #[doc(inline)] + pub(crate) use crate::not_hidden::Hidden as Something; +} diff --git a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs index 601eb711ae33d..a85ed41fa5ddf 100644 --- a/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs +++ b/tests/rustdoc-html/inline_local/glob-private-document-private-items.rs @@ -30,12 +30,12 @@ pub use mod1::*; //@ hasraw - "mod2" //@ hasraw - "Mod1Public" //@ hasraw - "Mod1Private" -//@ !hasraw - "Mod2Public" -//@ !hasraw - "Mod2Private" +//@ hasraw - "Mod2Public" +//@ hasraw - "Mod2Private" //@ has foo/mod1/struct.Mod1Public.html //@ has foo/mod1/struct.Mod1Private.html -//@ !has foo/mod1/struct.Mod2Public.html -//@ !has foo/mod1/struct.Mod2Private.html +//@ has foo/mod1/struct.Mod2Public.html +//@ has foo/mod1/struct.Mod2Private.html //@ has foo/mod1/mod2/index.html //@ hasraw - "Mod2Public" From a62d6d84a2dae1a8348851071c78f751b38ef46d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 24 Oct 2026 23:23:00 +0200 Subject: [PATCH 79/80] Disable `unused_macros` check on `rustdoc_skip_body` --- library/std/src/macros.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 7dcdd2c6246b1..15ff7afa45818 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -471,6 +471,7 @@ macro_rules! hash_map { /// /// The function's signature must still be valid on the documentation target. #[rustc_builtin_macro] +#[allow(unused_macros)] pub(crate) macro rustdoc_skip_body($item:item) { /* compiler built-in */ } From 072ded864312c7b052946ea2d8720b6db111f4f9 Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Sun, 25 Oct 2026 09:25:21 +0900 Subject: [PATCH 80/80] Add FIXME comments to `from_ascii_bytes_radix_impl` --- library/core/src/num/nonzero.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index c97e6f4c45395..d296d0ceb12b3 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1506,10 +1506,12 @@ macro_rules! nonzero_integer { #[inline] const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result { + // FIXME(const-hack): This should use `?` again, once it's `const` let n = match <$Int>::from_ascii_bytes_radix_impl(src, radix) { Ok(n) => n, Err(err) => return Err(err), }; + // FIXME(const-hack): This should use `Option::ok_or` again, once it's `const` if let Some(n) = Self::new(n) { Ok(n) } else {