From 0fae37518a75fae9dc4e3d0ef6ac65a15bfedbfd Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Sun, 10 May 2026 13:45:51 +0300 Subject: [PATCH 01/26] Constify Iterator-related methods and functions --- library/core/src/array/iter.rs | 9 ++++++--- library/core/src/array/iter/iter_inner.rs | 3 ++- library/core/src/iter/traits/marker.rs | 4 +++- library/core/src/ops/control_flow.rs | 12 ++++++++++-- library/core/src/ops/try_trait.rs | 10 ++++++++-- library/core/src/option.rs | 11 ++++++++--- library/core/src/result.rs | 7 ++++++- library/core/src/tuple.rs | 3 ++- 8 files changed, 45 insertions(+), 14 deletions(-) diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index cd2a9e00b5a6..7aed4a132406 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -23,11 +23,13 @@ pub struct IntoIter { impl IntoIter { #[inline] - fn unsize(&self) -> &InnerUnsized { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + const fn unsize(&self) -> &InnerUnsized { self.inner.deref() } #[inline] - fn unsize_mut(&mut self) -> &mut InnerUnsized { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + const fn unsize_mut(&mut self) -> &mut InnerUnsized { self.inner.deref_mut() } } @@ -219,7 +221,8 @@ pub fn as_slice(&self) -> &[T] { /// Returns a mutable slice of all elements that have not been yielded yet. #[stable(feature = "array_value_iter", since = "1.51.0")] #[inline] - pub fn as_mut_slice(&mut self) -> &mut [T] { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + pub const fn as_mut_slice(&mut self) -> &mut [T] { self.unsize_mut().as_mut_slice() } } diff --git a/library/core/src/array/iter/iter_inner.rs b/library/core/src/array/iter/iter_inner.rs index 3c2343591f8c..fa4ccf507e2f 100644 --- a/library/core/src/array/iter/iter_inner.rs +++ b/library/core/src/array/iter/iter_inner.rs @@ -134,7 +134,8 @@ pub(super) fn as_slice(&self) -> &[T] { } #[inline] - pub(super) fn as_mut_slice(&mut self) -> &mut [T] { + #[rustc_const_unstable(feature = "const_iter", issue = "92476")] + pub(super) const fn as_mut_slice(&mut self) -> &mut [T] { // SAFETY: We know that all elements within `alive` are properly initialized. unsafe { let slice = self.data.get_unchecked_mut(self.alive.clone()); diff --git a/library/core/src/iter/traits/marker.rs b/library/core/src/iter/traits/marker.rs index 2e756a6dd67c..542d283fe95a 100644 --- a/library/core/src/iter/traits/marker.rs +++ b/library/core/src/iter/traits/marker.rs @@ -63,9 +63,11 @@ impl FusedIterator for &mut I {} /// of this trait must inspect [`Iterator::size_hint()`]’s upper bound. #[unstable(feature = "trusted_len", issue = "37572")] #[rustc_unsafe_specialization_marker] -pub unsafe trait TrustedLen: Iterator {} +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +pub const unsafe trait TrustedLen: [const] Iterator {} #[unstable(feature = "trusted_len", issue = "37572")] +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] unsafe impl TrustedLen for &mut I {} /// An iterator that when yielding an item will have taken at least one element diff --git a/library/core/src/ops/control_flow.rs b/library/core/src/ops/control_flow.rs index 5f8974133a94..f279ef0bb85d 100644 --- a/library/core/src/ops/control_flow.rs +++ b/library/core/src/ops/control_flow.rs @@ -427,7 +427,11 @@ pub const fn into_value(self) -> T { impl ControlFlow { /// Creates a `ControlFlow` from any type implementing `Try`. #[inline] - pub(crate) fn from_try(r: R) -> Self { + #[rustc_const_unstable(feature = "const_control_flow", issue = "148739")] + pub(crate) const fn from_try(r: R) -> Self + where + R: [const] ops::Try, + { match R::branch(r) { ControlFlow::Continue(v) => ControlFlow::Continue(v), ControlFlow::Break(v) => ControlFlow::Break(R::from_residual(v)), @@ -436,7 +440,11 @@ pub(crate) fn from_try(r: R) -> Self { /// Converts a `ControlFlow` into any type implementing `Try`. #[inline] - pub(crate) fn into_try(self) -> R { + #[rustc_const_unstable(feature = "const_control_flow", issue = "148739")] + pub(crate) const fn into_try(self) -> R + where + R: [const] ops::Try, + { match self { ControlFlow::Continue(v) => R::from_output(v), ControlFlow::Break(v) => v, diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index aaa71786854d..420927864a1a 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -416,8 +416,14 @@ pub(crate) const fn wrap_mut_1( } #[inline] - pub(crate) fn wrap_mut_2(mut f: impl FnMut(A, B) -> T) -> impl FnMut(A, B) -> Self { - move |a, b| NeverShortCircuit(f(a, b)) + #[rustc_const_unstable(feature = "const_array", issue = "147606")] + pub(crate) const fn wrap_mut_2( + mut f: F, + ) -> impl [const] FnMut(A, B) -> Self + [const] Destruct + where + F: [const] FnMut(A, B) -> T + [const] Destruct, + { + const move |a, b| NeverShortCircuit(f(a, b)) } } diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 02cd88a6a434..2715981e2d66 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -1748,8 +1748,12 @@ pub const fn insert(&mut self, value: T) -> &mut T /// ``` #[inline] #[stable(feature = "option_entry", since = "1.20.0")] - pub fn get_or_insert(&mut self, value: T) -> &mut T { - self.get_or_insert_with(|| value) + #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] + pub const fn get_or_insert(&mut self, value: T) -> &mut T + where + T: [const] Destruct, + { + self.get_or_insert_with(const || value) } /// Inserts the default value into the option if it is [`None`], then @@ -2649,7 +2653,8 @@ impl ExactSizeIterator for IntoIter {} impl FusedIterator for IntoIter {} #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for IntoIter {} +#[rustc_const_unstable(feature = "const_iter", issue = "92476")] +unsafe impl const TrustedLen for IntoIter {} /// The iterator produced by [`Option::into_flat_iter`]. See its documentation for more. #[derive(Clone, Debug)] diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 5f438d72ac13..d2855d072350 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -1682,7 +1682,12 @@ pub const fn unwrap_or_else(self, op: F) -> T #[inline] #[track_caller] #[stable(feature = "option_result_unwrap_unchecked", since = "1.58.0")] - pub unsafe fn unwrap_err_unchecked(self) -> E { + #[rustc_const_unstable(feature = "const_result_unwrap_unchecked", issue = "148714")] + pub const unsafe fn unwrap_err_unchecked(self) -> E + where + T: [const] Destruct, + E: [const] Destruct, + { match self { // SAFETY: the safety contract must be upheld by the caller. Ok(_) => unsafe { hint::unreachable_unchecked() }, diff --git a/library/core/src/tuple.rs b/library/core/src/tuple.rs index 187e201c3cea..adfb027667fa 100644 --- a/library/core/src/tuple.rs +++ b/library/core/src/tuple.rs @@ -120,7 +120,8 @@ fn cmp(&self, other: &($($T,)+)) -> Ordering { maybe_tuple_doc! { $($T)+ @ #[stable(feature = "rust1", since = "1.0.0")] - impl<$($T: Default),+> Default for ($($T,)+) { + #[rustc_const_unstable(feature = "const_default", issue = "143894")] + impl<$($T: [const] Default),+> const Default for ($($T,)+) { #[inline] fn default() -> ($($T,)+) { ($({ let x: $T = Default::default(); x},)+) From 5e1ddfadfd5dc7a410ea024b612c793c3c2469d2 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Fri, 15 May 2026 00:23:09 +0900 Subject: [PATCH 02/26] ci: declare contents:read on Tier 2 sysroots workflow Signed-off-by: Arpit Jain --- src/tools/miri/.github/workflows/sysroots.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/miri/.github/workflows/sysroots.yml b/src/tools/miri/.github/workflows/sysroots.yml index 178c1a506277..a488e480c0c5 100644 --- a/src/tools/miri/.github/workflows/sysroots.yml +++ b/src/tools/miri/.github/workflows/sysroots.yml @@ -8,6 +8,9 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: sysroots: name: Build the sysroots From 7dd6d8b17cc75a5ee6fe410775ca85f7c2338a5b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 19 May 2026 14:32:49 +0200 Subject: [PATCH 03/26] rename set_last_error_and_return helper to make it clear what we return --- src/tools/miri/src/shims/io_error.rs | 6 +- src/tools/miri/src/shims/time.rs | 8 +- src/tools/miri/src/shims/unix/env.rs | 8 +- src/tools/miri/src/shims/unix/fd.rs | 36 +++--- .../miri/src/shims/unix/foreign_items.rs | 16 +-- .../src/shims/unix/freebsd/foreign_items.rs | 4 +- src/tools/miri/src/shims/unix/freebsd/sync.rs | 10 +- src/tools/miri/src/shims/unix/fs.rs | 90 +++++++------- .../miri/src/shims/unix/linux_like/epoll.rs | 18 +-- .../miri/src/shims/unix/linux_like/sync.rs | 12 +- src/tools/miri/src/shims/unix/macos/sync.rs | 14 +-- src/tools/miri/src/shims/unix/mem.rs | 4 +- src/tools/miri/src/shims/unix/socket.rs | 110 +++++++++--------- 13 files changed, 168 insertions(+), 168 deletions(-) diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 2e5410429efe..c715e38169c0 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -291,7 +291,7 @@ fn set_last_error(&mut self, err: impl Into) -> InterpResult<'tcx> { } /// Sets the last OS error and writes -1 to dest place. - fn set_last_error_and_return( + fn set_errno_and_return_neg1( &mut self, err: impl Into, dest: &MPlaceTy<'tcx>, @@ -303,7 +303,7 @@ fn set_last_error_and_return( } /// Sets the last OS error and return `-1` as a `i32`-typed Scalar - fn set_last_error_and_return_i32( + fn set_errno_and_return_neg1_i32( &mut self, err: impl Into, ) -> InterpResult<'tcx, Scalar> { @@ -313,7 +313,7 @@ fn set_last_error_and_return_i32( } /// Sets the last OS error and return `-1` as a `i64`-typed Scalar - fn set_last_error_and_return_i64( + fn set_errno_and_return_neg1_i64( &mut self, err: impl Into, ) -> InterpResult<'tcx, Scalar> { diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 87dded0766ad..9dfce51d2ea4 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -81,7 +81,7 @@ fn clock_gettime( .now() .duration_since(this.machine.monotonic_clock.epoch()), None => { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } }; @@ -109,7 +109,7 @@ fn gettimeofday( // Using tz is obsolete and should always be null let tz = this.read_pointer(tz_op)?; if !this.ptr_is_null(tz)? { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let duration = system_time_to_duration(&SystemTime::now())?; @@ -362,7 +362,7 @@ fn nanosleep(&mut self, duration: &OpTy<'tcx>, rem: &OpTy<'tcx>) -> InterpResult let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to. let Some(duration) = this.read_timespec(&duration)? else { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); }; let deadline = this.machine.monotonic_clock.now().add_lossy(duration); @@ -401,7 +401,7 @@ fn clock_nanosleep( } let Some(duration) = this.read_timespec(×pec)? else { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); }; let timeout_style = if flags == 0 { diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs index 97cc0d8eacf5..bd82b1ee9a08 100644 --- a/src/tools/miri/src/shims/unix/env.rs +++ b/src/tools/miri/src/shims/unix/env.rs @@ -164,7 +164,7 @@ fn setenv( interp_ok(Scalar::from_i32(0)) // return zero on success } else { // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - this.set_last_error_and_return_i32(LibcError("EINVAL")) + this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) } } @@ -188,7 +188,7 @@ fn unsetenv(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { interp_ok(Scalar::from_i32(0)) } else { // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. - this.set_last_error_and_return_i32(LibcError("EINVAL")) + this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) } } @@ -227,7 +227,7 @@ fn chdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`chdir`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let result = env::set_current_dir(path).map(|()| 0); @@ -288,7 +288,7 @@ fn uname( }; if this.ptr_is_null(uname_ptr)? { - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?; diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index ab17be0e04f4..dc13ced2b555 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -88,7 +88,7 @@ fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(old_fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; interp_ok(Scalar::from_i32(this.machine.fds.insert(fd))) } @@ -97,7 +97,7 @@ fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scala let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(old_fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; if new_fd_num != old_fd_num { // Close new_fd if it is previously opened. @@ -113,7 +113,7 @@ fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scala fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; // We need to check that there aren't unsupported options in `op`. @@ -159,7 +159,7 @@ fn ioctl( let arg = varargs.first(); let Some(fd) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; // Handle common opcodes. @@ -201,7 +201,7 @@ fn fcntl( // always sets this flag when opening a file. However we still need to check that the // file itself is open. if !this.machine.fds.is_fd_num(fd_num) { - this.set_last_error_and_return_i32(LibcError("EBADF")) + this.set_errno_and_return_neg1_i32(LibcError("EBADF")) } else { interp_ok(this.eval_libc("FD_CLOEXEC")) } @@ -223,13 +223,13 @@ fn fcntl( if let Some(fd) = this.machine.fds.get(fd_num) { interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start))) } else { - this.set_last_error_and_return_i32(LibcError("EBADF")) + this.set_errno_and_return_neg1_i32(LibcError("EBADF")) } } cmd if cmd == f_getfl => { // Check if this is a valid open file descriptor. let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; fd.get_flags(this) @@ -237,7 +237,7 @@ fn fcntl( cmd if cmd == f_setfl => { // Check if this is a valid open file descriptor. let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?; @@ -263,7 +263,7 @@ fn fcntl( // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fcntl`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } this.ffullsync_fd(fd_num) @@ -280,7 +280,7 @@ fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let fd_num = this.read_scalar(fd_op)?.to_i32()?; let Some(fd) = this.machine.fds.remove(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let result = fd.close_ref(this.machine.communicate(), this)?; // return `0` if close is successful @@ -320,7 +320,7 @@ fn read( // Get the FD. let Some(fd) = this.machine.fds.get(fd_num) else { trace!("read: FD not found"); - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; trace!("read: FD mapped to {fd:?}"); @@ -346,7 +346,7 @@ fn read( // This must fit since `count` fits. this.write_int(u64::try_from(read_size).unwrap(), &dest) } - Err(e) => this.set_last_error_and_return(e, &dest) + Err(e) => this.set_errno_and_return_neg1(e, &dest) }} ), ) @@ -376,7 +376,7 @@ fn write( // We temporarily dup the FD to be able to retain mutable access to `this`. let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let dest = dest.clone(); @@ -397,7 +397,7 @@ fn write( // This must fit since `count` fits. this.write_int(u64::try_from(write_size).unwrap(), &dest) } - Err(e) => this.set_last_error_and_return(e, &dest) + Err(e) => this.set_errno_and_return_neg1(e, &dest) }} ), @@ -435,7 +435,7 @@ fn readv( // Check that the FD exists. let Some(fd) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt); @@ -488,7 +488,7 @@ fn readv( this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?; u64::try_from(size).unwrap() }, - Err(e) => return this.set_last_error_and_return(e, &dest) + Err(e) => return this.set_errno_and_return_neg1(e, &dest) }; let mut remaining_bytes = bytes_read; @@ -555,7 +555,7 @@ fn writev( // Check that the FD exists. let Some(fd) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt); @@ -628,7 +628,7 @@ fn writev( this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?; match result { Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), - Err(e) => this.set_last_error_and_return(e, &dest) + Err(e) => this.set_errno_and_return_neg1(e, &dest) } }), ) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 67602a4fe88e..467304defb9b 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -1131,7 +1131,7 @@ fn emulate_foreign_item_inner( } else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) { // On Linux/Android, pid can be a TID as returned by `gettid`. let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else { - this.set_last_error_and_return(LibcError("ESRCH"), dest)?; + this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?; return interp_ok(EmulateItemResult::NeedsReturn); }; thread_id @@ -1145,10 +1145,10 @@ fn emulate_foreign_item_inner( let chunk_size = CpuAffinityMask::chunk_size(this); if this.ptr_is_null(mask)? { - this.set_last_error_and_return(LibcError("EFAULT"), dest)?; + this.set_errno_and_return_neg1(LibcError("EFAULT"), dest)?; } else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 { // we only copy whole chunks of size_of::() - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) { let cpuset = cpuset.clone(); // we only copy whole chunks of size_of::() @@ -1158,7 +1158,7 @@ fn emulate_foreign_item_inner( this.write_null(dest)?; } else { // The thread whose ID is pid could not be found - this.set_last_error_and_return(LibcError("ESRCH"), dest)?; + this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?; } } "sched_setaffinity" => { @@ -1176,7 +1176,7 @@ fn emulate_foreign_item_inner( } else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) { // On Linux/Android, pid can be a TID as returned by `gettid`. let Some(thread_id) = this.get_thread_id_from_linux_tid(pid) else { - this.set_last_error_and_return(LibcError("ESRCH"), dest)?; + this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?; return interp_ok(EmulateItemResult::NeedsReturn); }; thread_id @@ -1187,7 +1187,7 @@ fn emulate_foreign_item_inner( }; if this.ptr_is_null(mask)? { - this.set_last_error_and_return(LibcError("EFAULT"), dest)?; + this.set_errno_and_return_neg1(LibcError("EFAULT"), dest)?; } else { // NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`. // Any unspecified bytes are treated as zero here (none of the CPUs are configured). @@ -1204,7 +1204,7 @@ fn emulate_foreign_item_inner( } None => { // The intersection between the mask and the available CPUs was empty. - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; } } } @@ -1245,7 +1245,7 @@ fn emulate_foreign_item_inner( // macOS: https://keith.github.io/xcode-man-pages/getentropy.2.html // Solaris/Illumos: https://illumos.org/man/3C/getentropy if bufsize > 256 { - this.set_last_error_and_return(LibcError("EIO"), dest)?; + this.set_errno_and_return_neg1(LibcError("EIO"), dest)?; } else { this.gen_random(buf, bufsize)?; this.write_null(dest)?; 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 332bd26eff89..db3975a5bdfa 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -96,7 +96,7 @@ fn emulate_foreign_item_inner( }; if this.ptr_is_null(mask)? { - this.set_last_error_and_return(LibcError("EFAULT"), dest)?; + this.set_errno_and_return_neg1(LibcError("EFAULT"), dest)?; } // We only support CPU_LEVEL_WHICH and CPU_WHICH_PID for now. // This is the bare minimum to make the tests pass. @@ -111,7 +111,7 @@ fn emulate_foreign_item_inner( // If it's large enough, copying the kernel mask to user space is safe, regardless of the actual size. // See https://github.com/freebsd/freebsd-src/blob/909aa6781340f8c0b4ae01c6366bf1556ee2d1be/sys/kern/kern_cpuset.c#L1985 if set_size < u64::from(this.machine.num_cpus).div_ceil(8) { - this.set_last_error_and_return(LibcError("ERANGE"), dest)?; + this.set_errno_and_return_neg1(LibcError("ERANGE"), dest)?; } else { let cpuset = cpuset.clone(); let byte_count = diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs index 32338391f2dd..28f912ab973f 100644 --- a/src/tools/miri/src/shims/unix/freebsd/sync.rs +++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs @@ -102,7 +102,7 @@ fn _umtx_op( let umtx_time_place = this.ptr_to_mplace(uaddr2, umtx_time_layout); let Some(umtx_time) = this.read_umtx_time(&umtx_time_place)? else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); }; let style = if umtx_time.abs_time { @@ -123,7 +123,7 @@ fn _umtx_op( // `uaddr2` points to a `struct timespec`. let timespec = this.ptr_to_mplace(uaddr2, timespec_layout); let Some(duration) = this.read_timespec(×pec)? else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); }; // FreeBSD does not seem to document which clock is used when the timeout @@ -137,7 +137,7 @@ fn _umtx_op( duration, )) } else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } }; @@ -158,7 +158,7 @@ fn _umtx_op( ecx.write_int(0, &dest) } UnblockKind::TimedOut => { - ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest) + ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest) } } ), @@ -182,7 +182,7 @@ fn _umtx_op( // Return an error code. (That seems nicer than silently doing something non-intuitive.) // This means that if an address gets reused by a new allocation, // we'll use an independent futex queue for this... that seems acceptable. - return this.set_last_error_and_return(LibcError("EFAULT"), dest); + return this.set_errno_and_return_neg1(LibcError("EFAULT"), dest); }; let futex_ref = futex_ref.futex.clone(); diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index a5b3f891d9ca..67d25d78474e 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -457,7 +457,7 @@ fn open( let o_tmpfile = this.eval_libc_i32("O_TMPFILE"); if flag & o_tmpfile == o_tmpfile { // if the flag contains `O_TMPFILE` then we return a graceful error - return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP")); + return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP")); } } @@ -477,7 +477,7 @@ fn open( // O_NOFOLLOW only fails when the trailing component is a symlink; // the entire rest of the path can still contain symlinks. if path.is_symlink() { - return this.set_last_error_and_return_i32(LibcError("ELOOP")); + return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); } } } @@ -490,7 +490,7 @@ fn open( // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`open`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let fd = options @@ -514,7 +514,7 @@ fn lseek( let seek_from = if whence == this.eval_libc_i32("SEEK_SET") { if offset < 0 { // Negative offsets return `EINVAL`. - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } else { SeekFrom::Start(u64::try_from(offset).unwrap()) } @@ -523,13 +523,13 @@ fn lseek( } else if whence == this.eval_libc_i32("SEEK_END") { SeekFrom::End(i64::try_from(offset).unwrap()) } else { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); }; let communicate = this.machine.communicate(); let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap()); drop(fd); @@ -547,7 +547,7 @@ fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`unlink`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let result = fs::remove_file(path).map(|_| 0); @@ -577,7 +577,7 @@ fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`symlink`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let result = create_link(&target, &linkpath).map(|_| 0); @@ -600,13 +600,13 @@ fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'t // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`stat`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } // `stat` always follows symlinks. let metadata = match FileMetadata::from_path(this, &path, true)? { Ok(metadata) => metadata, - Err(err) => return this.set_last_error_and_return_i32(err), + Err(err) => return this.set_errno_and_return_neg1_i32(err), }; interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) @@ -629,12 +629,12 @@ fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<' // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`lstat`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } let metadata = match FileMetadata::from_path(this, &path, false)? { Ok(metadata) => metadata, - Err(err) => return this.set_last_error_and_return_i32(err), + Err(err) => return this.set_errno_and_return_neg1_i32(err), }; interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) @@ -656,12 +656,12 @@ fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tc if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fstat`", reject_with)?; // Set error code as "EBADF" (bad fd) - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } let metadata = match FileMetadata::from_fd_num(this, fd)? { Ok(metadata) => metadata, - Err(err) => return this.set_last_error_and_return_i32(err), + Err(err) => return this.set_errno_and_return_neg1_i32(err), }; interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?)) } @@ -686,7 +686,7 @@ fn linux_statx( // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`. if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? { - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?; @@ -727,7 +727,7 @@ fn linux_statx( assert!(empty_path_flag); LibcError("EBADF") }; - return this.set_last_error_and_return_i32(ecode); + return this.set_errno_and_return_neg1_i32(ecode); } // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following @@ -743,7 +743,7 @@ fn linux_statx( }; let metadata = match metadata { Ok(metadata) => metadata, - Err(err) => return this.set_last_error_and_return_i32(err), + Err(err) => return this.set_errno_and_return_neg1_i32(err), }; // The `_mask_op` parameter specifies the file information that the caller requested. @@ -861,19 +861,19 @@ fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult< let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; if this.ptr_is_null(path_ptr)? { - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } let path = this.read_path_from_c_str(path_ptr)?; // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`chmod`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; if let Err(err) = fs::set_permissions(path, permissions) { - return this.set_last_error_and_return_i32(err); + return this.set_errno_and_return_neg1_i32(err); } interp_ok(Scalar::from_i32(0)) @@ -886,7 +886,7 @@ fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<' let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?; let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(file) = fd.downcast::() else { // The docs don't talk about what happens for non-regular files... @@ -896,12 +896,12 @@ fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<' // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fchmod`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; if let Err(err) = file.file.set_permissions(permissions) { - return this.set_last_error_and_return_i32(err); + return this.set_errno_and_return_neg1_i32(err); } interp_ok(Scalar::from_i32(0)) @@ -918,7 +918,7 @@ fn rename( let newpath_ptr = this.read_pointer(newpath_op)?; if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? { - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } let oldpath = this.read_path_from_c_str(oldpath_ptr)?; @@ -927,7 +927,7 @@ fn rename( // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`rename`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let result = fs::rename(oldpath, newpath).map(|_| 0); @@ -950,7 +950,7 @@ fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult< // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`mkdir`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } #[cfg_attr(not(unix), allow(unused_mut))] @@ -977,7 +977,7 @@ fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`rmdir`", reject_with)?; - return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } let result = fs::remove_dir(path).map(|_| 0i32); @@ -1235,11 +1235,11 @@ fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`closedir`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; if let Some(entry) = open_dir.entry.take() { this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?; @@ -1257,11 +1257,11 @@ fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scala if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`ftruncate64`", reject_with)?; // Set error code as "EBADF" (bad fd) - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(file) = fd.downcast::() else { @@ -1276,11 +1276,11 @@ fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scala let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; interp_ok(Scalar::from_i32(result)) } else { - this.set_last_error_and_return_i32(LibcError("EINVAL")) + this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) } } else { // The file is not writable - this.set_last_error_and_return_i32(LibcError("EINVAL")) + this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) } } @@ -1356,7 +1356,7 @@ fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fsync`", reject_with)?; // Set error code as "EBADF" (bad fd) - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } self.ffullsync_fd(fd) @@ -1365,7 +1365,7 @@ fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(fd_num) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let file = fd.downcast::().ok_or_else(|| { @@ -1384,11 +1384,11 @@ fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fdatasync`", reject_with)?; // Set error code as "EBADF" (bad fd) - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let file = fd.downcast::().ok_or_else(|| { @@ -1413,24 +1413,24 @@ fn sync_file_range( let flags = this.read_scalar(flags_op)?.to_i32()?; if offset < 0 || nbytes < 0 { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE") | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE") | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER"); if flags & allowed_flags != flags { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`sync_file_range`", reject_with)?; // Set error code as "EBADF" (bad fd) - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let file = fd.downcast::().ok_or_else(|| { @@ -1590,7 +1590,7 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`mkstemp`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } // Get the bytes of the suffix we expect in _target_ encoding. @@ -1606,7 +1606,7 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { // If we don't find the suffix, it is an error. if last_six_char_bytes != suffix_bytes { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } // At this point we know we have 6 ASCII 'X' characters as a suffix. @@ -1667,14 +1667,14 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { _ => { // "On error, -1 is returned, and errno is set to // indicate the error" - return this.set_last_error_and_return_i32(e); + return this.set_errno_and_return_neg1_i32(e); } }, } } // We ran out of attempts to create the file, return an error. - this.set_last_error_and_return_i32(LibcError("EEXIST")) + this.set_errno_and_return_neg1_i32(LibcError("EEXIST")) } } diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index d2d978bffee7..3ecc2c1135de 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -279,12 +279,12 @@ fn epoll_ctl( // Throw EFAULT if epfd and fd have the same value. if epfd_value == fd { - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } // Check if epfd is a valid epoll file descriptor. let Some(epfd) = this.machine.fds.get(epfd_value) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let epfd = epfd .downcast::() @@ -293,7 +293,7 @@ fn epoll_ctl( let mut interest_list = epfd.interest_list.borrow_mut(); let Some(fd_ref) = this.machine.fds.get(fd) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let id = fd_ref.id(); @@ -356,12 +356,12 @@ fn epoll_ctl( }; if interest_list.try_insert(epoll_key, new_interest).is_err() { // We already had interest in this. - return this.set_last_error_and_return_i32(LibcError("EEXIST")); + return this.set_errno_and_return_neg1_i32(LibcError("EEXIST")); } } else { // Modify the existing interest. let Some(interest) = interest_list.get_mut(&epoll_key) else { - return this.set_last_error_and_return_i32(LibcError("ENOENT")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOENT")); }; interest.relevant_events = events; interest.data = data; @@ -389,7 +389,7 @@ fn epoll_ctl( // Remove epoll_event_interest from interest_list and ready_set. if interest_list.remove(&epoll_key).is_none() { // We did not have interest in this. - return this.set_last_error_and_return_i32(LibcError("ENOENT")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOENT")); }; epfd.ready_set.borrow_mut().remove(&epoll_key); // If this was the last interest in this FD, remove us from the global list @@ -452,7 +452,7 @@ fn epoll_wait( let timeout = this.read_scalar(timeout)?.to_i32()?; if epfd_value <= 0 || maxevents <= 0 { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } // This needs to come after the maxevents value check, or else maxevents.try_into().unwrap() @@ -463,10 +463,10 @@ fn epoll_wait( )?; let Some(epfd) = this.machine.fds.get(epfd_value) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(epfd) = epfd.downcast::() else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; if timeout == 0 || !epfd.ready_set.borrow().is_empty() { diff --git a/src/tools/miri/src/shims/unix/linux_like/sync.rs b/src/tools/miri/src/shims/unix/linux_like/sync.rs index 5a1a9ed53594..87d73d7f2e28 100644 --- a/src/tools/miri/src/shims/unix/linux_like/sync.rs +++ b/src/tools/miri/src/shims/unix/linux_like/sync.rs @@ -64,7 +64,7 @@ pub fn futex<'tcx>( }; if bitset == 0 { - return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } let timeout = ecx.deref_pointer_as(timeout, ecx.libc_ty_layout("timespec"))?; @@ -72,7 +72,7 @@ pub fn futex<'tcx>( None } else { let Some(duration) = ecx.read_timespec(&timeout)? else { - return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest); }; let timeout_clock = if op & futex_realtime == futex_realtime { ecx.check_no_isolation( @@ -164,7 +164,7 @@ pub fn futex<'tcx>( ecx.write_int(0, &dest) } UnblockKind::TimedOut => { - ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest) + ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest) } } ), @@ -172,7 +172,7 @@ pub fn futex<'tcx>( } else { // The futex value doesn't match the expected value, so we return failure // right away without sleeping: -1 and errno set to EAGAIN. - return ecx.set_last_error_and_return(LibcError("EAGAIN"), dest); + return ecx.set_errno_and_return_neg1(LibcError("EAGAIN"), dest); } } // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val) @@ -189,7 +189,7 @@ pub fn futex<'tcx>( // Return an error code. (That seems nicer than silently doing something non-intuitive.) // This means that if an address gets reused by a new allocation, // we'll use an independent futex queue for this... that seems acceptable. - return ecx.set_last_error_and_return(LibcError("EFAULT"), dest); + return ecx.set_errno_and_return_neg1(LibcError("EFAULT"), dest); }; let futex_ref = futex_ref.futex.clone(); @@ -205,7 +205,7 @@ pub fn futex<'tcx>( u32::MAX }; if bitset == 0 { - return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest); } // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait // will see the latest value on addr which could be changed by our caller diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs index ea0fd5c5b0a3..b93dc4871450 100644 --- a/src/tools/miri/src/shims/unix/macos/sync.rs +++ b/src/tools/miri/src/shims/unix/macos/sync.rs @@ -163,7 +163,7 @@ fn os_sync_wait_on_address( || clock_timeout .is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0) { - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; return interp_ok(()); } @@ -202,7 +202,7 @@ fn os_sync_wait_on_address( futex.size.set(size); futex.shared.set(is_shared); } else if futex.size.get() != size || futex.shared.get() != is_shared { - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; return interp_ok(()); } @@ -226,7 +226,7 @@ fn os_sync_wait_on_address( this.write_scalar(Scalar::from_i32(remaining), &dest) } UnblockKind::TimedOut => { - this.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest) + this.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest) } } } @@ -264,7 +264,7 @@ fn os_sync_wake_by_address( // Perform validation of the arguments. let addr = ptr.addr().bytes(); if addr == 0 || !matches!(size, 4 | 8) || (flags != none && flags != shared) { - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; return interp_ok(()); } @@ -282,19 +282,19 @@ fn os_sync_wake_by_address( // non-intuitive.) This means that if an address gets reused by a // new allocation, we'll use an independent futex queue for this... // that seems acceptable. - this.set_last_error_and_return(LibcError("ENOENT"), dest)?; + this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?; return interp_ok(()); }; if futex.futex.waiters() == 0 { - this.set_last_error_and_return(LibcError("ENOENT"), dest)?; + this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?; return interp_ok(()); // If there are waiters in the queue, they have all used the parameters // stored in `futex` (we check this in `os_sync_wait_on_address` above). // Detect mismatches between "our" parameters and the parameters used by // the waiters and return an error in that case. } else if futex.size.get() != size || futex.shared.get() != is_shared { - this.set_last_error_and_return(LibcError("EINVAL"), dest)?; + this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; return interp_ok(()); } diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs index 01aa5afafcc3..c2ad7c0e9d0a 100644 --- a/src/tools/miri/src/shims/unix/mem.rs +++ b/src/tools/miri/src/shims/unix/mem.rs @@ -132,11 +132,11 @@ fn munmap(&mut self, addr: &OpTy<'tcx>, length: &OpTy<'tcx>) -> InterpResult<'tc // addr must be a multiple of the page size, but apart from that munmap is just implemented // as a dealloc. if !addr.addr().bytes().is_multiple_of(this.machine.page_size) { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else { - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); }; if length > this.target_usize_max() { this.set_last_error(LibcError("EINVAL"))?; diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 2e9a26974eff..e7754ceedd7c 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -295,7 +295,7 @@ fn socket( // Reject if isolation is enabled if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`socket`", reject_with)?; - return this.set_last_error_and_return_i32(LibcError("EACCES")); + return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } let mut is_sock_nonblock = false; @@ -369,17 +369,17 @@ fn bind( let socket = this.read_scalar(socket)?.to_i32()?; let address = match this.read_socket_address(address, address_len, "bind")? { Ok(addr) => addr, - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), }; // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -407,7 +407,7 @@ fn bind( // See LibcError("EAFNOSUPPORT") }; - return this.set_last_error_and_return_i32(err); + return this.set_errno_and_return_neg1_i32(err); } *state = SocketState::Bound(address); @@ -437,12 +437,12 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult< // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -460,7 +460,7 @@ fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult< // we now have an associated host socket. this.machine.blocking_io.register(socket); } - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), }, SocketState::Initial => { throw_unsup_format!( @@ -500,12 +500,12 @@ fn accept4( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -562,7 +562,7 @@ fn accept4( // See . this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest) } - Err(e) => this.set_last_error_and_return(e, dest), + Err(e) => this.set_errno_and_return_neg1(e, dest), } } else { // The socket is in blocking mode and thus the accept call should block @@ -590,17 +590,17 @@ fn connect( let socket = this.read_scalar(socket)?.to_i32()?; let address = match this.read_socket_address(address, address_len, "connect")? { Ok(address) => address, - Err(e) => return this.set_last_error_and_return(e, dest), + Err(e) => return this.set_errno_and_return_neg1(e, dest), }; // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket - return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -610,7 +610,7 @@ fn connect( SocketState::Initial => { /* fall-through to below */ } // The socket is already in a connecting state. SocketState::Connecting(_) => - return this.set_last_error_and_return(LibcError("EALREADY"), dest), + return this.set_errno_and_return_neg1(LibcError("EALREADY"), dest), // We don't return EISCONN for already connected sockets, for which we're // sure that the connection is established, since TCP sockets are usually // allowed to be connected multiple times. @@ -630,7 +630,7 @@ fn connect( // we now have an associated host socket. this.machine.blocking_io.register(socket.clone()); } - Err(e) => return this.set_last_error_and_return(e, dest), + Err(e) => return this.set_errno_and_return_neg1(e, dest), }; if socket.is_non_block.get() { @@ -640,7 +640,7 @@ fn connect( // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS // we just always return EINPROGRESS and check whether the connection succeeded // once we want to use the connected socket. - this.set_last_error_and_return(LibcError("EINPROGRESS"), dest) + this.set_errno_and_return_neg1(LibcError("EINPROGRESS"), dest) } else { // The socket is in blocking mode and thus the connect call should block // until the connection with the server is established. @@ -661,7 +661,7 @@ fn connect( // that it has been consumed by `ensure_connected` // and is now stored in `socket.error`. let err = socket.error.take().unwrap(); - this.set_last_error_and_return(err, &dest) + this.set_errno_and_return_neg1(err, &dest) } else { this.write_scalar(Scalar::from_i32(0), &dest) } @@ -691,12 +691,12 @@ fn send( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket - return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; let mut is_op_non_block = false; @@ -748,7 +748,7 @@ fn send( dest: MPlaceTy<'tcx>, } |this, result: Result<(), ()>| { if result.is_err() { - return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest) + return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest) } if is_op_non_block || socket.is_non_block.get() { @@ -756,7 +756,7 @@ fn send( // thus don't want to block until we can send. match this.try_non_block_send(&socket, buffer_ptr, length)? { Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), - Err(e) => this.set_last_error_and_return(e, &dest), + Err(e) => this.set_errno_and_return_neg1(e, &dest), } } else { // The socket is in blocking mode and thus the send call should block @@ -770,7 +770,7 @@ fn send( } |this, result: Result| { match result { Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), - Err(e) => this.set_last_error_and_return(e, &dest) + Err(e) => this.set_errno_and_return_neg1(e, &dest) } }), ) @@ -800,12 +800,12 @@ fn recv( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket - return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; let mut should_peek = false; @@ -869,7 +869,7 @@ fn recv( dest: MPlaceTy<'tcx>, } |this, result: Result<(), ()>| { if result.is_err() { - return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest) + return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest) } if is_op_non_block || socket.is_non_block.get() { @@ -877,7 +877,7 @@ fn recv( // thus don't want to block until we can receive. match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? { Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), - Err(e) => this.set_last_error_and_return(e, &dest), + Err(e) => this.set_errno_and_return_neg1(e, &dest), } } else { // The socket is in blocking mode and thus the receive call should block @@ -892,7 +892,7 @@ fn recv( } |this, result: Result| { match result { Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest), - Err(e) => this.set_last_error_and_return(e, &dest) + Err(e) => this.set_errno_and_return_neg1(e, &dest) } }), ) @@ -921,12 +921,12 @@ fn setsockopt( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; if level == this.eval_libc_i32("SOL_SOCKET") { @@ -939,7 +939,7 @@ fn setsockopt( if option_name == opt_so_nosigpipe { if option_len != 4 { // Option value should be C-int which is usually 4 bytes. - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); @@ -953,7 +953,7 @@ fn setsockopt( if option_name == opt_so_reuseaddr { if option_len != 4 { // Option value should be C-int which is usually 4 bytes. - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); let _val = this.read_scalar(&option_value)?.to_i32()?; @@ -971,7 +971,7 @@ fn setsockopt( if option_name == opt_ip_ttl { if option_len != 4 { // Option value should be C-uint which is usually 4 bytes. - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32); let ttl = this.read_scalar(&option_value)?.to_u32()?; @@ -990,7 +990,7 @@ fn setsockopt( return match result { Ok(_) => interp_ok(Scalar::from_i32(0)), - Err(e) => this.set_last_error_and_return_i32(e), + Err(e) => this.set_errno_and_return_neg1_i32(e), }; } else { throw_unsup_format!( @@ -1003,7 +1003,7 @@ fn setsockopt( if option_name == opt_tcp_nodelay { if option_len != 4 { // Option value should be C-int which is usually 4 bytes. - return this.set_last_error_and_return_i32(LibcError("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32); let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0; @@ -1021,7 +1021,7 @@ fn setsockopt( return match result { Ok(_) => interp_ok(Scalar::from_i32(0)), - Err(e) => this.set_last_error_and_return_i32(e), + Err(e) => this.set_errno_and_return_neg1_i32(e), }; } else { throw_unsup_format!( @@ -1059,18 +1059,18 @@ fn getsockopt( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() { // This socket option returns a value and thus we need to return EFAULT // when either the value or the length pointers are null pointers. - return this.set_last_error_and_return_i32(LibcError("EFAULT")); + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); } let socklen_layout = this.libc_ty_layout("socklen_t"); @@ -1132,7 +1132,7 @@ fn getsockopt( let ttl = match ttl { Ok(ttl) => ttl, - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), }; // Allocate new buffer on the stack with the `u32` layout. @@ -1161,7 +1161,7 @@ fn getsockopt( let nodelay = match nodelay { Ok(nodelay) => nodelay, - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), }; // Allocate new buffer on the stack with the `i32` layout. @@ -1220,12 +1220,12 @@ fn getsockname( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -1250,7 +1250,7 @@ fn getsockname( SocketState::Listening(listener) => match listener.local_addr() { Ok(address) => address, - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), }, SocketState::Connecting(stream) | SocketState::Connected(stream) => { if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) { @@ -1267,7 +1267,7 @@ fn getsockname( } match stream.local_addr() { Ok(address) => address, - Err(e) => return this.set_last_error_and_return_i32(e), + Err(e) => return this.set_errno_and_return_neg1_i32(e), } } // For non-bound sockets the POSIX manual says the returned address is unspecified. @@ -1296,12 +1296,12 @@ fn getpeername( // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return(LibcError("EBADF"), dest); + return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); + return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -1322,7 +1322,7 @@ fn getpeername( dest: MPlaceTy<'tcx>, } |this, result: Result<(), ()>| { if result.is_err() { - return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest) + return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest) }; let SocketState::Connected(stream) = &*socket.state.borrow() else { @@ -1331,7 +1331,7 @@ fn getpeername( let address = match stream.peer_addr() { Ok(address) => address, - Err(e) => return this.set_last_error_and_return(e, &dest), + Err(e) => return this.set_errno_and_return_neg1(e, &dest), }; this.write_socket_address( @@ -1354,12 +1354,12 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t // Get the file handle let Some(fd) = this.machine.fds.get(socket) else { - return this.set_last_error_and_return_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(socket) = fd.downcast::() else { // Man page specifies to return ENOTSOCK if `fd` is not a socket. - return this.set_last_error_and_return_i32(LibcError("ENOTSOCK")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!"); @@ -1368,7 +1368,7 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t let state = socket.state.borrow(); let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else { - return this.set_last_error_and_return_i32(LibcError("ENOTCONN")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTCONN")); }; let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD"); @@ -1380,11 +1380,11 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t _ if is_write_shutdown => Shutdown::Write, _ if is_read_write_shutdown => Shutdown::Both, // An invalid value was passed to `how`. - _ => return this.set_last_error_and_return_i32(LibcError("EINVAL")), + _ => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")), }; if let Err(e) = stream.shutdown(how) { - return this.set_last_error_and_return_i32(e); + return this.set_errno_and_return_neg1_i32(e); }; drop(state); @@ -1458,7 +1458,7 @@ fn block_for_accept( // We need to block the thread again as it would still block. this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest) } - Err(e) => this.set_last_error_and_return(e, &dest), + Err(e) => this.set_errno_and_return_neg1(e, &dest), } }), ) From 1c4042bc61916ed22fe2b6bf37134b76ea1b6403 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 20 May 2026 06:13:28 +0000 Subject: [PATCH 04/26] Prepare for merging from rust-lang/rust This updates the rust-version file to 1f8e04d34ab0c1fd9574840aa6db670e41593bfb. --- 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 5e581cb38e5e..4a823e1b6430 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -281c97c3240a9abd984ca0c6a2cd7389115e80d5 +1f8e04d34ab0c1fd9574840aa6db670e41593bfb From d4406dde640abf96fdddb74cc867ce47e650bd01 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Wed, 20 May 2026 08:35:26 +0200 Subject: [PATCH 05/26] fix: enable filling up write buffer on Windows hosts --- .../pass-dep/libc/libc-socket-no-blocking.rs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index 4ae7436cac65..ff941fccf795 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -337,23 +337,23 @@ fn test_send_recv_nonblock() { .unwrap() }; - if !cfg!(windows_host) { - // Keep sending data until the buffer is full and we block. - // We cannot test this on Windows since there apparently the send buffer - // never fills up, at least for localhost connections. - - let fill_buf = [1u8; 5_000_000]; - // This fills the socket receive buffer and thus should start blocking. - let err = unsafe { + let fill_buf = [1u8; 32_000]; + // Keep sending data until the buffer is full and we would block. + loop { + let result = unsafe { libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, 0), ) - .unwrap_err() }; - assert_eq!(err.kind(), ErrorKind::WouldBlock) + + match result { + Ok(_) => { /* continue to fill buffer */ } + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } } server_thread.join().unwrap(); @@ -453,23 +453,23 @@ fn test_send_recv_dontwait() { .unwrap() }; - if !cfg!(windows_host) { - // Keep sending data until the buffer is full and we block. - // We cannot test this on Windows since there apparently the send buffer - // never fills up, at least for localhost connections. - - let fill_buf = [1u8; 5_000_000]; - // This fills the socket receive buffer and thus should start blocking. - let err = unsafe { + let fill_buf = [1u8; 32_000]; + // Keep sending data until the buffer is full and we would block. + loop { + let result = unsafe { libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::send(client_sockfd, buf, count, libc::MSG_DONTWAIT), ) - .unwrap_err() }; - assert_eq!(err.kind(), ErrorKind::WouldBlock) + + match result { + Ok(_) => { /* continue to fill buffer */ } + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } } server_thread.join().unwrap(); @@ -540,23 +540,23 @@ fn test_write_read_nonblock() { // Writing into the empty buffer should succeed without blocking. libc_utils::write_all(client_sockfd, TEST_BYTES).unwrap(); - if !cfg!(windows_host) { - // Keep sending data until the buffer is full and we block. - // We cannot test this on Windows since there apparently the send buffer - // never fills up, at least for localhost connections. - - let fill_buf = [1u8; 5_000_000]; - // This fills the socket receive buffer and thus should start blocking. - let err = unsafe { + let fill_buf = [1u8; 32_000]; + // Keep sending data until the buffer is full and we would block. + loop { + let result = unsafe { libc_utils::write_all_generic( fill_buf.as_ptr().cast(), fill_buf.len(), libc_utils::NoRetry, |buf, count| libc::write(client_sockfd, buf, count), ) - .unwrap_err() }; - assert_eq!(err.kind(), ErrorKind::WouldBlock) + + match result { + Ok(_) => { /* continue to fill buffer */ } + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } } server_thread.join().unwrap(); From c328459c20cb19637fbb22531317633e5d64bdef Mon Sep 17 00:00:00 2001 From: Haoran Wang Date: Sat, 23 May 2026 12:53:44 +0800 Subject: [PATCH 06/26] Clarify "infinite size" in cyclic-type diagnostic refers to the type name --- compiler/rustc_middle/src/ty/error.rs | 2 +- .../ui/closures/closure-referencing-itself-issue-25954.stderr | 2 +- tests/ui/closures/generic-typed-nested-closures-59494.stderr | 2 +- tests/ui/closures/issue-25439.stderr | 2 +- tests/ui/const-generics/occurs-check/unused-substs-2.rs | 2 +- tests/ui/const-generics/occurs-check/unused-substs-2.stderr | 2 +- tests/ui/const-generics/occurs-check/unused-substs-3.rs | 2 +- tests/ui/const-generics/occurs-check/unused-substs-3.stderr | 2 +- tests/ui/const-generics/occurs-check/unused-substs-5.stderr | 2 +- .../coroutine/coroutine-yielding-or-returning-itself.stderr | 4 ++-- tests/ui/typeck/cyclic_type_ice.stderr | 2 +- .../ui/unboxed-closures/unboxed-closure-no-cyclic-sig.stderr | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 52f37ed4a9ea..81cd3efffc9d 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -35,7 +35,7 @@ fn report_maybe_different(expected: &str, found: &str) -> String { } match self { - TypeError::CyclicTy(_) => "cyclic type of infinite size".into(), + TypeError::CyclicTy(_) => "recursive type with infinite-size name".into(), TypeError::CyclicConst(_) => "encountered a self-referencing constant".into(), TypeError::Mismatch => "types differ".into(), TypeError::PolarityMismatch(values) => { diff --git a/tests/ui/closures/closure-referencing-itself-issue-25954.stderr b/tests/ui/closures/closure-referencing-itself-issue-25954.stderr index 22b7be4c729b..e189dd70e68d 100644 --- a/tests/ui/closures/closure-referencing-itself-issue-25954.stderr +++ b/tests/ui/closures/closure-referencing-itself-issue-25954.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/closure-referencing-itself-issue-25954.rs:16:13 | LL | let q = || p.b.set(5i32); - | ^^^^^^^^^^^^^^^^ cyclic type of infinite size + | ^^^^^^^^^^^^^^^^ recursive type with infinite-size name error: aborting due to 1 previous error diff --git a/tests/ui/closures/generic-typed-nested-closures-59494.stderr b/tests/ui/closures/generic-typed-nested-closures-59494.stderr index 9706fea82a30..3a0ed68f2026 100644 --- a/tests/ui/closures/generic-typed-nested-closures-59494.stderr +++ b/tests/ui/closures/generic-typed-nested-closures-59494.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/generic-typed-nested-closures-59494.rs:21:40 | LL | let t7 = |env| |a| |b| t7p(f, g)(((env, a), b)); - | ^^^ cyclic type of infinite size + | ^^^ recursive type with infinite-size name error: aborting due to 1 previous error diff --git a/tests/ui/closures/issue-25439.stderr b/tests/ui/closures/issue-25439.stderr index 19a26396a5d9..c48423cbf576 100644 --- a/tests/ui/closures/issue-25439.stderr +++ b/tests/ui/closures/issue-25439.stderr @@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself --> $DIR/issue-25439.rs:8:9 | LL | fix(|_, x| x); - | ^^^^^^ cyclic type of infinite size + | ^^^^^^ recursive type with infinite-size name | = note: closures cannot capture themselves or take themselves as argument; this error may be the result of a recent compiler bug-fix, diff --git a/tests/ui/const-generics/occurs-check/unused-substs-2.rs b/tests/ui/const-generics/occurs-check/unused-substs-2.rs index 5bdd3e39806e..fa44079be22d 100644 --- a/tests/ui/const-generics/occurs-check/unused-substs-2.rs +++ b/tests/ui/const-generics/occurs-check/unused-substs-2.rs @@ -21,7 +21,7 @@ fn bind() -> (T, Self) { fn main() { let (mut t, foo) = Foo::bind(); //~^ ERROR mismatched types - //~| NOTE cyclic type + //~| NOTE recursive type // `t` is `ty::Infer(TyVar(?1t))` // `foo` contains `ty::Infer(TyVar(?1t))` in its substs diff --git a/tests/ui/const-generics/occurs-check/unused-substs-2.stderr b/tests/ui/const-generics/occurs-check/unused-substs-2.stderr index a2c4dec47243..bb5a9ad04417 100644 --- a/tests/ui/const-generics/occurs-check/unused-substs-2.stderr +++ b/tests/ui/const-generics/occurs-check/unused-substs-2.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/unused-substs-2.rs:22:24 | LL | let (mut t, foo) = Foo::bind(); - | ^^^^^^^^^^^ cyclic type of infinite size + | ^^^^^^^^^^^ recursive type with infinite-size name error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/occurs-check/unused-substs-3.rs b/tests/ui/const-generics/occurs-check/unused-substs-3.rs index dfb051192e2f..45f9082465e0 100644 --- a/tests/ui/const-generics/occurs-check/unused-substs-3.rs +++ b/tests/ui/const-generics/occurs-check/unused-substs-3.rs @@ -12,7 +12,7 @@ fn main() { let (mut t, foo) = bind(); //~^ ERROR mismatched types - //~| NOTE cyclic type + //~| NOTE recursive type // `t` is `ty::Infer(TyVar(?1t))` // `foo` contains `ty::Infer(TyVar(?1t))` in its substs diff --git a/tests/ui/const-generics/occurs-check/unused-substs-3.stderr b/tests/ui/const-generics/occurs-check/unused-substs-3.stderr index 30a2a7901bdf..b93903f1112f 100644 --- a/tests/ui/const-generics/occurs-check/unused-substs-3.stderr +++ b/tests/ui/const-generics/occurs-check/unused-substs-3.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/unused-substs-3.rs:13:24 | LL | let (mut t, foo) = bind(); - | ^^^^^^ cyclic type of infinite size + | ^^^^^^ recursive type with infinite-size name error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/occurs-check/unused-substs-5.stderr b/tests/ui/const-generics/occurs-check/unused-substs-5.stderr index 46230d455b27..c24061b9e330 100644 --- a/tests/ui/const-generics/occurs-check/unused-substs-5.stderr +++ b/tests/ui/const-generics/occurs-check/unused-substs-5.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/unused-substs-5.rs:15:19 | LL | x = q::<_, N>(x); - | ^ cyclic type of infinite size + | ^ recursive type with infinite-size name error: aborting due to 1 previous error diff --git a/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr b/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr index 32799148ae1d..0c153e2d0af3 100644 --- a/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr +++ b/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr @@ -9,7 +9,7 @@ LL | | LL | | if false { yield None.unwrap(); } LL | | None.unwrap() LL | | }) - | |_____^ cyclic type of infinite size + | |_____^ recursive type with infinite-size name | = note: closures cannot capture themselves or take themselves as argument; this error may be the result of a recent compiler bug-fix, @@ -34,7 +34,7 @@ LL | | LL | | if false { yield None.unwrap(); } LL | | None.unwrap() LL | | }) - | |_____^ cyclic type of infinite size + | |_____^ recursive type with infinite-size name | = note: closures cannot capture themselves or take themselves as argument; this error may be the result of a recent compiler bug-fix, diff --git a/tests/ui/typeck/cyclic_type_ice.stderr b/tests/ui/typeck/cyclic_type_ice.stderr index 645766becbf7..7b73609b2f4e 100644 --- a/tests/ui/typeck/cyclic_type_ice.stderr +++ b/tests/ui/typeck/cyclic_type_ice.stderr @@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself --> $DIR/cyclic_type_ice.rs:3:7 | LL | f(f); - | ^ cyclic type of infinite size + | ^ recursive type with infinite-size name | = note: closures cannot capture themselves or take themselves as argument; this error may be the result of a recent compiler bug-fix, diff --git a/tests/ui/unboxed-closures/unboxed-closure-no-cyclic-sig.stderr b/tests/ui/unboxed-closures/unboxed-closure-no-cyclic-sig.stderr index 563167f3c0b0..01b581cadaa4 100644 --- a/tests/ui/unboxed-closures/unboxed-closure-no-cyclic-sig.stderr +++ b/tests/ui/unboxed-closures/unboxed-closure-no-cyclic-sig.stderr @@ -2,7 +2,7 @@ error[E0644]: closure/coroutine type that references itself --> $DIR/unboxed-closure-no-cyclic-sig.rs:8:7 | LL | g(|_| { }); - | ^^^ cyclic type of infinite size + | ^^^ recursive type with infinite-size name | = note: closures cannot capture themselves or take themselves as argument; this error may be the result of a recent compiler bug-fix, From d4a8031c1fd74436079c29d7b6227f08c70b4ba2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 24 May 2026 09:09:50 +0200 Subject: [PATCH 07/26] restrict vendor intrinsics to little-endian targets --- src/tools/miri/src/shims/foreign_items.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 7cd80b85784f..bff93a7bb9d7 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::path::Path; -use rustc_abi::{Align, CanonAbi, ExternAbi, Size}; +use rustc_abi::{Align, CanonAbi, Endian, ExternAbi, Size}; use rustc_ast::expand::allocator::NO_ALLOC_SHIM_IS_UNSTABLE; use rustc_data_structures::either::Either; use rustc_hir::attrs::Linkage; @@ -817,7 +817,9 @@ fn emulate_foreign_item_inner( } // Used to implement the x86 `_mm{,256,512}_popcnt_epi{8,16,32,64}` and wasm // `{i,u}8x16_popcnt` functions. - name if name.starts_with("llvm.ctpop.v") => { + name if name.starts_with("llvm.ctpop.v") + && this.tcx.sess.target.endian == Endian::Little => + { let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let (op, op_len) = this.project_to_simd(op)?; @@ -840,14 +842,16 @@ fn emulate_foreign_item_inner( // Target-specific shims name if name.starts_with("llvm.x86.") - && matches!(this.tcx.sess.target.arch, Arch::X86 | Arch::X86_64) => + && matches!(this.tcx.sess.target.arch, Arch::X86 | Arch::X86_64) + && this.tcx.sess.target.endian == Endian::Little => { return shims::x86::EvalContextExt::emulate_x86_intrinsic( this, link_name, abi, args, dest, ); } name if name.starts_with("llvm.aarch64.") - && this.tcx.sess.target.arch == Arch::AArch64 => + && this.tcx.sess.target.arch == Arch::AArch64 + && this.tcx.sess.target.endian == Endian::Little => { return shims::aarch64::EvalContextExt::emulate_aarch64_intrinsic( this, link_name, abi, args, dest, From da3727f1935b69f610f1fb3f2cc9c00b08061399 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Tue, 14 Apr 2026 22:10:51 -0400 Subject: [PATCH 08/26] Support running no_core Linux programs Co-authored-by: Ralf Jung --- src/tools/miri/src/concurrency/thread.rs | 7 ++++-- src/tools/miri/src/eval.rs | 21 ++++++++++++------ src/tools/miri/src/helpers.rs | 5 +++++ src/tools/miri/src/machine.rs | 22 ++++++++++++++----- .../miri/src/shims/unix/foreign_items.rs | 22 +++++++++++++++++-- .../src/shims/unix/freebsd/foreign_items.rs | 10 ++++++++- src/tools/miri/tests/pass/no_core.rs | 22 +++++++++++++++++++ 7 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/tools/miri/tests/pass/no_core.rs diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 2b293a59ddb7..4dabdb48c4f6 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -962,8 +962,11 @@ fn start_regular_thread( let old_thread_id = this.machine.threads.set_active_thread_id(new_thread_id); // The child inherits its parent's cpu affinity. - if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&old_thread_id).cloned() { - this.machine.thread_cpu_affinity.insert(new_thread_id, cpuset); + // Skips this if `machine.thread_cpu_affinity` is not initialized. + if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity + && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned() + { + thread_cpu_affinity.insert(new_thread_id, cpuset); } // Perform the function pointer load in the new thread frame. diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index cf4f7d689ac2..c9b2b7634b7b 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -21,6 +21,7 @@ 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}; use crate::*; @@ -289,14 +290,20 @@ pub fn create_ecx<'tcx>( MiriMachine::new(config, layout_cx, genmc_ctx), ); - // Make sure we have MIR. We check MIR for some stable monomorphic function in libcore. - let sentinel = - helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS); - if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) { - tcx.dcx().fatal( - "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\ - Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead." + // Make sure we have MIR. We check MIR for some stable monomorphic function in libcore. However, + // if the current crate is #![no_core] it's fine to be missing the usual items from libcore. + if !is_no_core(tcx) { + let sentinel = helpers::try_resolve_path( + tcx, + &["core", "ascii", "escape_default"], + Namespace::ValueNS, ); + if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) { + tcx.dcx().fatal( + "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\ + Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead." + ); + } } // Compute argc and argv from `config.args`. diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 349cb2d66482..926dd0c24da3 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1085,6 +1085,11 @@ pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { } } +/// Check whether the local crate has the `#![no_core]` attribute. +pub fn is_no_core(tcx: TyCtxt<'_>) -> bool { + rustc_hir::find_attr!(tcx, crate, NoCore) +} + /// We don't support 16-bit systems, so let's have ergonomic conversion from `u32` to `usize`. pub trait ToUsize { fn to_usize(self) -> usize; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index d0bde4b1f052..80f392363ba0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -40,6 +40,7 @@ use crate::concurrency::{ AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory, }; +use crate::helpers::is_no_core; use crate::*; /// First real-time signal. @@ -546,7 +547,8 @@ pub struct MiriMachine<'tcx> { /// Stores which thread is eligible to run on which CPUs. /// This has no effect at all, it is just tracked to produce the correct result /// in `sched_getaffinity` - pub(crate) thread_cpu_affinity: FxHashMap, + /// This will be `None` when running `#![no_core]` crates. + pub(crate) thread_cpu_affinity: Option>, /// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri. pub(crate) layouts: PrimitiveLayouts<'tcx>, @@ -735,11 +737,19 @@ pub(crate) fn new( config.num_cpus ); let threads = ThreadManager::new(config); - let mut thread_cpu_affinity = FxHashMap::default(); - if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android) { - thread_cpu_affinity - .insert(threads.active_thread(), CpuAffinityMask::new(&layout_cx, config.num_cpus)); - } + let thread_cpu_affinity = + if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android) + && !is_no_core(tcx) + { + let mut affinity = FxHashMap::default(); + affinity.insert( + threads.active_thread(), + CpuAffinityMask::new(&layout_cx, config.num_cpus), + ); + Some(affinity) + } else { + None + }; let blocking_io = BlockingIoManager::new(config.isolated_op == IsolatedOp::Allow) .expect("Couldn't create poll instance"); let alloc_addresses = diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 467304defb9b..fa2af98b9fc8 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -1126,6 +1126,12 @@ fn emulate_foreign_item_inner( let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; + if this.machine.thread_cpu_affinity.is_none() { + throw_unsup_format!( + "`sched_getaffinity` is not supported on #![no_core] programs" + ) + } + let thread_id = if pid == 0 { this.active_thread() } else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) { @@ -1149,7 +1155,9 @@ fn emulate_foreign_item_inner( } else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 { // we only copy whole chunks of size_of::() this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?; - } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) { + } else if let Some(cpuset) = + this.machine.thread_cpu_affinity.as_ref().unwrap().get(&thread_id) + { let cpuset = cpuset.clone(); // we only copy whole chunks of size_of::() let byte_count = @@ -1171,6 +1179,12 @@ fn emulate_foreign_item_inner( let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; + if this.machine.thread_cpu_affinity.is_none() { + throw_unsup_format!( + "`sched_setaffinity` is not supported on #![no_core] programs" + ) + } + let thread_id = if pid == 0 { this.active_thread() } else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) { @@ -1199,7 +1213,11 @@ fn emulate_foreign_item_inner( std::array::from_fn(|i| bits_slice.get(i).copied().unwrap_or(0)); match CpuAffinityMask::from_array(this, this.machine.num_cpus, bits_array) { Some(cpuset) => { - this.machine.thread_cpu_affinity.insert(thread_id, cpuset); + this.machine + .thread_cpu_affinity + .as_mut() + .unwrap() + .insert(thread_id, cpuset); this.write_null(dest)?; } None => { 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 db3975a5bdfa..1cc87050e59d 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -75,6 +75,12 @@ fn emulate_foreign_item_inner( let set_size = this.read_target_usize(set_size)?; // measured in bytes let mask = this.read_pointer(mask)?; + if this.machine.thread_cpu_affinity.is_none() { + throw_unsup_format!( + "`cpuset_getaffinity` is not supported on #![no_core] programs" + ) + } + let _level_root = this.eval_libc_i32("CPU_LEVEL_ROOT"); let _level_cpuset = this.eval_libc_i32("CPU_LEVEL_CPUSET"); let level_which = this.eval_libc_i32("CPU_LEVEL_WHICH"); @@ -104,7 +110,9 @@ fn emulate_foreign_item_inner( throw_unsup_format!( "`cpuset_getaffinity` is only supported with `level` set to CPU_LEVEL_WHICH and `which` set to CPU_WHICH_PID." ); - } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&id) { + } else if let Some(cpuset) = + this.machine.thread_cpu_affinity.as_ref().unwrap().get(&id) + { // `cpusetsize` must be large enough to contain the entire CPU mask. // FreeBSD only uses `cpusetsize` to verify that it's sufficient for the kernel's CPU mask. // If it's too small, the syscall returns ERANGE. diff --git a/src/tools/miri/tests/pass/no_core.rs b/src/tools/miri/tests/pass/no_core.rs new file mode 100644 index 000000000000..0cbffcb1ae59 --- /dev/null +++ b/src/tools/miri/tests/pass/no_core.rs @@ -0,0 +1,22 @@ +//! Test that Miri is able to run no_core programs. +//! This ensures that we don't depend on any paths from core when no_core is set. + +#![no_std] +#![no_core] +#![no_main] +#![feature(rustc_attrs, no_core, lang_items, intrinsics)] +#![allow(internal_features)] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +#[no_mangle] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + 0 +} From 30b942df981c0f41cce1b818e8a88c24b50ff107 Mon Sep 17 00:00:00 2001 From: Dominik Schwaiger Date: Wed, 20 May 2026 20:31:46 +0200 Subject: [PATCH 09/26] as_mut_ptr test update description Updates the description in the `tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs` test. The referenced file no longer exists. Co-authored-by: Ralf Jung --- .../miri/tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs index 4e273a617e11..99584f27e93b 100644 --- a/src/tools/miri/tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs +++ b/src/tools/miri/tests/fail/tree_borrows/implicit_writes/as_mut_ptr.rs @@ -1,5 +1,4 @@ -// This code no longer works using implicit writes in tree borrows. -// This code tests that. The passing version is in `pass/tree_borrows/implicit_writes/as_mut_ptr.rs`. +// This code works in Tree Borrows without implicit writes, but is expected to fail with implicit writes. //@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes fn main() { From cebc29f482754f34c8bc263653392ba1573236c6 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Wed, 20 May 2026 10:46:09 +0200 Subject: [PATCH 10/26] fix: deallocate temporary buffer on failed `readv` --- src/tools/miri/src/shims/unix/fd.rs | 5 +- .../pass-dep/libc/libc-socket-no-blocking.rs | 84 ++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index dc13ced2b555..d3e2d63e2076 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -488,7 +488,10 @@ fn readv( this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?; u64::try_from(size).unwrap() }, - Err(e) => return this.set_errno_and_return_neg1(e, &dest) + Err(e) => { + this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?; + return this.set_errno_and_return_neg1(e, &dest) + } }; let mut remaining_bytes = bytes_read; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs index 4ae7436cac65..71d62c1ce2bd 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -10,8 +10,8 @@ mod libc_utils; use std::io::ErrorKind; -use std::thread; use std::time::Duration; +use std::{ptr, thread}; use libc_utils::*; @@ -50,6 +50,8 @@ fn main() { ))] test_send_recv_dontwait(); test_write_read_nonblock(); + test_readv_nonblock_err(); + test_writev_nonblock_err(); test_getsockname_ipv4_connect_nonblock(); @@ -562,6 +564,86 @@ fn test_write_read_nonblock() { server_thread.join().unwrap(); } +/// Test that Miri's internal temporary read buffer gets correctly deallocated when the +/// vectored read produces an error due to the socket read buffer being empty. +fn test_readv_nonblock_err() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + let mut buffer = [0u8; TEST_BYTES.len()]; + let (buffer1, buffer2) = buffer.split_at_mut(2); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + let err = unsafe { + errno_result(libc::readv(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); +} + +/// Test that Miri's internal temporary write buffer gets correctly deallocated when the +/// vectored write produces an error due to the socket write buffer being full. +fn test_writev_nonblock_err() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + let mut write_buffer = [1u8; 32_000]; + let (buffer1, buffer2) = write_buffer.split_at_mut(15_000); + + let iov = [ + libc::iovec { iov_base: ptr::null_mut::(), iov_len: 0 as libc::size_t }, + libc::iovec { + iov_base: buffer1.as_mut_ptr().cast::(), + iov_len: buffer1.len() as libc::size_t, + }, + libc::iovec { + iov_base: buffer2.as_mut_ptr().cast::(), + iov_len: buffer2.len() as libc::size_t, + }, + ]; + + loop { + let result = unsafe { + errno_result(libc::writev(client_sockfd, iov.as_ptr(), iov.len() as libc::c_int)) + }; + + match result { + Ok(_) => { /* continue filling buffer */ } + Err(err) if err.kind() == ErrorKind::WouldBlock => break, + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } +} + /// Test the `getsockname` syscall on a connecting IPv4 socket /// which is not connected. fn test_getsockname_ipv4_connect_nonblock() { From a799d97630d75b22e64780bb20687454baa72d60 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Tue, 26 May 2026 16:15:20 +0900 Subject: [PATCH 11/26] ci(ci): declare workflow-level contents: read per RalfJung Per @RalfJung's review at #5040, adding the same permissions block to ci.yml as the sysroots PR. Signed-off-by: Arpit Jain --- src/tools/miri/.github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index b325a121c9a6..14978c2b967b 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -12,6 +12,9 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: test: name: test (${{ matrix.host_target }}) From d9accd4d8ae784866c558a672930ad7d88244719 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 May 2026 15:01:47 +0200 Subject: [PATCH 12/26] mkstemp: do not silently do the wrong thing on non-Unix/Windows hosts --- src/tools/miri/src/shims/unix/fs.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 67d25d78474e..d221dc52a585 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1624,18 +1624,23 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let mut fopts = OpenOptions::new(); fopts.read(true).write(true).create_new(true); - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - // Do not allow others to read or modify this file. - fopts.mode(0o600); - fopts.custom_flags(libc::O_EXCL); - } - #[cfg(windows)] - { - use std::os::windows::fs::OpenOptionsExt; - // Do not allow others to read or modify this file. - fopts.share_mode(0); + cfg_select! { + unix => + { + use std::os::unix::fs::OpenOptionsExt; + // Do not allow others to read or modify this file. + fopts.mode(0o600); + fopts.custom_flags(libc::O_EXCL); + } + windows => + { + use std::os::windows::fs::OpenOptionsExt; + // Do not allow others to read or modify this file. + fopts.share_mode(0); + } + _ => { + throw_unsup_format!("`mkstemp` is not supported on this host OS"); + } } // If the generated file already exists, we will try again `max_attempts` many times. From 2914eb788bdea26c2ba4bb4d85e9dd5e21828635 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 May 2026 15:03:07 +0200 Subject: [PATCH 13/26] non-Windows path handling: make the code match the comment --- src/tools/miri/src/shims/os_str.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs index 78e7f2d418c3..7de1e7ed4081 100644 --- a/src/tools/miri/src/shims/os_str.rs +++ b/src/tools/miri/src/shims/os_str.rs @@ -346,7 +346,7 @@ fn unix_to_windows(path: &mut Vec) } Cow::Owned(OsString::from_wide(&path)) }; - #[cfg(unix)] + #[cfg(not(windows))] return if *target_os == Os::Windows { // Windows target, Unix host. let mut path: Vec = os_str.into_owned().into_encoded_bytes(); From ee8da29feb2be6a1e8a5c891840ff89bd452df46 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Wed, 13 May 2026 15:16:36 +0200 Subject: [PATCH 14/26] Add support for socket read and write timeouts --- src/tools/miri/src/clock.rs | 6 +- src/tools/miri/src/helpers.rs | 26 --- src/tools/miri/src/provenance_gc.rs | 2 +- src/tools/miri/src/shims/time.rs | 47 ++++ src/tools/miri/src/shims/unix/socket.rs | 207 ++++++++++++++---- .../miri/tests/pass-dep/libc/libc-socket.rs | 97 +++++++- src/tools/miri/tests/pass/shims/socket.rs | 56 +++++ 7 files changed, 366 insertions(+), 75 deletions(-) diff --git a/src/tools/miri/src/clock.rs b/src/tools/miri/src/clock.rs index 655697509552..25b645189ce2 100644 --- a/src/tools/miri/src/clock.rs +++ b/src/tools/miri/src/clock.rs @@ -11,12 +11,12 @@ const NANOSECONDS_PER_BASIC_BLOCK: u128 = 5000; /// An instant (a fixed moment in time) in Miri's monotone clock. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Instant { kind: InstantKind, } -#[derive(Debug)] +#[derive(Clone, Debug)] enum InstantKind { Host(StdInstant), Virtual { nanoseconds: u128 }, @@ -134,7 +134,7 @@ pub fn now(&self) -> Instant { } /// A deadline for some event to occur. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Deadline { Monotonic(Instant), RealTime(SystemTime), diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 349cb2d66482..c65ff125f61c 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1,6 +1,5 @@ use std::num::NonZero; use std::sync::Mutex; -use std::time::Duration; use std::{cmp, iter}; use rand::Rng; @@ -714,31 +713,6 @@ fn deref_pointer_and_write( this.write_scalar(value, &value_place) } - /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None` - /// if the value in the `timespec` struct is invalid. Some libc functions will return - /// `EINVAL` in this case. - fn read_timespec(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { - let this = self.eval_context_mut(); - let seconds_place = this.project_field(tp, FieldIdx::ZERO)?; - let seconds_scalar = this.read_scalar(&seconds_place)?; - let seconds = seconds_scalar.to_target_isize(this)?; - let nanoseconds_place = this.project_field(tp, FieldIdx::ONE)?; - let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?; - let nanoseconds = nanoseconds_scalar.to_target_isize(this)?; - - interp_ok(try { - // tv_sec must be non-negative. - let seconds: u64 = seconds.try_into().ok()?; - // tv_nsec must be non-negative. - let nanoseconds: u32 = nanoseconds.try_into().ok()?; - if nanoseconds >= 1_000_000_000 { - // tv_nsec must not be greater than 999,999,999. - None? - } - Duration::new(seconds, nanoseconds) - }) - } - /// Read bytes from a byte slice. fn read_byte_slice<'a>(&'a self, slice: &ImmTy<'tcx>) -> InterpResult<'tcx, &'a [u8]> where diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs index 3656a9eaa87c..c292f764d6d1 100644 --- a/src/tools/miri/src/provenance_gc.rs +++ b/src/tools/miri/src/provenance_gc.rs @@ -19,7 +19,7 @@ fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} )+ } } -no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId); +no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline); impl VisitProvenance for &'static str { fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 9dfce51d2ea4..acc0d5bc8841 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -459,4 +459,51 @@ fn Sleep(&mut self, timeout: &OpTy<'tcx>) -> InterpResult<'tcx> { ); interp_ok(()) } + + /// Parse a `timespec` struct and return it as a [`Duration`]. It returns [`None`] + /// if the value in the `timespec` struct is invalid. Some libc functions will return + /// EINVAL in this case. + fn read_timespec(&mut self, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option> { + let this = self.eval_context_mut(); + let sec_field = this.project_field_named(tp, "tv_sec")?; + let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?; + let nsec_field = this.project_field_named(tp, "tv_nsec")?; + let nsec = this.read_scalar(&nsec_field)?.to_int(nsec_field.layout.size)?; + + interp_ok(try { + // tv_sec must be non-negative. + let seconds: u64 = sec.try_into().ok()?; + // tv_nsec must be non-negative. + let nanoseconds: u32 = nsec.try_into().ok()?; + if nanoseconds >= 1_000_000_000 { + // tv_nsec must not be greater than 999,999,999. + None? + } + Duration::new(seconds, nanoseconds) + }) + } + + /// 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(); + let sec_field = this.project_field_named(tp, "tv_sec")?; + let sec = this.read_scalar(&sec_field)?.to_int(sec_field.layout.size)?; + + let usec_field = this.project_field_named(tp, "tv_usec")?; + let usec = this.read_scalar(&usec_field)?.to_int(usec_field.layout.size)?; + + interp_ok(try { + // tv_sec must be non-negative. + let seconds: u64 = sec.try_into().ok()?; + // tv_usec must be non-negative. + let microseconds: u32 = usec.try_into().ok()?; + if microseconds >= 1_000_000 { + // tv_usec must not be greater than 999,999. + None? + } + Duration::new(seconds, microseconds.strict_mul(1000)) + }) + } } diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index e7754ceedd7c..ae882f8ff3a4 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -3,6 +3,7 @@ use std::io::Read; use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4}; use std::sync::atomic::AtomicBool; +use std::time::Duration; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; @@ -67,6 +68,18 @@ struct Socket { io_readiness: RefCell, /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`. error: 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 + /// 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 FileDescription for Socket { @@ -108,14 +121,16 @@ fn read<'tcx>( assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!"); let socket = self; + let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.read_timeout.get()); ecx.ensure_connected( socket.clone(), - !socket.is_non_block.get(), + deadline.clone(), "read", callback!( @capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result>, @@ -134,8 +149,8 @@ fn read<'tcx>( finish.call(this, result) } else { // The socket is in blocking mode and thus the read call should block - // until we can read some bytes from the socket. - this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish) + // until we can read some bytes from the socket or the timeout exceeded. + this.block_for_recv(socket, deadline, ptr, len, /* should_peek */ false, finish) } } ), @@ -153,14 +168,16 @@ fn write<'tcx>( assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!"); let socket = self; + let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.write_timeout.get()); ecx.ensure_connected( socket.clone(), - !socket.is_non_block.get(), + deadline.clone(), "write", callback!( @capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result> @@ -179,8 +196,8 @@ fn write<'tcx>( return finish.call(this, result) } else { // The socket is in blocking mode and thus the write call should block - // until we can write some bytes into the socket. - this.block_for_send(socket, ptr, len, finish) + // until we can write some bytes into the socket or the timeout exceeded. + this.block_for_send(socket, deadline, ptr, len, finish) } } ), @@ -353,6 +370,8 @@ fn socket( is_non_block: Cell::new(is_sock_nonblock), io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), error: RefCell::new(None), + read_timeout: Cell::new(None), + write_timeout: Cell::new(None), }); interp_ok(Scalar::from_i32(fds.insert(fd))) @@ -567,6 +586,17 @@ fn accept4( } else { // The socket is in blocking mode and thus the accept call should block // until an incoming connection is ready. + + if socket.read_timeout.get().is_some() { + // Some Unixes like Linux also apply the SO_RCVTIMEO socket option + // to `accept` calls: + // + // This is currently not supported by Miri. + throw_unsup_format!( + "accept4: blocking accept is not supported when SO_RCVTIMEO is non-zero" + ) + } + this.block_for_accept( socket, address_ptr, @@ -645,11 +675,20 @@ fn connect( // The socket is in blocking mode and thus the connect call should block // until the connection with the server is established. - let dest = dest.clone(); + if socket.write_timeout.get().is_some() { + // Some Unixes like Linux also apply the SO_SNDTIMEO socket option + // to `connect` calls: + // + // This is currently not supported by Miri. + throw_unsup_format!( + "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero" + ) + } + let dest = dest.clone(); this.ensure_connected( socket.clone(), - /* should_wait */ true, + /* deadline */ None, "connect", callback!( @capture<'tcx> { @@ -729,29 +768,29 @@ fn send( ); } - // If either the operation or the socket is non-blocking, we don't want - // to wait until the connection is established. - let should_wait = !is_op_non_block && !socket.is_non_block.get(); + let is_non_block = is_op_non_block || socket.is_non_block.get(); + let deadline = this.action_deadline(is_non_block, socket.write_timeout.get()); let dest = dest.clone(); this.ensure_connected( socket.clone(), - should_wait, + deadline.clone(), "send", callback!( @capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, flags: i32, buffer_ptr: Pointer, length: usize, - is_op_non_block: bool, + is_non_block: bool, dest: MPlaceTy<'tcx>, } |this, result: Result<(), ()>| { if result.is_err() { return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest) } - if is_op_non_block || socket.is_non_block.get() { + if is_non_block { // We have a non-blocking operation or a non-blocking socket and // thus don't want to block until we can send. match this.try_non_block_send(&socket, buffer_ptr, length)? { @@ -760,9 +799,10 @@ fn send( } } else { // The socket is in blocking mode and thus the send call should block - // until we can send some bytes into the socket. + // until we can send some bytes into the socket or the timeout exceeded. this.block_for_send( socket, + deadline, buffer_ptr, length, callback!(@capture<'tcx> { @@ -850,29 +890,29 @@ fn recv( ); } - // If either the operation or the socket is non-blocking, we don't want - // to wait until the connection is established. - let should_wait = !is_op_non_block && !socket.is_non_block.get(); + let is_non_block = is_op_non_block || socket.is_non_block.get(); + let deadline = this.action_deadline(is_non_block, socket.read_timeout.get()); let dest = dest.clone(); this.ensure_connected( socket.clone(), - should_wait, + deadline.clone(), "recv", callback!( @capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, buffer_ptr: Pointer, length: usize, should_peek: bool, - is_op_non_block: bool, + is_non_block: bool, dest: MPlaceTy<'tcx>, } |this, result: Result<(), ()>| { if result.is_err() { return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest) } - if is_op_non_block || socket.is_non_block.get() { + if is_non_block { // We have a non-blocking operation or a non-blocking socket and // thus don't want to block until we can receive. match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? { @@ -881,9 +921,10 @@ fn recv( } } else { // The socket is in blocking mode and thus the receive call should block - // until we can receive some bytes from the socket. + // until we can receive some bytes from the socket or the timeout exceeded. this.block_for_recv( socket, + deadline, buffer_ptr, length, should_peek, @@ -930,6 +971,8 @@ fn setsockopt( }; if level == this.eval_libc_i32("SOL_SOCKET") { + let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO"); + let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO"); let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR"); if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) { @@ -950,6 +993,25 @@ fn setsockopt( } } + if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo { + let timeval_layout = this.libc_ty_layout("timeval"); + let option_value = this.ptr_to_mplace(option_value_ptr, timeval_layout); + + let timeout = match this.read_timeval(&option_value)? { + None => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")), + Some(Duration::ZERO) => None, + Some(duration) => Some(duration), + }; + + if option_name == opt_so_rcvtimeo { + socket.read_timeout.set(timeout); + } else { + socket.write_timeout.set(timeout); + } + + return interp_ok(Scalar::from_i32(0)); + } + if option_name == opt_so_reuseaddr { if option_len != 4 { // Option value should be C-int which is usually 4 bytes. @@ -1085,6 +1147,8 @@ fn getsockopt( // buffer, in which case we have to truncate. let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") { let opt_so_error = this.eval_libc_i32("SO_ERROR"); + let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO"); + let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO"); if option_name == opt_so_error { // Reading SO_ERROR should always return the latest async error. Because our stored @@ -1109,6 +1173,28 @@ fn getsockopt( let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?; this.write_int(return_value, &value_buffer)?; value_buffer + } else if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo { + let timeout = if option_name == opt_so_rcvtimeo { + socket.read_timeout.get() + } else { + socket.write_timeout.get() + } + .unwrap_or_default(); + + let secs = timeout.as_secs(); + let usecs = timeout.subsec_micros(); + + let timeval_layout = this.libc_ty_layout("timeval"); + // Allocate new buffer on the stack with the `timeval` layout. + let timeval_buffer = this.allocate(timeval_layout, MemoryKind::Stack)?; + + let sec_field = this.project_field_named(&timeval_buffer, "tv_sec")?; + this.write_int(secs, &sec_field)?; + + let usec_field = this.project_field_named(&timeval_buffer, "tv_usec")?; + this.write_int(usecs, &usec_field)?; + + timeval_buffer } else { throw_unsup_format!( "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET", @@ -1312,7 +1398,8 @@ fn getpeername( // UNIX targets should return ENOTCONN when the connection is not yet established. this.ensure_connected( socket.clone(), - /* should_wait */ false, + // Check whether the socket is connected without blocking. + Some(this.machine.monotonic_clock.now().into()), "getpeername", callback!( @capture<'tcx> { @@ -1415,6 +1502,28 @@ fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'t impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Get the deadline for an action (e.g. reading or writing). + /// When `is_non_block` is [`true`], the returned deadline is "now", i.e., + /// we wake up immediately if the action cannot be completed. + /// If `action_timeout` is `Some(duration)`, the returned deadline is in the + /// future be the specified `duration`. Otherwise, no deadline ([`None`]) is + /// returned, indicating that the action can block indefinitely. + fn action_deadline( + &self, + is_non_block: bool, + action_timeout: Option, + ) -> Option { + let this = self.eval_context_ref(); + + if is_non_block { + // Non-blocking sockets always have a zero timeout. + Some(this.machine.monotonic_clock.now().into()) + } else { + action_timeout + .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into()) + } + } + /// Block the thread until there's an incoming connection or an error occurred. /// /// This recursively calls itself should the operation still block for some reason. @@ -1433,19 +1542,23 @@ fn block_for_accept( this.block_thread_for_io( socket.clone(), BlockingIoInterest::Read, - None, + /* deadline */ None, callback!(@capture<'tcx> { + socket: FileDescriptionRef, address_ptr: Pointer, address_len_ptr: Pointer, is_client_sock_nonblock: bool, - socket: FileDescriptionRef, dest: MPlaceTy<'tcx>, } |this, kind: UnblockKind| { - assert_eq!(kind, UnblockKind::Ready); - // Remove the blocking I/O interest for unblocking this thread. this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match kind { + UnblockKind::Ready => { /* fall-through to below */ }, + // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned. + UnblockKind::TimedOut => return this.set_errno_and_return_neg1(LibcError("EWOULDBLOCK"), &dest) + } + match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? { Ok(sockfd) => { // We need to create the scalar using the destination size since @@ -1515,6 +1628,8 @@ fn try_non_block_accept( is_non_block: Cell::new(is_client_sock_nonblock), io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()), error: RefCell::new(None), + read_timeout: Cell::new(None), + write_timeout: Cell::new(None), }); // Register the socket to the blocking I/O manager because // there is an associated host socket. @@ -1533,6 +1648,7 @@ fn try_non_block_accept( fn block_for_send( &mut self, socket: FileDescriptionRef, + deadline: Option, buffer_ptr: Pointer, length: usize, finish: DynMachineCallback<'tcx, Result>, @@ -1541,22 +1657,27 @@ fn block_for_send( this.block_thread_for_io( socket.clone(), BlockingIoInterest::Write, - None, + deadline.clone(), callback!(@capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, buffer_ptr: Pointer, length: usize, finish: DynMachineCallback<'tcx, Result>, } |this, kind: UnblockKind| { - assert_eq!(kind, UnblockKind::Ready); - // Remove the blocking I/O interest for unblocking this thread. this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match kind { + UnblockKind::Ready => { /* fall-through to below */ }, + // When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned. + UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK"))) + } + match this.try_non_block_send(&socket, buffer_ptr, length)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_send(socket, buffer_ptr, length, finish) + this.block_for_send(socket, deadline, buffer_ptr, length, finish) }, result => finish.call(this, result) } @@ -1647,6 +1768,7 @@ fn try_non_block_send( fn block_for_recv( &mut self, socket: FileDescriptionRef, + deadline: Option, buffer_ptr: Pointer, length: usize, should_peek: bool, @@ -1656,23 +1778,28 @@ fn block_for_recv( this.block_thread_for_io( socket.clone(), BlockingIoInterest::Read, - None, + deadline.clone(), callback!(@capture<'tcx> { socket: FileDescriptionRef, + deadline: Option, buffer_ptr: Pointer, length: usize, should_peek: bool, finish: DynMachineCallback<'tcx, Result>, } |this, kind: UnblockKind| { - assert_eq!(kind, UnblockKind::Ready); - // Remove the blocking I/O interest for unblocking this thread. this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); + match kind { + UnblockKind::Ready => { /* fall-through to below */ }, + // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned. + UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK"))) + } + match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => { // We need to block the thread again as it would still block. - this.block_for_recv(socket, buffer_ptr, length, should_peek, finish) + this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish) }, result => finish.call(this, result) } @@ -1777,7 +1904,7 @@ fn try_non_block_recv( fn ensure_connected( &mut self, socket: FileDescriptionRef, - should_wait: bool, + deadline: Option, foreign_name: &'static str, action: DynMachineCallback<'tcx, Result<(), ()>>, ) -> InterpResult<'tcx> { @@ -1801,11 +1928,6 @@ fn ensure_connected( // We're currently connecting. Since the underlying mio socket is non-blocking, // the only way to determine whether we are done connecting is by polling. - // If we should wait until the connection is established, the timeout is `None`. - // Otherwise, we use a zero duration timeout, i.e. we return immediately - // (but we still go through the scheduler once -- which is fine). - let deadline = - if should_wait { None } else { Some(this.machine.monotonic_clock.now().into()) }; this.block_thread_for_io( socket.clone(), @@ -1814,7 +1936,6 @@ fn ensure_connected( callback!( @capture<'tcx> { socket: FileDescriptionRef, - should_wait: bool, foreign_name: &'static str, action: DynMachineCallback<'tcx, Result<(), ()>>, } |this, kind: UnblockKind| { @@ -1822,9 +1943,7 @@ fn ensure_connected( this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread()); if UnblockKind::TimedOut == kind { - // We can only time out when `should_wait` is false. // This then means that the socket is not yet connected. - assert!(!should_wait); return action.call(this, Err(())) } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 1ecfba3ff6ff..9262bfc8829a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -7,7 +7,7 @@ mod utils; use std::io::ErrorKind; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{ptr, thread}; use libc_utils::*; @@ -55,6 +55,9 @@ fn main() { test_shutdown_writable_after_read_close(); test_getsockopt_truncate(); + + test_sockopt_sndtimeo(); + test_sockopt_rcvtimeo(); } /// Test creating a socket and then closing it afterwards. @@ -759,3 +762,95 @@ fn test_getsockopt_truncate() { let long_ttl = unsafe { option_value.assume_init() }; assert_eq!(long_ttl, ttl); } + +/// Test setting and getting the SO_SNDTIMEO socket option. +/// Also test that writes don't block indefinitely when we +/// have a nonzero timeout. +fn test_sockopt_sndtimeo() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); + + let timeout = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO) + .unwrap(); + // By default, no write timeout should be set. + assert_eq!(timeout.tv_sec, 0); + assert_eq!(timeout.tv_usec, 0); + + // A 50 millisecond timeout. + let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 50_000 }; + net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO, short_timeout).unwrap(); + + let timeout = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO) + .unwrap(); + // We should now read the same value as we wrote above. + assert_eq!(timeout.tv_sec, short_timeout.tv_sec); + assert_eq!(timeout.tv_usec, short_timeout.tv_usec); + + let buffer = [1u8; 32_000]; + loop { + let before = Instant::now(); + let result = unsafe { + errno_result(libc::write(client_sockfd, buffer.as_ptr().cast(), buffer.len())) + }; + match result { + 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 => { + // The last write should return an EAGAIN/EWOULDBLOCK after ~50ms instead + // of blocking indefinitely. + assert!(Instant::now().duration_since(before) >= Duration::from_millis(50)); + break; + } + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } +} + +/// Test setting and getting the SO_RCVTIMEO socket option. +/// Also test that reads don't block indefinitely when we +/// have a nonzero timeout. +fn test_sockopt_rcvtimeo() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); + + let timeout = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO) + .unwrap(); + // By default, no read timeout should be set. + assert_eq!(timeout.tv_sec, 0); + assert_eq!(timeout.tv_usec, 0); + + // A 50 millisecond timeout. + let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 50_000 }; + net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, short_timeout).unwrap(); + + let timeout = + net::getsockopt::(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO) + .unwrap(); + // We should now read the same value as we wrote above. + assert_eq!(timeout.tv_sec, short_timeout.tv_sec); + assert_eq!(timeout.tv_usec, short_timeout.tv_usec); + + let mut buffer = [0u8; 16]; + // The read should return an EAGAIN/EWOULDBLOCK after ~10ms instead of blocking indefinitely. + let before = Instant::now(); + let err = unsafe { + errno_result(libc::read(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len())) + .unwrap_err() + }; + assert_eq!(err.kind(), ErrorKind::WouldBlock); + // Ensure that we blocked for at least 50 milliseconds. + assert!(Instant::now().duration_since(before) >= Duration::from_millis(50)) +} diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 335df02b54f8..785be17e2c0d 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -4,6 +4,7 @@ use std::io::{ErrorKind, Read, Write}; use std::net::{Shutdown, TcpListener, TcpStream}; use std::thread; +use std::time::Duration; const TEST_BYTES: &[u8] = b"these are some test bytes!"; @@ -17,6 +18,8 @@ fn main() { test_shutdown(); test_sockopt_ttl(); test_sockopt_nodelay(); + test_sockopt_read_timeout(); + test_sockopt_write_timeout(); } fn test_create_ipv4_listener() { @@ -167,3 +170,56 @@ fn test_sockopt_nodelay() { stream.set_nodelay(false).unwrap(); assert_eq!(stream.nodelay().unwrap(), false); } + +/// 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 listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + + let mut stream = TcpStream::connect(address).unwrap(); + let _other_end = listener.accept().unwrap(); + + // By default, reads on blocking sockets should block indefinitely. + assert_eq!(stream.read_timeout().unwrap(), None); + + let short_read_timeout = Some(Duration::from_millis(10)); + stream.set_read_timeout(short_read_timeout).unwrap(); + assert_eq!(stream.read_timeout().unwrap(), short_read_timeout); + + let mut buffer = [0u8; 128]; + // This should not block indefinitely and instead return EAGAIN/EWOULDBLOCK. + let err = stream.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 listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + + let mut stream = TcpStream::connect(address).unwrap(); + let _other_end = listener.accept().unwrap(); + + // By default, writes on blocking sockets should block indefinitely. + assert_eq!(stream.write_timeout().unwrap(), None); + + let short_write_timeout = Some(Duration::from_millis(10)); + stream.set_write_timeout(short_write_timeout).unwrap(); + assert_eq!(stream.write_timeout().unwrap(), short_write_timeout); + + let fill_buffer = [1u8; 1024]; + loop { + match stream.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 78a6b58a42d30bb8393b2d5773ca466c438afac5 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Tue, 26 May 2026 09:57:05 -0400 Subject: [PATCH 15/26] add red test --- tests/coverage/async_closure2.cov-map | 66 ++++++++++++++++++++++++++ tests/coverage/async_closure2.coverage | 33 +++++++++++++ tests/coverage/async_closure2.rs | 23 +++++++++ 3 files changed, 122 insertions(+) create mode 100644 tests/coverage/async_closure2.cov-map create mode 100644 tests/coverage/async_closure2.coverage create mode 100644 tests/coverage/async_closure2.rs diff --git a/tests/coverage/async_closure2.cov-map b/tests/coverage/async_closure2.cov-map new file mode 100644 index 000000000000..70e0a0f37116 --- /dev/null +++ b/tests/coverage/async_closure2.cov-map @@ -0,0 +1,66 @@ +Function name: async_closure2::call_once:: +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0c, 01, 00, 2a] +Number of files: 1 +- file 0 => $DIR/async_closure2.rs +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 12, 1) to (start + 0, 42) +Highest counter ID seen: c0 + +Function name: async_closure2::call_once::::{closure#0} +Raw bytes (21): 0x[01, 01, 01, 05, 09, 03, 01, 0c, 2b, 00, 2c, 01, 01, 05, 00, 0e, 02, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/async_closure2.rs +Number of expressions: 1 +- expression 0 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 3 +- Code(Counter(0)) at (prev + 12, 43) to (start + 0, 44) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 14) +- Code(Expression(0, Sub)) at (prev + 1, 1) to (start + 0, 2) + = (c1 - c2) +Highest counter ID seen: c0 + +Function name: async_closure2::main +Raw bytes (54): 0x[01, 01, 00, 0a, 01, 10, 01, 00, 0e, 01, 01, 09, 00, 16, 01, 04, 05, 00, 17, 01, 00, 18, 00, 21, 01, 00, 22, 00, 2f, 01, 01, 05, 00, 0f, 01, 00, 10, 00, 15, 01, 00, 16, 00, 1a, 01, 00, 1b, 00, 2b, 05, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/async_closure2.rs +Number of expressions: 0 +Number of file 0 mappings: 10 +- Code(Counter(0)) at (prev + 16, 1) to (start + 0, 14) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 22) +- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 23) +- Code(Counter(0)) at (prev + 0, 24) to (start + 0, 33) +- Code(Counter(0)) at (prev + 0, 34) to (start + 0, 47) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 15) +- Code(Counter(0)) at (prev + 0, 16) to (start + 0, 21) +- Code(Counter(0)) at (prev + 0, 22) to (start + 0, 26) +- Code(Counter(0)) at (prev + 0, 27) to (start + 0, 43) +- Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c1 + +Function name: async_closure2::main::{closure#0} +Raw bytes (14): 0x[01, 01, 00, 02, 01, 11, 22, 00, 23, 01, 03, 05, 00, 06] +Number of files: 1 +- file 0 => $DIR/async_closure2.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Counter(0)) at (prev + 17, 34) to (start + 0, 35) +- Code(Counter(0)) at (prev + 3, 5) to (start + 0, 6) +Highest counter ID seen: c0 + +Function name: async_closure2::main::{closure#0}::{closure#0}::<_> (unused) +Raw bytes (44): 0x[01, 01, 00, 08, 00, 11, 22, 00, 23, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 05, 00, 06] +Number of files: 1 +- file 0 => $DIR/async_closure2.rs +Number of expressions: 0 +Number of file 0 mappings: 8 +- Code(Zero) at (prev + 17, 34) to (start + 0, 35) +- Code(Zero) at (prev + 1, 9) to (start + 0, 14) +- Code(Zero) at (prev + 0, 15) to (start + 0, 24) +- Code(Zero) at (prev + 0, 28) to (start + 0, 44) +- Code(Zero) at (prev + 1, 9) to (start + 0, 14) +- Code(Zero) at (prev + 0, 15) to (start + 0, 24) +- Code(Zero) at (prev + 0, 28) to (start + 0, 44) +- Code(Zero) at (prev + 1, 5) to (start + 0, 6) +Highest counter ID seen: (none) + diff --git a/tests/coverage/async_closure2.coverage b/tests/coverage/async_closure2.coverage new file mode 100644 index 000000000000..9e1b63d27c84 --- /dev/null +++ b/tests/coverage/async_closure2.coverage @@ -0,0 +1,33 @@ + LL| |// Regression test for . + LL| | + LL| |//@ edition: 2021 + LL| | + LL| |//@ aux-build: executor.rs + LL| |extern crate executor; + LL| | + LL| |use std::sync::atomic::{AtomicUsize, Ordering}; + LL| | + LL| |static STEPS: AtomicUsize = AtomicUsize::new(0); + LL| | + LL| 1|async fn call_once(f: impl AsyncFnOnce()) { + LL| 1| f().await; + LL| 1|} + LL| | + LL| 1|pub fn main() { + LL| 1| let async_closure = async || { + LL| 0| STEPS.fetch_add(1, Ordering::SeqCst); + LL| 0| STEPS.fetch_add(1, Ordering::SeqCst); + LL| 1| }; + ------------------ + | Unexecuted instantiation: async_closure2::main::{closure#0}::{closure#0}::<_> + ------------------ + | async_closure2::main::{closure#0}: + | LL| 1| let async_closure = async || { + | LL| | STEPS.fetch_add(1, Ordering::SeqCst); + | LL| | STEPS.fetch_add(1, Ordering::SeqCst); + | LL| 1| }; + ------------------ + LL| 1| executor::block_on(call_once(async_closure)); + LL| 1| assert_eq!(STEPS.load(Ordering::SeqCst), 2); + LL| 1|} + diff --git a/tests/coverage/async_closure2.rs b/tests/coverage/async_closure2.rs new file mode 100644 index 000000000000..01e268d928d3 --- /dev/null +++ b/tests/coverage/async_closure2.rs @@ -0,0 +1,23 @@ +// Regression test for . + +//@ edition: 2021 + +//@ aux-build: executor.rs +extern crate executor; + +use std::sync::atomic::{AtomicUsize, Ordering}; + +static STEPS: AtomicUsize = AtomicUsize::new(0); + +async fn call_once(f: impl AsyncFnOnce()) { + f().await; +} + +pub fn main() { + let async_closure = async || { + STEPS.fetch_add(1, Ordering::SeqCst); + STEPS.fetch_add(1, Ordering::SeqCst); + }; + executor::block_on(call_once(async_closure)); + assert_eq!(STEPS.load(Ordering::SeqCst), 2); +} From cf3249c0ccc2ef99adab21cb7b31a7a005826bb8 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Tue, 26 May 2026 10:01:36 -0400 Subject: [PATCH 16/26] coverage: Use original HIR info for synthetic by-move coroutine bodies --- .../rustc_mir_transform/src/coverage/hir_info.rs | 13 ++++++++++--- tests/coverage/async_closure2.cov-map | 12 +++++++++--- tests/coverage/async_closure2.coverage | 8 ++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/hir_info.rs b/compiler/rustc_mir_transform/src/coverage/hir_info.rs index 28fdc52b06cb..85cf1970c12c 100644 --- a/compiler/rustc_mir_transform/src/coverage/hir_info.rs +++ b/compiler/rustc_mir_transform/src/coverage/hir_info.rs @@ -1,7 +1,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Span; use rustc_span::def_id::LocalDefId; @@ -24,9 +24,16 @@ pub(crate) fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> E // FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back // to HIR for it. - // HACK: For synthetic MIR bodies (async closures), use the def id of the HIR body. + // Synthetic by-move coroutine bodies don't have useful HIR of their own. + // Use the original coroutine body instead. These synthetic bodies are + // created with a coroutine type, so we can inspect that type as-is. if tcx.is_synthetic_mir(def_id) { - return extract_hir_info(tcx, tcx.local_parent(def_id)); + let effective_def_id = + match *tcx.type_of(def_id).instantiate_identity().skip_normalization().kind() { + ty::Coroutine(coroutine_def_id, _) => coroutine_def_id.expect_local(), + _ => tcx.local_parent(def_id), + }; + return extract_hir_info(tcx, effective_def_id); } let hir_node = tcx.hir_node_by_def_id(def_id); diff --git a/tests/coverage/async_closure2.cov-map b/tests/coverage/async_closure2.cov-map index 70e0a0f37116..bd58e4db5084 100644 --- a/tests/coverage/async_closure2.cov-map +++ b/tests/coverage/async_closure2.cov-map @@ -39,13 +39,19 @@ Number of file 0 mappings: 10 Highest counter ID seen: c1 Function name: async_closure2::main::{closure#0} -Raw bytes (14): 0x[01, 01, 00, 02, 01, 11, 22, 00, 23, 01, 03, 05, 00, 06] +Raw bytes (44): 0x[01, 01, 00, 08, 01, 11, 22, 00, 23, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 05, 00, 06] Number of files: 1 - file 0 => $DIR/async_closure2.rs Number of expressions: 0 -Number of file 0 mappings: 2 +Number of file 0 mappings: 8 - Code(Counter(0)) at (prev + 17, 34) to (start + 0, 35) -- Code(Counter(0)) at (prev + 3, 5) to (start + 0, 6) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14) +- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24) +- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14) +- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24) +- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 6) Highest counter ID seen: c0 Function name: async_closure2::main::{closure#0}::{closure#0}::<_> (unused) diff --git a/tests/coverage/async_closure2.coverage b/tests/coverage/async_closure2.coverage index 9e1b63d27c84..cd79b303fb66 100644 --- a/tests/coverage/async_closure2.coverage +++ b/tests/coverage/async_closure2.coverage @@ -15,16 +15,16 @@ LL| | LL| 1|pub fn main() { LL| 1| let async_closure = async || { - LL| 0| STEPS.fetch_add(1, Ordering::SeqCst); - LL| 0| STEPS.fetch_add(1, Ordering::SeqCst); + LL| 1| STEPS.fetch_add(1, Ordering::SeqCst); + LL| 1| STEPS.fetch_add(1, Ordering::SeqCst); LL| 1| }; ------------------ | Unexecuted instantiation: async_closure2::main::{closure#0}::{closure#0}::<_> ------------------ | async_closure2::main::{closure#0}: | LL| 1| let async_closure = async || { - | LL| | STEPS.fetch_add(1, Ordering::SeqCst); - | LL| | STEPS.fetch_add(1, Ordering::SeqCst); + | LL| 1| STEPS.fetch_add(1, Ordering::SeqCst); + | LL| 1| STEPS.fetch_add(1, Ordering::SeqCst); | LL| 1| }; ------------------ LL| 1| executor::block_on(call_once(async_closure)); From f46fade8990f7ef24f3f35a1a5c7411268a9091d Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 26 May 2026 14:32:05 +0000 Subject: [PATCH 17/26] Emit uwtable annotation for modules --- compiler/rustc_codegen_llvm/src/attributes.rs | 2 ++ compiler/rustc_codegen_llvm/src/context.rs | 19 +++++++++++++++++++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 12 ++++++++++++ 3 files changed, 33 insertions(+) diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index f4a15b7b4026..d7d1e4966c25 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -165,6 +165,8 @@ pub(crate) fn uwtable_attr(llcx: &llvm::Context, use_sync_unwind: Option) // NOTE: We should determine if we even need async unwind tables, as they // take have more overhead and if we can use sync unwind tables we // probably should. + // + // Similar logic exists for the per-module uwtable annotation in `context.rs`. let async_unwind = !use_sync_unwind.unwrap_or(false); llvm::CreateUWTableAttr(llcx, async_unwind) } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index a7b5c71b5128..cfc82dc5a405 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -311,6 +311,25 @@ pub(crate) unsafe fn create_module<'ll>( ); } + if sess.must_emit_unwind_tables() { + // This assertion checks that Max is the correct merge behavior. + // Async unwind tables are strictly more useful than sync uwtables. + const { + assert!((llvm::UWTableKind::None as u32) < (llvm::UWTableKind::Sync as u32)); + assert!((llvm::UWTableKind::Sync as u32) < (llvm::UWTableKind::Async as u32)); + } + + llvm::add_module_flag_u32( + llmod, + llvm::ModuleFlagMergeBehavior::Max, + "uwtable", + match sess.opts.unstable_opts.use_sync_unwind { + Some(true) => llvm::UWTableKind::Sync as u32, + Some(false) | None => llvm::UWTableKind::Async as u32, + }, + ); + } + // Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.) if sess.is_sanitizer_kcfi_enabled() { llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi", 1); diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index a240bca80955..7855584cdd73 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -240,6 +240,18 @@ pub(crate) enum DLLStorageClass { DllExport = 2, // Function to be accessible from DLL. } +/// Must match the layout of `UWTableKind`. +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) enum UWTableKind { + /// No unwind table requested + None = 0, + /// "Synchronous" unwind tables + Sync = 1, + /// "Asynchronous" unwind tables (instr precise) + Async = 2, +} + /// Must match the layout of `LLVMRustAttributeKind`. /// Semantically a subset of the C++ enum llvm::Attribute::AttrKind, /// though it is not ABI compatible (since it's a C++ enum) From 581bf3c2f935c7e9916cbe0ca0183fd6e6bcc4c7 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 26 May 2026 14:32:28 +0000 Subject: [PATCH 18/26] Add tests for uwtable annotations on modules --- tests/codegen-llvm/force-no-unwind-tables.rs | 2 ++ tests/codegen-llvm/force-unwind-tables.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/codegen-llvm/force-no-unwind-tables.rs b/tests/codegen-llvm/force-no-unwind-tables.rs index 1de5e0858e0b..a7d07df5ba6d 100644 --- a/tests/codegen-llvm/force-no-unwind-tables.rs +++ b/tests/codegen-llvm/force-no-unwind-tables.rs @@ -9,3 +9,5 @@ fn foo() { panic!(); } + +// CHECK-NOT: !"uwtable" diff --git a/tests/codegen-llvm/force-unwind-tables.rs b/tests/codegen-llvm/force-unwind-tables.rs index a2ef8a104543..b406d493e843 100644 --- a/tests/codegen-llvm/force-unwind-tables.rs +++ b/tests/codegen-llvm/force-unwind-tables.rs @@ -4,3 +4,5 @@ // CHECK: attributes #{{.*}} uwtable pub fn foo() {} + +// CHECK: !{{[0-9]+}} = !{i32 7, !"uwtable", i32 2} From e724fa66200c2b3d5051d73f0ae678b225667f98 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 May 2026 20:05:15 +0200 Subject: [PATCH 19/26] interpret/validity: properly treat zero-variant enums so that we do not have to check layout.is_uninhabited --- .../src/interpret/validity.rs | 33 ++++++++++--------- .../rustc_const_eval/src/interpret/visitor.rs | 11 ++++++- .../match_binder_checks_validity1.stderr | 2 +- .../ui/consts/const-eval/ub-uninhabit.stderr | 4 +-- .../const-eval/validate_uninhabited_zsts.rs | 2 +- .../validate_uninhabited_zsts.stderr | 2 +- tests/ui/consts/issue-64506.rs | 2 +- tests/ui/consts/issue-64506.stderr | 2 +- tests/ui/statics/uninhabited-static.rs | 4 +-- tests/ui/statics/uninhabited-static.stderr | 4 +-- 10 files changed, 38 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index bb461ccb6f37..249a65e22824 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -1353,6 +1353,16 @@ fn visit_box( interp_ok(()) } + #[inline] + fn visit_variantless(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { + let ty = val.layout.ty; + assert!(ty.is_enum(), "encountered non-enum variantless type `{ty}`"); + throw_validation_failure!( + self.path, + format!("encountered a value of zero-variant enum `{ty}`") + ); + } + #[inline] fn visit_value(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { trace!("visit_value: {:?}, {:?}", *val, val.layout); @@ -1557,23 +1567,14 @@ fn visit_value(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'t } } - // *After* all of this, check further information stored in the layout. - // On leaf types like `!` or empty enums, this will raise the error. - // This means that for types wrapping such a type, we won't ever get here, but it's - // just the simplest way to check for this case. - // - // FIXME: We could avoid some redundant checks here. For newtypes wrapping - // scalars, we do the same check on every "level" (e.g., first we check - // the fields of MyNewtype, and then we check MyNewType again). - if val.layout.is_uninhabited() { - let ty = val.layout.ty; - throw_validation_failure!( - self.path, - format!("encountered a value of uninhabited type `{ty}`") - ); - } + // Assert that we checked everything there is to check about this type. + assert!( + !val.layout.is_uninhabited(), + "a value of type `{}` passed validation but that type is uninhabited", + val.layout.ty + ); if cfg!(debug_assertions) { - // Check that we don't miss any new changes to layout computation in our checks above. + // Only run expensive checks when debug assertions are enabled. match val.layout.backend_repr { BackendRepr::Scalar(scalar_layout) => { if !scalar_layout.is_uninit_valid() { diff --git a/compiler/rustc_const_eval/src/interpret/visitor.rs b/compiler/rustc_const_eval/src/interpret/visitor.rs index 86f9156fe7c7..92d13b30c5ff 100644 --- a/compiler/rustc_const_eval/src/interpret/visitor.rs +++ b/compiler/rustc_const_eval/src/interpret/visitor.rs @@ -41,6 +41,11 @@ fn visit_union(&mut self, _v: &Self::V, _fields: NonZero) -> InterpResult fn visit_box(&mut self, _box_ty: Ty<'tcx>, _v: &Self::V) -> InterpResult<'tcx> { interp_ok(()) } + /// Visits the given type after it has been found to have no variants. + #[inline(always)] + fn visit_variantless(&mut self, _v: &Self::V) -> InterpResult<'tcx> { + interp_ok(()) + } /// Called each time we recurse down to a field of a "product-like" aggregate /// (structs, tuples, arrays and the like, but not enums), passing in old (outer) @@ -193,7 +198,11 @@ fn walk_value(&mut self, v: &Self::V) -> InterpResult<'tcx> { self.visit_variant(v, idx, &inner)?; } // For single-variant layouts, we already did everything there is to do. - Variants::Single { .. } | Variants::Empty => {} + Variants::Single { .. } => {} + // Non-variant layouts need special treatment by the visitor. + Variants::Empty => { + self.visit_variantless(v)?; + } } interp_ok(()) diff --git a/src/tools/miri/tests/fail/validity/match_binder_checks_validity1.stderr b/src/tools/miri/tests/fail/validity/match_binder_checks_validity1.stderr index c905f3a65b62..ca0c311da8cc 100644 --- a/src/tools/miri/tests/fail/validity/match_binder_checks_validity1.stderr +++ b/src/tools/miri/tests/fail/validity/match_binder_checks_validity1.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: constructing invalid value of type main::Void: encountered a value of uninhabited type `main::Void` +error: Undefined Behavior: constructing invalid value of type main::Void: encountered a value of zero-variant enum `main::Void` --> tests/fail/validity/match_binder_checks_validity1.rs:LL:CC | LL | _x => println!("hi from the void!"), diff --git a/tests/ui/consts/const-eval/ub-uninhabit.stderr b/tests/ui/consts/const-eval/ub-uninhabit.stderr index 244cb96b6923..64da16121b82 100644 --- a/tests/ui/consts/const-eval/ub-uninhabit.stderr +++ b/tests/ui/consts/const-eval/ub-uninhabit.stderr @@ -1,4 +1,4 @@ -error[E0080]: constructing invalid value of type Bar: encountered a value of uninhabited type `Bar` +error[E0080]: constructing invalid value of type Bar: encountered a value of zero-variant enum `Bar` --> $DIR/ub-uninhabit.rs:20:35 | LL | const BAD_BAD_BAD: Bar = unsafe { MaybeUninit { uninit: () }.init }; @@ -15,7 +15,7 @@ LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) }; HEX_DUMP } -error[E0080]: constructing invalid value of type [Bar; 1]: at [0], encountered a value of uninhabited type `Bar` +error[E0080]: constructing invalid value of type [Bar; 1]: at [0], encountered a value of zero-variant enum `Bar` --> $DIR/ub-uninhabit.rs:26:42 | LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { MaybeUninit { uninit: () }.init }; diff --git a/tests/ui/consts/const-eval/validate_uninhabited_zsts.rs b/tests/ui/consts/const-eval/validate_uninhabited_zsts.rs index dbb165c64d9e..9eaaa637c97c 100644 --- a/tests/ui/consts/const-eval/validate_uninhabited_zsts.rs +++ b/tests/ui/consts/const-eval/validate_uninhabited_zsts.rs @@ -18,7 +18,7 @@ enum Void {} //~^ ERROR value of the never type const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3]; -//~^ ERROR value of uninhabited type +//~^ ERROR value of zero-variant enum fn main() { FOO; diff --git a/tests/ui/consts/const-eval/validate_uninhabited_zsts.stderr b/tests/ui/consts/const-eval/validate_uninhabited_zsts.stderr index cd59e7ba55ad..5c22b8f14ad7 100644 --- a/tests/ui/consts/const-eval/validate_uninhabited_zsts.stderr +++ b/tests/ui/consts/const-eval/validate_uninhabited_zsts.stderr @@ -10,7 +10,7 @@ note: inside `foo` LL | unsafe { std::mem::transmute(()) } | ^^^^^^^^^^^^^^^^^^^^^^^ the failure occurred here -error[E0080]: constructing invalid value of type empty::Empty: at .0, encountered a value of uninhabited type `Void` +error[E0080]: constructing invalid value of type empty::Empty: at .0, encountered a value of zero-variant enum `Void` --> $DIR/validate_uninhabited_zsts.rs:20:42 | LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3]; diff --git a/tests/ui/consts/issue-64506.rs b/tests/ui/consts/issue-64506.rs index 8511c1edb302..08cb5af7d58d 100644 --- a/tests/ui/consts/issue-64506.rs +++ b/tests/ui/consts/issue-64506.rs @@ -14,7 +14,7 @@ union Foo { b: (), } let x = unsafe { Foo { b: () }.a }; - //~^ ERROR: value of uninhabited type + //~^ ERROR: value of zero-variant enum let x = &x.inner; }; diff --git a/tests/ui/consts/issue-64506.stderr b/tests/ui/consts/issue-64506.stderr index bd4f8f96a279..822c3a5c70dd 100644 --- a/tests/ui/consts/issue-64506.stderr +++ b/tests/ui/consts/issue-64506.stderr @@ -1,4 +1,4 @@ -error[E0080]: constructing invalid value of type ChildStdin: at .inner, encountered a value of uninhabited type `AnonPipe` +error[E0080]: constructing invalid value of type ChildStdin: at .inner, encountered a value of zero-variant enum `AnonPipe` --> $DIR/issue-64506.rs:16:22 | LL | let x = unsafe { Foo { b: () }.a }; diff --git a/tests/ui/statics/uninhabited-static.rs b/tests/ui/statics/uninhabited-static.rs index febbca6f0026..243a525c71ed 100644 --- a/tests/ui/statics/uninhabited-static.rs +++ b/tests/ui/statics/uninhabited-static.rs @@ -10,9 +10,9 @@ enum Void {} static VOID2: Void = unsafe { std::mem::transmute(()) }; //~ ERROR static of uninhabited type //~| WARN: previously accepted -//~| ERROR value of uninhabited type `Void` +//~| ERROR value of zero-variant enum `Void` static NEVER2: Void = unsafe { std::mem::transmute(()) }; //~ ERROR static of uninhabited type //~| WARN: previously accepted -//~| ERROR value of uninhabited type `Void` +//~| ERROR value of zero-variant enum `Void` fn main() {} diff --git a/tests/ui/statics/uninhabited-static.stderr b/tests/ui/statics/uninhabited-static.stderr index ccfb98a74f4a..a1f355064635 100644 --- a/tests/ui/statics/uninhabited-static.stderr +++ b/tests/ui/statics/uninhabited-static.stderr @@ -39,13 +39,13 @@ LL | static NEVER: !; = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #74840 -error[E0080]: constructing invalid value of type Void: encountered a value of uninhabited type `Void` +error[E0080]: constructing invalid value of type Void: encountered a value of zero-variant enum `Void` --> $DIR/uninhabited-static.rs:11:31 | LL | static VOID2: Void = unsafe { std::mem::transmute(()) }; | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `VOID2` failed here -error[E0080]: constructing invalid value of type Void: encountered a value of uninhabited type `Void` +error[E0080]: constructing invalid value of type Void: encountered a value of zero-variant enum `Void` --> $DIR/uninhabited-static.rs:14:32 | LL | static NEVER2: Void = unsafe { std::mem::transmute(()) }; From 1f02e4d1dfb3e36839e68b6ec14c96c99022caf8 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 27 May 2026 07:39:24 +0700 Subject: [PATCH 20/26] std: Fix thread::available_parallelism on Redox targets --- library/std/src/sys/thread/unix.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index 81ef39581d74..eb3fcff05ea9 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -155,6 +155,7 @@ pub fn available_parallelism() -> io::Result> { target_os = "aix", target_vendor = "apple", target_os = "cygwin", + target_os = "redox", target_os = "wasi", ) => { #[allow(unused_assignments)] @@ -316,7 +317,7 @@ pub fn available_parallelism() -> io::Result> { } } _ => { - // FIXME: implement on Redox, l4re + // FIXME: implement on l4re Err(io::const_error!(io::ErrorKind::Unsupported, "getting the number of hardware threads is not supported on the target platform")) } } From 12b3dd8a35767e0d73639667ca2d5de93a54389f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Tue, 26 May 2026 19:22:25 +0200 Subject: [PATCH 21/26] Limit the additional DLL to Windows 156229 didn't limit the additional DLL copy to Windows as it was supposed to. --- src/bootstrap/src/core/build_steps/dist.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 4bd4fb0834e2..a486cbdf4126 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -2591,8 +2591,10 @@ pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: TargetSelection // To workaround lack of rpath on Windows, we bundle another copy of // the LLVM DLL to make rust-lld and llvm-tools work when `sysroot/bin` // is missing from PATH, i.e. when they not launched by rustc. - let dst_libdir = sysroot.join("lib/rustlib").join(target).join("bin"); - maybe_install_llvm(builder, target, &dst_libdir, false); + if target.triple.contains("windows") { + let dst_libdir = sysroot.join("lib/rustlib").join(target).join("bin"); + maybe_install_llvm(builder, target, &dst_libdir, false); + } } } From 90fe8cc66f63070eeafed09ec683e95d820316c0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 27 May 2026 09:27:35 +0000 Subject: [PATCH 22/26] Adjust UWTableKind comment ref to llvm type --- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 7855584cdd73..2728152b5d20 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -240,7 +240,7 @@ pub(crate) enum DLLStorageClass { DllExport = 2, // Function to be accessible from DLL. } -/// Must match the layout of `UWTableKind`. +/// Must match the layout of `llvm::UWTableKind`. #[derive(Copy, Clone)] #[repr(C)] pub(crate) enum UWTableKind { From 59428e76e821e7320d9579f1f5c8e291d3fb7a75 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 10 May 2026 17:02:34 +0200 Subject: [PATCH 23/26] MIR inlining: allow backends to opt-in to inlining intrinsics --- compiler/rustc_codegen_cranelift/src/lib.rs | 4 +++ compiler/rustc_codegen_gcc/src/lib.rs | 6 +++- compiler/rustc_codegen_llvm/src/lib.rs | 8 +++++ .../rustc_codegen_ssa/src/traits/backend.rs | 6 ++++ compiler/rustc_interface/src/interface.rs | 1 + .../rustc_mir_transform/src/check_inline.rs | 12 +++---- compiler/rustc_mir_transform/src/inline.rs | 16 +++++++-- compiler/rustc_session/src/session.rs | 4 +++ library/core/src/any.rs | 25 +------------ library/core/src/intrinsics/mod.rs | 4 ++- .../inline/type_id_eq.call.Inline.diff | 36 +++++++++++++++++++ tests/mir-opt/inline/type_id_eq.rs | 20 +++++++++++ .../ui/consts/const_transmute_type_id2.stderr | 7 ---- .../ui/consts/const_transmute_type_id3.stderr | 7 ---- .../ui/consts/const_transmute_type_id4.stderr | 7 ---- .../ui/consts/const_transmute_type_id5.stderr | 7 ---- .../ui/consts/const_transmute_type_id6.stderr | 7 ---- 17 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 tests/mir-opt/inline/type_id_eq.call.Inline.diff create mode 100644 tests/mir-opt/inline/type_id_eq.rs diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index acfe8188e360..48858e20381d 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -237,6 +237,10 @@ fn join_codegen( ) -> (CompiledModules, FxIndexMap) { ongoing_codegen.downcast::().unwrap().join(sess, outputs) } + + fn fallback_intrinsics(&self) -> Vec { + vec![sym::type_id_eq] + } } /// Determine if the Cranelift ir verifier should run. diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 6ca2ef88ef29..8e7611ed4939 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -98,7 +98,7 @@ use rustc_middle::util::Providers; use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames}; -use rustc_span::Symbol; +use rustc_span::{Symbol, sym}; use rustc_target::spec::{Arch, RelocModel}; use tempfile::TempDir; @@ -311,6 +311,10 @@ fn join_codegen( fn target_config(&self, sess: &Session) -> TargetConfig { target_config(sess, &self.target_info) } + + fn fallback_intrinsics(&self) -> Vec { + vec![sym::type_id_eq] + } } fn new_context<'gcc, 'tcx>(tcx: TyCtxt<'tcx>) -> Context<'gcc> { diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index a697f8fe70bb..31ef5b8fdc4f 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -333,6 +333,14 @@ fn replaced_intrinsics(&self) -> Vec { will_not_use_fallback } + fn fallback_intrinsics(&self) -> Vec { + // `type_id_eq` is a safe choice since *all* backends use the fallback body for that. + // When adding more intrinsics, keep in mind that the distributed standard library + // is compiled with the LLVM backend but might later be included in a project built + // with cranelift or GCC. + vec![sym::type_id_eq] + } + fn target_cpu(&self, sess: &Session) -> String { crate::llvm_util::target_cpu(sess).to_string() } diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index cbb75836f979..26c80a0afc18 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -79,6 +79,12 @@ fn replaced_intrinsics(&self) -> Vec { vec![] } + /// Returns a list of all intrinsics that this backend definitely + /// does *not* replace, which means their fallback bodies can be MIR-inlined. + fn fallback_intrinsics(&self) -> Vec { + vec![] + } + /// Is ThinLTO supported by this backend? fn thin_lto_supported(&self) -> bool { true diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 875ed4ae5d30..95aeb1f7a234 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -449,6 +449,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se }; codegen_backend.init(&sess); sess.replaced_intrinsics = FxHashSet::from_iter(codegen_backend.replaced_intrinsics()); + sess.fallback_intrinsics = FxHashSet::from_iter(codegen_backend.fallback_intrinsics()); sess.thin_lto_supported = codegen_backend.thin_lto_supported(); let cfg = parse_cfg(sess.dcx(), config.crate_cfg); diff --git a/compiler/rustc_mir_transform/src/check_inline.rs b/compiler/rustc_mir_transform/src/check_inline.rs index 4158869f265b..50ef94f623dc 100644 --- a/compiler/rustc_mir_transform/src/check_inline.rs +++ b/compiler/rustc_mir_transform/src/check_inline.rs @@ -59,12 +59,12 @@ pub(super) fn is_inline_valid_on_fn<'tcx>( return Err("cold"); } - // Intrinsic fallback bodies are automatically made cross-crate inlineable, - // but at this stage we don't know whether codegen knows the intrinsic, - // so just conservatively don't inline it. This also ensures that we do not - // accidentally inline the body of an intrinsic that *must* be overridden. - if find_attr!(tcx, def_id, RustcIntrinsic) { - return Err("callee is an intrinsic"); + // Intrinsics without fallback body cannot be inlined. The logic for which intrinsics *with* + // body can be inlined is in the inlining pass. + if let Some(intrinsic) = tcx.intrinsic(def_id) + && intrinsic.must_be_overridden + { + return Err("callee is an intrinsic without fallback body"); } Ok(()) diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 31871c62fa7a..434f96996eea 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -565,12 +565,24 @@ fn resolve_callsite<'tcx, I: Inliner<'tcx>>( let args = tcx .try_normalize_erasing_regions(inliner.typing_env(), Unnormalized::new_wip(args)) .ok()?; - let callee = + let mut callee = Instance::try_resolve(tcx, inliner.typing_env(), def_id, args).ok().flatten()?; - if let InstanceKind::Virtual(..) | InstanceKind::Intrinsic(_) = callee.def { + if let InstanceKind::Virtual(..) = callee.def { return None; } + if let InstanceKind::Intrinsic(..) = callee.def { + let intrinsic = tcx.intrinsic(def_id).unwrap(); + if intrinsic.must_be_overridden { + return None; // intrinsic without fallback body + } + if !tcx.sess.fallback_intrinsics.contains(&intrinsic.name) { + return None; // intrinsic that the backend may want to overwrite + } + // The callee is the fallback body. + debug!("callsite is fallback body: {def_id:?}"); + callee = ty::Instance { def: ty::InstanceKind::Item(def_id), args: callee.args }; + } if inliner.history().contains(&callee.def_id()) { return None; diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 003164e8f905..c83bb62324e7 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -164,6 +164,9 @@ pub struct Session { /// The names of intrinsics that the current codegen backend replaces /// with its own implementations. pub replaced_intrinsics: FxHashSet, + /// The names of intrinsics that the current codegen backend does *not* replace + /// with its own implementations. + pub fallback_intrinsics: FxHashSet, /// Does the codegen backend support ThinLTO? pub thin_lto_supported: bool, @@ -1124,6 +1127,7 @@ pub fn build_session( target_filesearch, host_filesearch, replaced_intrinsics: FxHashSet::default(), // filled by `run_compiler` + fallback_intrinsics: FxHashSet::default(), // filled by `run_compiler` thin_lto_supported: true, // filled by `run_compiler` mir_opt_bisect_eval_count: AtomicUsize::new(0), used_features: Lock::default(), diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 62300d8b70a9..54a6408a0632 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -742,30 +742,7 @@ unsafe impl Sync for TypeId {} impl const PartialEq for TypeId { #[inline] fn eq(&self, other: &Self) -> bool { - #[cfg(miri)] - return crate::intrinsics::type_id_eq(*self, *other); - #[cfg(not(miri))] - { - let this = self; - crate::intrinsics::const_eval_select!( - @capture { this: &TypeId, other: &TypeId } -> bool: - if const { - crate::intrinsics::type_id_eq(*this, *other) - } else { - // Ideally we would just invoke `type_id_eq` unconditionally here, - // but since we do not MIR inline intrinsics, because backends - // may want to override them (and miri does!), MIR opts do not - // clean up this call sufficiently for LLVM to turn repeated calls - // of `TypeId` comparisons against one specific `TypeId` into - // a lookup table. - // SAFETY: We know that at runtime none of the bits have provenance and all bits - // are initialized. So we can just convert the whole thing to a `u128` and compare that. - unsafe { - crate::mem::transmute::<_, u128>(*this) == crate::mem::transmute::<_, u128>(*other) - } - } - ) - } + crate::intrinsics::type_id_eq(*self, *other) } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 1aeb1a0eb397..9ef9c226f3cd 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2934,7 +2934,9 @@ pub const fn type_of(_id: crate::any::TypeId) -> crate::mem::type_info::Type { #[rustc_intrinsic] #[rustc_do_not_const_check] pub const fn type_id_eq(a: crate::any::TypeId, b: crate::any::TypeId) -> bool { - a.data == b.data + // SAFETY: we know `TypeId` is 16 bytes of initialized data. + // This is runtime-only code so we do not have to worry about provenance. + unsafe { crate::mem::transmute::<_, u128>(a) == crate::mem::transmute::<_, u128>(b) } } /// Gets the size of the type represented by this `TypeId`. diff --git a/tests/mir-opt/inline/type_id_eq.call.Inline.diff b/tests/mir-opt/inline/type_id_eq.call.Inline.diff new file mode 100644 index 000000000000..85090e0a2738 --- /dev/null +++ b/tests/mir-opt/inline/type_id_eq.call.Inline.diff @@ -0,0 +1,36 @@ +- // MIR for `call` before Inline ++ // MIR for `call` after Inline + + fn call(_1: TypeId, _2: TypeId) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + let mut _3: std::any::TypeId; + let mut _4: std::any::TypeId; ++ scope 1 (inlined type_id_eq) { ++ let mut _5: u128; ++ let mut _6: u128; ++ } + + bb0: { + StorageLive(_3); + _3 = copy _1; + StorageLive(_4); + _4 = copy _2; +- _0 = type_id_eq(move _3, move _4) -> [return: bb1, unwind unreachable]; +- } +- +- bb1: { ++ StorageLive(_5); ++ _5 = copy _3 as u128 (Transmute); ++ StorageLive(_6); ++ _6 = copy _4 as u128 (Transmute); ++ _0 = Eq(move _5, move _6); ++ StorageDead(_6); ++ StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/inline/type_id_eq.rs b/tests/mir-opt/inline/type_id_eq.rs new file mode 100644 index 000000000000..72348447cdc4 --- /dev/null +++ b/tests/mir-opt/inline/type_id_eq.rs @@ -0,0 +1,20 @@ +#![feature(core_intrinsics)] +//@ test-mir-pass: Inline +//@ compile-flags: --crate-type=lib -C panic=abort + +use std::any::{Any, TypeId}; +use std::intrinsics::type_id_eq; + +struct A { + a: i32, + b: T, +} + +// EMIT_MIR type_id_eq.call.Inline.diff +// CHECK-LABEL: fn call( +pub fn call(a: TypeId, b: TypeId) -> bool { + // CHECK: as u128 (Transmute) + // CHECK: as u128 (Transmute) + // CHECK: Eq + type_id_eq(a, b) +} diff --git a/tests/ui/consts/const_transmute_type_id2.stderr b/tests/ui/consts/const_transmute_type_id2.stderr index b420deaa49ce..9c103c5c2ba6 100644 --- a/tests/ui/consts/const_transmute_type_id2.stderr +++ b/tests/ui/consts/const_transmute_type_id2.stderr @@ -5,14 +5,7 @@ LL | assert!(a == b); | ^^^^^^ evaluation of `_` failed inside this call | note: inside `::eq` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL --> $SRC_DIR/core/src/any.rs:LL:COL - ::: $SRC_DIR/core/src/any.rs:LL:COL - | - = note: in this macro invocation -note: inside `::eq::compiletime` - --> $SRC_DIR/core/src/any.rs:LL:COL - = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/consts/const_transmute_type_id3.stderr b/tests/ui/consts/const_transmute_type_id3.stderr index 9f796cda6145..679b048c1ab7 100644 --- a/tests/ui/consts/const_transmute_type_id3.stderr +++ b/tests/ui/consts/const_transmute_type_id3.stderr @@ -5,14 +5,7 @@ LL | assert!(a == b); | ^^^^^^ evaluation of `_` failed inside this call | note: inside `::eq` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL --> $SRC_DIR/core/src/any.rs:LL:COL - ::: $SRC_DIR/core/src/any.rs:LL:COL - | - = note: in this macro invocation -note: inside `::eq::compiletime` - --> $SRC_DIR/core/src/any.rs:LL:COL - = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/consts/const_transmute_type_id4.stderr b/tests/ui/consts/const_transmute_type_id4.stderr index c844b10d78cf..4060f086cbac 100644 --- a/tests/ui/consts/const_transmute_type_id4.stderr +++ b/tests/ui/consts/const_transmute_type_id4.stderr @@ -5,14 +5,7 @@ LL | assert!(a == b); | ^^^^^^ evaluation of `_` failed inside this call | note: inside `::eq` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL --> $SRC_DIR/core/src/any.rs:LL:COL - ::: $SRC_DIR/core/src/any.rs:LL:COL - | - = note: in this macro invocation -note: inside `::eq::compiletime` - --> $SRC_DIR/core/src/any.rs:LL:COL - = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/consts/const_transmute_type_id5.stderr b/tests/ui/consts/const_transmute_type_id5.stderr index 9a7384eb95b4..ac057d829562 100644 --- a/tests/ui/consts/const_transmute_type_id5.stderr +++ b/tests/ui/consts/const_transmute_type_id5.stderr @@ -5,14 +5,7 @@ LL | assert!(b == b); | ^^^^^^ evaluation of `_` failed inside this call | note: inside `::eq` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL --> $SRC_DIR/core/src/any.rs:LL:COL - ::: $SRC_DIR/core/src/any.rs:LL:COL - | - = note: in this macro invocation -note: inside `::eq::compiletime` - --> $SRC_DIR/core/src/any.rs:LL:COL - = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/consts/const_transmute_type_id6.stderr b/tests/ui/consts/const_transmute_type_id6.stderr index c0b35f3d1081..fd3ac663505f 100644 --- a/tests/ui/consts/const_transmute_type_id6.stderr +++ b/tests/ui/consts/const_transmute_type_id6.stderr @@ -5,14 +5,7 @@ LL | id == id | ^^^^^^^^ evaluation of `X` failed inside this call | note: inside `::eq` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL --> $SRC_DIR/core/src/any.rs:LL:COL - ::: $SRC_DIR/core/src/any.rs:LL:COL - | - = note: in this macro invocation -note: inside `::eq::compiletime` - --> $SRC_DIR/core/src/any.rs:LL:COL - = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `crate::intrinsics::const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error From ac2e825fb553a99b3dc2faf02516e9264b033907 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sat, 23 May 2026 16:21:20 +0200 Subject: [PATCH 24/26] fix: make socket tests work on native host --- .../libc/libc-socket-no-blocking-epoll.rs | 15 ++- .../miri/tests/pass-dep/libc/libc-socket.rs | 110 +++++++++++++----- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index 9c8c5be647d4..abfadb191913 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -294,10 +294,17 @@ fn test_send_nonblock() { if written as usize == fill_buf.len() { // When we didn't have a short write we should still be able to write more. // Ensure the socket is still writable. - assert_eq!( - current_epoll_readiness::<8>(client_sockfd, EPOLLOUT | EPOLLET), - EPOLLOUT - ); + let readiness = current_epoll_readiness::<8>(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)); + } } } Err(err) if err.kind() == ErrorKind::WouldBlock => break, diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 9262bfc8829a..b1d0a90105cc 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -8,7 +8,7 @@ use std::io::ErrorKind; use std::time::{Duration, Instant}; -use std::{ptr, thread}; +use std::{ptr, slice, thread}; use libc_utils::*; @@ -121,11 +121,26 @@ 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 a u64 of size 8 bytes we trigger an invalid length error. - let err = net::setsockopt(sockfd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1u64).unwrap_err(); + // 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.kind(), ErrorKind::InvalidInput); // Check that it is the right kind of `InvalidInput`. 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.kind(), ErrorKind::InvalidInput); + 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( @@ -174,18 +189,35 @@ 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::(), - // Add 1 to the address to make the size invalid. - (size_of::() + 1) as libc::socklen_t, + (size_of::() - 1) as libc::socklen_t, )) .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::InvalidInput); // Check that it is the right kind of `InvalidInput`. 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.kind(), ErrorKind::InvalidInput); + // Check that it is the right kind of `InvalidInput`. + assert_eq!(err.raw_os_error(), Some(libc::EINVAL)); + } } fn test_bind_ipv6() { @@ -719,48 +751,62 @@ fn test_shutdown_writable_after_read_close() { fn test_getsockopt_truncate() { let (sockfd, _) = net::make_listener_ipv4().unwrap(); - // The actual TTL with a correctly sized buffer. - let ttl = net::getsockopt::(sockfd, libc::IPPROTO_IP, libc::IP_TTL).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::::zeroed(); - // The actual length is 4 bytes. - let mut short_option_len = 2 as libc::socklen_t; + 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::IPPROTO_IP, - libc::IP_TTL, + 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, 2); - let short_ttl = unsafe { option_value.assume_init() }; + assert_eq!(short_option_len, 5); + let truncated_timeout = unsafe { option_value.assume_init() }; - // Assert that the value was silently truncated. - assert_eq!(short_ttl.to_ne_bytes()[0..2], ttl.to_ne_bytes()[0..2]); + 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 4 bytes. - let mut long_option_len = 6 as libc::socklen_t; + 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::IPPROTO_IP, - libc::IP_TTL, + 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, 4); - let long_ttl = unsafe { option_value.assume_init() }; - assert_eq!(long_ttl, ttl); + 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. @@ -781,8 +827,8 @@ fn test_sockopt_sndtimeo() { assert_eq!(timeout.tv_sec, 0); assert_eq!(timeout.tv_usec, 0); - // A 50 millisecond timeout. - let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 50_000 }; + // A 40 millisecond timeout. + let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 40_000 }; net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_SNDTIMEO, short_timeout).unwrap(); let timeout = @@ -804,9 +850,9 @@ fn test_sockopt_sndtimeo() { // it's because of the write timeout exceeding because the write buffer // is full. Err(err) if err.kind() == ErrorKind::WouldBlock => { - // The last write should return an EAGAIN/EWOULDBLOCK after ~50ms instead + // The last write should return an EAGAIN/EWOULDBLOCK after ~40ms instead // of blocking indefinitely. - assert!(Instant::now().duration_since(before) >= Duration::from_millis(50)); + assert!(Instant::now().duration_since(before) >= Duration::from_millis(40)); break; } Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), @@ -832,8 +878,8 @@ fn test_sockopt_rcvtimeo() { assert_eq!(timeout.tv_sec, 0); assert_eq!(timeout.tv_usec, 0); - // A 50 millisecond timeout. - let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 50_000 }; + // A 40 millisecond timeout. + let short_timeout = libc::timeval { tv_sec: 0, tv_usec: 40_000 }; net::setsockopt(client_sockfd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, short_timeout).unwrap(); let timeout = @@ -851,6 +897,6 @@ fn test_sockopt_rcvtimeo() { .unwrap_err() }; assert_eq!(err.kind(), ErrorKind::WouldBlock); - // Ensure that we blocked for at least 50 milliseconds. - assert!(Instant::now().duration_since(before) >= Duration::from_millis(50)) + // Ensure that we blocked for at least 40 milliseconds. + assert!(Instant::now().duration_since(before) >= Duration::from_millis(40)) } From 5d3fc4f39a9a9f64f7273a44da5d8160824b05e2 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Thu, 21 May 2026 01:06:34 +0200 Subject: [PATCH 25/26] Process host I/O events before any foreign function invocation This ensures that the readiness of host-backed file descriptions are up-to-date before executing a foreign function on it. --- src/tools/miri/src/concurrency/blocking_io.rs | 36 ++++++++++++++----- src/tools/miri/src/concurrency/thread.rs | 27 ++++---------- .../miri/src/shims/unix/foreign_items.rs | 11 ++++++ .../libc/libc-socket-no-blocking-epoll.rs | 8 ----- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/tools/miri/src/concurrency/blocking_io.rs b/src/tools/miri/src/concurrency/blocking_io.rs index c16aecedb334..6508a7fe2f4f 100644 --- a/src/tools/miri/src/concurrency/blocking_io.rs +++ b/src/tools/miri/src/concurrency/blocking_io.rs @@ -154,15 +154,9 @@ pub fn new(communicate: bool) -> Result { } /// Poll for new I/O events from the OS or wait until the timeout expired. - /// - /// - If the timeout is [`Some`] and contains [`Duration::ZERO`], the poll doesn't block and just - /// reads all events since the last poll. - /// - If the timeout is [`Some`] and contains a non-zero duration, it blocks at most for the - /// specified duration. - /// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs. - /// + /// The timeout semantics are the same as described in [`Poll::poll`]. /// The events also immediately get processed: threads get unblocked, and epoll readiness gets updated. - pub fn poll<'tcx>( + fn poll<'tcx>( ecx: &mut MiriInterpCx<'tcx>, timeout: Option, ) -> InterpResult<'tcx, Result<(), io::Error>> { @@ -344,6 +338,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { /// readiness gets set for the source even when the requested interest /// might not be fulfilled. /// + /// The callback function will immediately be executed with [`UnblockKind::Ready`] + /// when `interest` is already fulfilled for `source_fd`. + /// /// There can also be spurious wake-ups by the OS and thus it's the callers /// responsibility to verify that the requested I/O interests are /// really ready and to block again if they're not. @@ -374,4 +371,27 @@ fn block_thread_for_io( interp_ok(()) } } + + /// Poll for I/O events until either an I/O event happened or the timeout expired. + /// + /// - If the timeout is [`Some`] and contains [`Duration::ZERO`], the poll doesn't block and just + /// reads all events since the last poll. + /// - If the timeout is [`Some`] and contains a non-zero duration, it blocks at most for the + /// specified duration. + /// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs. + /// + /// Unblocks all threads which are blocked on I/O and whose I/O interests + /// are currently fulfilled. + fn poll_and_unblock(&mut self, timeout: Option) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + match BlockingIoManager::poll(this, timeout)? { + Ok(_) => interp_ok(()), + // We can ignore errors originating from interrupts; that's just a spurious wakeup. + Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()), + // For other errors we panic. On Linux and BSD hosts this should only be + // reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred. + Err(e) => panic!("unexpected error while polling: {e}"), + } + } } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 4dabdb48c4f6..b730b9daa99a 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -1,9 +1,9 @@ //! Implements threads. +use std::mem; use std::sync::atomic::Ordering::Relaxed; use std::task::Poll; use std::time::{Duration, SystemTime}; -use std::{io, mem}; use rand::RngExt; use rand::seq::IteratorRandom; @@ -698,8 +698,11 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { // or timeouts to take care of. if this.machine.communicate() { - // When isolation is disabled we need to check for events for - // threads which are blocked on host I/O. + // When isolation is disabled we need to check for events for threads + // which are blocked on host I/O. Unlike the `poll_and_unblock` before + // any foreign item, the call here is needed to ensure that threads which + // are blocked on host I/O are woken up even if no shimmed functions are + // executed afterwards. // We do this before running any other threads such that the threads // which received events are available for scheduling afterwards. @@ -793,24 +796,6 @@ fn is_thread_blocked_on_host(&self, thread: &Thread<'tcx>) -> bool { } } - /// Poll for I/O events until either an I/O event happened or the timeout expired. - /// The different timeout values are described in [`BlockingIoManager::poll`]. - /// - /// Unblocks all threads which are blocked on I/O and whose I/O interests - /// are currently fulfilled. - fn poll_and_unblock(&mut self, timeout: Option) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - - match BlockingIoManager::poll(this, timeout)? { - Ok(_) => interp_ok(()), - // We can ignore errors originating from interrupts; that's just a spurious wakeup. - Err(e) if e.kind() == io::ErrorKind::Interrupted => interp_ok(()), - // For other errors we panic. On Linux and BSD hosts this should only be - // reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred. - Err(e) => panic!("unexpected error while polling: {e}"), - } - } - /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks. /// /// This method returns the minimum duration until the next thread deadline. diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index fa2af98b9fc8..58f05b644f58 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -1,5 +1,6 @@ use std::ffi::OsStr; use std::str; +use std::time::Duration; use rustc_abi::{CanonAbi, Size}; use rustc_middle::ty::Ty; @@ -108,6 +109,16 @@ fn emulate_foreign_item_inner( ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); + if this.machine.communicate() { + // When isolation is disabled we need to check for new host I/O events before + // running any shimmed function. This is needed to ensure that the shim we + // execute has up-to-date information about host readiness (as reflected + // e.g. by epoll) even if the current thread never yields. + + // Perform a non-blocking poll for newly available I/O events from the OS. + this.poll_and_unblock(Some(Duration::ZERO))?; + } + // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. match link_name.as_str() { // Environment related shims diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index 9c8c5be647d4..7202384a8cf6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -523,11 +523,6 @@ fn test_readiness_after_short_peek() { // Write some bytes into the peer socket. libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); - // FIXME: Changes in host I/O readiness are only processed when entering the scheduler. - // Ensure that we process the effects if the `write_all` by yielding the current (only) thread. - // - thread::yield_now(); - // `buffer` is intentionally bigger than `TEST_BYTES.len()` to trigger a short peek. let mut buffer = [0; 128]; let bytes_read = unsafe { @@ -541,9 +536,6 @@ fn test_readiness_after_short_peek() { } as usize; assert_eq!(bytes_read, TEST_BYTES.len()); - // FIXME(#5047): same as above. - thread::yield_now(); - // Ensure that the readable readiness is still set. assert_eq!(current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET), EPOLLIN); From 56f5c388406c51ae1681323b1cc00b0291f90b15 Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Mon, 25 May 2026 19:08:39 +0200 Subject: [PATCH 26/26] Fix const-eval of shared generic reborrows --- .../rustc_const_eval/src/interpret/step.rs | 12 +++-- .../rustc_mir_transform/src/promote_consts.rs | 6 +-- .../miri/tests/pass/reborrow-coerce-shared.rs | 48 +++++++++++++++++++ tests/ui/reborrow/coerce_shared_consteval.rs | 28 +++++++++++ .../reborrow/reborrow-promotion-rejected.rs | 21 ++++++++ .../reborrow-promotion-rejected.stderr | 13 +++++ 6 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/tools/miri/tests/pass/reborrow-coerce-shared.rs create mode 100644 tests/ui/reborrow/coerce_shared_consteval.rs create mode 100644 tests/ui/reborrow/reborrow-promotion-rejected.rs create mode 100644 tests/ui/reborrow/reborrow-promotion-rejected.stderr diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index c60fb92a7a20..0718638c1d21 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -230,9 +230,15 @@ pub fn eval_rvalue_into_place( })?; } - Reborrow(_, _, place) => { - let op = self.eval_place_to_op(place, Some(dest.layout))?; - self.copy_op(&op, &dest)?; + Reborrow(_, mutability, place) => { + let op = self.eval_place_to_op(place, None)?; + if mutability.is_not() { + // Shared generic reborrows use `CoerceShared`: a bitwise copy into a + // distinct same-layout target ADT. + self.copy_op_allow_transmute(&op, &dest)?; + } else { + self.copy_op(&op, &dest)?; + } } RawPtr(kind, place) => { diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index f3e86c4eef75..3694a0614a7b 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -580,11 +580,7 @@ fn validate_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> self.validate_ref(*kind, place)?; } - Rvalue::Reborrow(_, _, place) => { - // FIXME(reborrow): should probably have a place_simplified like above. - let op = &Operand::Copy(*place); - self.validate_operand(op)? - } + Rvalue::Reborrow(..) => return Err(Unpromotable), Rvalue::Aggregate(_, operands) => { for o in operands { diff --git a/src/tools/miri/tests/pass/reborrow-coerce-shared.rs b/src/tools/miri/tests/pass/reborrow-coerce-shared.rs new file mode 100644 index 000000000000..bb9918055bfc --- /dev/null +++ b/src/tools/miri/tests/pass/reborrow-coerce-shared.rs @@ -0,0 +1,48 @@ +// Regression test for the Miri reproducer in rust-lang/rust#156313. +// +// The issue's exact recursive example ICEd while evaluating the argument +// conversion before the recursion mattered. This keeps that same +// `CustomMut`-to-`CustomRef` call path, but terminates after one recursive +// call so it can be a pass test once the ICE is fixed. + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +#[allow(unused)] +struct CustomMut<'a, T>(&'a mut T); +impl<'a, T> Reborrow for CustomMut<'a, T> {} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +struct CustomRef<'a, T>(&'a T); + +impl<'a, T> Clone for CustomRef<'a, T> { + fn clone(&self) -> Self { + Self(self.0) + } +} +impl<'a, T> Copy for CustomRef<'a, T> {} + +fn method(_a: CustomRef<'_, ()>) {} + +fn recursive_method(_a: CustomRef<'_, ()>, recurse: bool) { + if recurse { + let a = CustomMut(&mut ()); + recursive_method(a, false); + } +} + +fn issue_156313_runtime_reproducer() { + let a = CustomMut(&mut ()); + method(a); +} + +fn issue_156313_recursive_call_reproducer() { + let a = CustomMut(&mut ()); + recursive_method(a, true); +} + +fn main() { + issue_156313_runtime_reproducer(); + issue_156313_recursive_call_reproducer(); +} diff --git a/tests/ui/reborrow/coerce_shared_consteval.rs b/tests/ui/reborrow/coerce_shared_consteval.rs new file mode 100644 index 000000000000..29b44b643863 --- /dev/null +++ b/tests/ui/reborrow/coerce_shared_consteval.rs @@ -0,0 +1,28 @@ +//@ run-pass +// Regression test for the const-eval reproducer in rust-lang/rust#156313. + +#![feature(reborrow)] +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::marker::{CoerceShared, Reborrow}; + +pub struct MyMut<'a>(&'a u8); + +impl Reborrow for MyMut<'_> {} + +#[derive(Clone, Copy)] +pub struct MyRef<'a>(&'a u8); + +impl<'a> CoerceShared> for MyMut<'a> {} + +const fn consteval_reproducer() { + let value = 1; + foo(MyMut(&value)); +} + +const fn foo(x: MyRef<'_>) {} + +fn main() { + const { consteval_reproducer(); } +} diff --git a/tests/ui/reborrow/reborrow-promotion-rejected.rs b/tests/ui/reborrow/reborrow-promotion-rejected.rs new file mode 100644 index 000000000000..38366cd9ac2a --- /dev/null +++ b/tests/ui/reborrow/reborrow-promotion-rejected.rs @@ -0,0 +1,21 @@ +//@ check-fail + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct MyMut<'a>(&'a u8); +impl Reborrow for MyMut<'_> {} + +#[derive(Clone, Copy)] +struct MyRef<'a>(&'a u8); +impl<'a> CoerceShared> for MyMut<'a> {} + +const fn coerce(x: MyRef<'_>) -> MyRef<'_> { + x +} + +static BAD: &'static MyRef<'static> = &coerce(MyMut(&1)); +//~^ ERROR temporary value dropped while borrowed + +fn main() {} diff --git a/tests/ui/reborrow/reborrow-promotion-rejected.stderr b/tests/ui/reborrow/reborrow-promotion-rejected.stderr new file mode 100644 index 000000000000..f7e1560f0208 --- /dev/null +++ b/tests/ui/reborrow/reborrow-promotion-rejected.stderr @@ -0,0 +1,13 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/reborrow-promotion-rejected.rs:18:47 + | +LL | static BAD: &'static MyRef<'static> = &coerce(MyMut(&1)); + | --------^^^^^^^^^- + | | | | + | | | temporary value is freed at the end of this statement + | | creates a temporary value which is freed while still in use + | using this value as a static requires that borrow lasts for `'static` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0716`.