From 98f8ed15fead3cf0c506f969a1141fef331071f6 Mon Sep 17 00:00:00 2001 From: Tobias Decking Date: Sun, 9 Aug 2026 19:59:13 +0200 Subject: [PATCH 01/19] Rework `div_ceil` for nonzero integers. --- library/core/src/num/nonzero.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index e747ac591a3e2..d2acaf837c761 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1561,7 +1561,10 @@ macro_rules! nonzero_integer_signedness_dependent_impls { without modifying the original"] #[inline] pub const fn div_ceil(self, rhs: Self) -> Self { - let v = self.get().div_ceil(rhs.get()); + // An implementation of the function without calculating the remainder. + // It is better than the implementation for normal integers, but it can only + // be used here because of the possibility to subtract by one without overflow. + let v = (self.get() - 1) / rhs.get() + 1; // SAFETY: ceiled division of two positive integers can never be zero. unsafe { Self::new_unchecked(v) } } From c83b0a459335fe196088557fa0b02872ca7a3240 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 16 Aug 2026 19:06:14 -0400 Subject: [PATCH 02/19] std: Add a test for operations that depend on overflow checks I wasn't able to find a test that verifies different behavior based on whether or not overflow checks are available, so add one here. --- tests/ui/std/overflow-check-ops.rs | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/ui/std/overflow-check-ops.rs diff --git a/tests/ui/std/overflow-check-ops.rs b/tests/ui/std/overflow-check-ops.rs new file mode 100644 index 0000000000000..9f79bb6505753 --- /dev/null +++ b/tests/ui/std/overflow-check-ops.rs @@ -0,0 +1,42 @@ +//! Verify the behavior differences between enabling and disabling overflow checks. + +//@ run-pass +//@ needs-unwind +//@ revisions: ERROR WRAP +//@[ERROR] compile-flags: -C overflow-checks=true +//@[WRAP] compile-flags: -C overflow-checks=false + +#![feature(cfg_overflow_checks)] + +use std::hint::black_box as bb; +use std::{assert_matches, fmt, panic}; + +#[track_caller] +fn check(func: fn() -> T, wrapping_res: T, name: &str) { + let type_name = std::any::type_name::(); + let res = panic::catch_unwind(func); + if cfg!(overflow_checks) { + assert_matches!(res, Err(_), "{type_name} {name}"); + } else { + assert_eq!(res.unwrap(), wrapping_res, "{type_name} {name}"); + } +} + +fn main() { + check(|| bb(u32::MAX) + bb(1), 0, "add"); + check(|| bb(0u32) - bb(1), u32::MAX, "sub"); + check(|| bb(u32::MAX) * bb(2), u32::MAX << 1, "mul"); + check(|| bb(1u32) << bb(32), 1, "shl"); + check(|| bb(u32::MAX) >> bb(32), u32::MAX, "shr"); + check(|| bb(u32::MAX).pow(bb(2)), 1, "pow"); + check(|| bb(u32::MAX).next_power_of_two(), 0, "next_power_of_two"); + + check(|| bb(i32::MAX) + bb(1), i32::MIN, "add"); + check(|| bb(i32::MIN) - bb(1), i32::MAX, "sub"); + check(|| bb(i32::MAX) * bb(2), i32::MAX << 1, "mul"); + check(|| -bb(i32::MIN), i32::MIN, "neg"); + check(|| bb(i32::MIN).abs(), i32::MIN, "abs"); + check(|| bb(1) << bb(32), 1, "shl"); + check(|| bb(i32::MAX) >> bb(32), i32::MAX, "shr"); + check(|| bb(i32::MAX).pow(bb(2)), 1, "pow"); +} From 8550ca5e56084b56c564cbb3147b5d13f6d1bb19 Mon Sep 17 00:00:00 2001 From: "U. Lasiotus" Date: Sun, 2 Aug 2026 16:50:19 +0000 Subject: [PATCH 03/19] library: motor: bump moto-rt ABI ver to 17 and implement several missing methods Newly implemented/adjusted: - vectored read/write - file locking - native paths (bin/tmp) - command output - process pid --- library/Cargo.lock | 4 +- library/std/Cargo.toml | 2 +- library/std/src/process/tests.rs | 12 ++- library/std/src/sys/fd/motor.rs | 11 +-- library/std/src/sys/fs/motor.rs | 36 ++++++--- library/std/src/sys/io/error/motor.rs | 1 + library/std/src/sys/pal/motor/mod.rs | 24 ++++++ library/std/src/sys/paths/mod.rs | 6 +- library/std/src/sys/paths/motor.rs | 50 +++++++++++- library/std/src/sys/process/mod.rs | 6 +- library/std/src/sys/process/motor.rs | 111 ++++++++++++++++---------- library/std/src/sys/stdio/motor.rs | 27 ++++++- 12 files changed, 215 insertions(+), 75 deletions(-) diff --git a/library/Cargo.lock b/library/Cargo.lock index c57f939fe10a1..4e2241382915f 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "moto-rt" -version = "0.16.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcd8eb21b606833bc3a8c1c343bc75eb1999b288b2c2d515ae872e9e2f153187" +checksum = "9f3c01b588c6d37e3f4f065712c4f33e12fad17ad4152a70e8346991e2bbe92e" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index f216d8a1d2874..770563212b7ec 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -71,7 +71,7 @@ fortanix-sgx-abi = { version = "0.6.1", features = [ ], public = true } [target.'cfg(target_os = "motor")'.dependencies] -moto-rt = { version = "0.16", features = ['rustc-dep-of-std'], public = true } +moto-rt = { version = "0.17", features = ['rustc-dep-of-std'], public = true } [target.'cfg(target_os = "hermit")'.dependencies] hermit-abi = { version = "0.5.0", features = [ diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 9fe14b2e468a5..35ce30f1146e4 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -16,14 +16,12 @@ fn known_command() -> Command { } } -#[cfg(target_os = "android")] -fn shell_cmd() -> Command { - Command::new("/system/bin/sh") -} - -#[cfg(not(target_os = "android"))] fn shell_cmd() -> Command { - Command::new("/bin/sh") + if cfg!(target_os = "android") || cfg!(target_os = "motor") { + Command::new("/system/bin/sh") + } else { + Command::new("/bin/sh") + } } #[test] diff --git a/library/std/src/sys/fd/motor.rs b/library/std/src/sys/fd/motor.rs index b81072bcb50c7..e41f176ca982e 100644 --- a/library/std/src/sys/fd/motor.rs +++ b/library/std/src/sys/fd/motor.rs @@ -2,7 +2,7 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error}; +use crate::sys::{AsInner, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error}; #[derive(Debug)] pub struct FileDesc(OwnedFd); @@ -17,7 +17,8 @@ impl FileDesc { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - io::default_read_vectored(|b| self.read(b), bufs) + moto_rt::fs::read_vectored(self.as_raw_fd(), &mut io_slices_mut(bufs)) + .map_err(map_motor_error) } pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { @@ -30,16 +31,16 @@ impl FileDesc { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - crate::io::default_write_vectored(|b| self.write(b), bufs) + moto_rt::fs::write_vectored(self.as_raw_fd(), &io_slices(bufs)).map_err(map_motor_error) } pub fn is_write_vectored(&self) -> bool { - false + true } #[inline] pub fn is_read_vectored(&self) -> bool { - false + true } pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index a76f64a47c24a..938d1537790a1 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -6,7 +6,21 @@ use crate::path::{Path, PathBuf}; use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::{Dir, exists}; use crate::sys::time::SystemTime; -use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, map_motor_error, unsupported}; +use crate::sys::{ + AsInner, AsInnerMut, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error, + unsupported, +}; + +fn try_lock(fd: RawFd, operation: u8) -> Result<(), crate::fs::TryLockError> { + moto_rt::fs::file_lock(fd, operation).map_err(|err| { + let err = map_motor_error(err); + if err.kind() == io::ErrorKind::WouldBlock { + crate::fs::TryLockError::WouldBlock + } else { + crate::fs::TryLockError::Error(err) + } + }) +} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct FileType { @@ -196,11 +210,12 @@ impl File { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - crate::io::default_read_vectored(|b| self.read(b), bufs) + moto_rt::fs::read_vectored(self.as_raw_fd(), &mut io_slices_mut(bufs)) + .map_err(map_motor_error) } pub fn is_read_vectored(&self) -> bool { - false + true } pub fn read_buf(&self, cursor: BorrowedCursor<'_, u8>) -> io::Result<()> { @@ -212,11 +227,11 @@ impl File { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - crate::io::default_write_vectored(|b| self.write(b), bufs) + moto_rt::fs::write_vectored(self.as_raw_fd(), &io_slices(bufs)).map_err(map_motor_error) } pub fn is_write_vectored(&self) -> bool { - false + true } pub fn flush(&self) -> io::Result<()> { @@ -259,23 +274,24 @@ impl File { } pub fn lock(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::LOCK_EXCLUSIVE) + .map_err(map_motor_error) } pub fn lock_shared(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::LOCK_SHARED).map_err(map_motor_error) } pub fn try_lock(&self) -> Result<(), crate::fs::TryLockError> { - Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + try_lock(self.as_raw_fd(), moto_rt::fs::TRY_LOCK_EXCLUSIVE) } pub fn try_lock_shared(&self) -> Result<(), crate::fs::TryLockError> { - Err(crate::fs::TryLockError::Error(io::Error::from(io::ErrorKind::Unsupported))) + try_lock(self.as_raw_fd(), moto_rt::fs::TRY_LOCK_SHARED) } pub fn unlock(&self) -> io::Result<()> { - unsupported() + moto_rt::fs::file_lock(self.as_raw_fd(), moto_rt::fs::UNLOCK).map_err(map_motor_error) } pub fn size(&self) -> Option> { diff --git a/library/std/src/sys/io/error/motor.rs b/library/std/src/sys/io/error/motor.rs index 3c22d5fcb7b06..06417417e8554 100644 --- a/library/std/src/sys/io/error/motor.rs +++ b/library/std/src/sys/io/error/motor.rs @@ -50,6 +50,7 @@ pub fn decode_error_kind(code: io::RawOsError) -> io::ErrorKind { moto_rt::Error::BadHandle => io::ErrorKind::InvalidInput, moto_rt::Error::FileTooLarge => io::ErrorKind::FileTooLarge, moto_rt::Error::NotConnected => io::ErrorKind::NotConnected, + moto_rt::Error::ConnectionReset => io::ErrorKind::ConnectionReset, moto_rt::Error::StorageFull => io::ErrorKind::StorageFull, moto_rt::Error::InvalidData => io::ErrorKind::InvalidData, _ => io::ErrorKind::Uncategorized, diff --git a/library/std/src/sys/pal/motor/mod.rs b/library/std/src/sys/pal/motor/mod.rs index 5bf217db9013a..6ff8a4798fbdb 100644 --- a/library/std/src/sys/pal/motor/mod.rs +++ b/library/std/src/sys/pal/motor/mod.rs @@ -1,14 +1,38 @@ #![allow(unsafe_op_in_unsafe_fn)] use crate::io; +use crate::vec::Vec; pub(crate) fn map_motor_error(err: moto_rt::Error) -> io::Error { let error_code: moto_rt::ErrorCode = err.into(); io::Error::from_raw_os_error(error_code.into()) } +/// The buffer list `moto_rt::fs::write_vectored` takes. +/// +/// Unix hands `writev` an `[IoSlice]` directly, but only because its `IoSlice` +/// is documented to be ABI-compatible with `iovec`. Motor uses the generic +/// representation, whose layout is deliberately unspecified, so the list is +/// rebuilt through the public `Deref` rather than reinterpreted. That costs one +/// small allocation against a filesystem or IPC round trip, and needs no +/// `unsafe` and no assumption about a type shared with every other target. +pub(crate) fn io_slices<'a>(bufs: &'a [io::IoSlice<'_>]) -> Vec<&'a [u8]> { + bufs.iter().map(|buf| &**buf).collect() +} + +/// The buffer list `moto_rt::fs::read_vectored` takes. See [`io_slices`]. +pub(crate) fn io_slices_mut<'a>(bufs: &'a mut [io::IoSliceMut<'_>]) -> Vec<&'a mut [u8]> { + bufs.iter_mut().map(|buf| &mut **buf).collect() +} + +// Weak: when a program is linked with mlibc (e.g. via the Motor clang +// driver, which always links mlibc's crt1.o), crt1.o's strong motor_start +// must win. mlibc's entry initializes the VDSO vtable and the C runtime +// (TCB, stdio, .init_array constructors) and then calls the C `main` +// that rustc generates, so Rust std works identically in both flows. #[cfg(not(test))] #[unsafe(no_mangle)] +#[linkage = "weak"] pub extern "C" fn motor_start() -> ! { // Initialize the runtime. moto_rt::start(); diff --git a/library/std/src/sys/paths/mod.rs b/library/std/src/sys/paths/mod.rs index 57f894249ae74..69c814ef06c06 100644 --- a/library/std/src/sys/paths/mod.rs +++ b/library/std/src/sys/paths/mod.rs @@ -15,9 +15,9 @@ cfg_select! { #[expect(dead_code)] mod unsupported; mod imp { - pub use super::motor::{chdir, current_exe, getcwd, temp_dir}; - pub use super::unsupported::{ - JoinPathsError, SplitPaths, home_dir, join_paths, split_paths, + pub use super::motor::{ + JoinPathsError, SplitPaths, chdir, current_exe, getcwd, home_dir, join_paths, + split_paths, temp_dir, }; } } diff --git a/library/std/src/sys/paths/motor.rs b/library/std/src/sys/paths/motor.rs index 33d746b13d592..82f8a35e50db6 100644 --- a/library/std/src/sys/paths/motor.rs +++ b/library/std/src/sys/paths/motor.rs @@ -1,7 +1,49 @@ -use crate::io; +use crate::ffi::{OsStr, OsString}; use crate::os::motor::ffi::OsStrExt; use crate::path::{self, PathBuf}; use crate::sys::pal::map_motor_error; +use crate::{fmt, io, iter, str}; + +const PATH_SEPARATOR: char = ':'; + +pub type SplitPaths<'a> = iter::Map, fn(&str) -> PathBuf>; + +pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> { + fn into_pathbuf(part: &str) -> PathBuf { + PathBuf::from(part) + } + unparsed.as_str().split(PATH_SEPARATOR).map(into_pathbuf as fn(&str) -> PathBuf) +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + let mut joined = String::new(); + for (i, path) in paths.enumerate() { + let path = path.as_ref().as_str(); + if i > 0 { + joined.push(PATH_SEPARATOR); + } + if path.contains(PATH_SEPARATOR) { + return Err(JoinPathsError); + } + joined.push_str(path); + } + Ok(OsString::from(joined)) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "path segment contains separator `{PATH_SEPARATOR}`") + } +} + +impl crate::error::Error for JoinPathsError {} pub fn getcwd() -> io::Result { moto_rt::fs::getcwd().map(PathBuf::from).map_err(map_motor_error) @@ -11,10 +53,14 @@ pub fn chdir(path: &path::Path) -> io::Result<()> { moto_rt::fs::chdir(path.as_os_str().as_str()).map_err(map_motor_error) } +pub fn home_dir() -> Option { + Some(PathBuf::from("/user")) +} + pub fn current_exe() -> io::Result { moto_rt::process::current_exe().map(PathBuf::from).map_err(map_motor_error) } pub fn temp_dir() -> PathBuf { - PathBuf::from(moto_rt::fs::TEMP_DIR) + crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| PathBuf::from("/user/tmp")) } diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index f46870e0c4042..e0d52a6c5c295 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -49,8 +49,7 @@ pub use imp::{ target_os = "l4re" )) ), - target_os = "windows", - target_os = "motor" + target_os = "windows" ))] pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec)> { let (mut process, mut pipes) = cmd.spawn(Stdio::MakePipe, false)?; @@ -88,7 +87,6 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< target_os = "l4re" )) ), - target_os = "windows", - target_os = "motor" + target_os = "windows" )))] pub use imp::output; diff --git a/library/std/src/sys/process/motor.rs b/library/std/src/sys/process/motor.rs index 080da9be3af92..5cedbf9792b3d 100644 --- a/library/std/src/sys/process/motor.rs +++ b/library/std/src/sys/process/motor.rs @@ -3,39 +3,36 @@ use super::env::{CommandEnv, CommandResolvedEnvs}; use crate::ffi::OsStr; pub use crate::ffi::OsString as EnvKey; use crate::num::NonZeroI32; -use crate::os::fd::{FromRawFd, IntoRawFd}; +use crate::os::fd::{AsRawFd, FromRawFd}; use crate::os::motor::ffi::OsStrExt; use crate::path::Path; use crate::process::StdioPipes; use crate::sys::fs::File; -use crate::sys::{AsInner, FromInner, map_motor_error}; +use crate::sys::{IntoInner, map_motor_error}; use crate::{fmt, io}; pub enum Stdio { Inherit, Null, MakePipe, + // There is no public `From` conversion yet. + #[expect(dead_code)] + ParentStdin, + ParentStdout, + ParentStderr, Fd(crate::sys::fd::FileDesc), } impl Stdio { - fn into_rt(self) -> moto_rt::RtFd { + fn into_rt(&self) -> moto_rt::RtFd { match self { Stdio::Inherit => moto_rt::process::STDIO_INHERIT, Stdio::Null => moto_rt::process::STDIO_NULL, Stdio::MakePipe => moto_rt::process::STDIO_MAKE_PIPE, - Stdio::Fd(fd) => fd.into_raw_fd(), - } - } - - fn try_clone(&self) -> io::Result { - match self { - Self::Fd(fd) => { - Ok(Self::Fd(crate::sys::fd::FileDesc::from_inner(fd.as_inner().try_clone()?))) - } - Self::Inherit => Ok(Self::Inherit), - Self::Null => Ok(Self::Null), - Self::MakePipe => Ok(Self::MakePipe), + Stdio::ParentStdin => moto_rt::process::STDIO_PARENT_STDIN, + Stdio::ParentStdout => moto_rt::process::STDIO_PARENT_STDOUT, + Stdio::ParentStderr => moto_rt::process::STDIO_PARENT_STDERR, + Stdio::Fd(fd) => fd.as_raw_fd(), } } } @@ -53,10 +50,7 @@ pub struct Command { impl Command { pub fn new(program: &OsStr) -> Command { - let mut env = CommandEnv::default(); - env.remove(OsStr::new(moto_rt::process::STDIO_IS_TERMINAL_ENV_KEY)); - - Command { program: program.as_str().to_owned(), env, ..Default::default() } + Command { program: program.as_str().to_owned(), ..Default::default() } } pub fn arg(&mut self, arg: &OsStr) { @@ -114,21 +108,21 @@ impl Command { needs_stdin: bool, ) -> io::Result<(Process, StdioPipes)> { let stdin = if let Some(stdin) = self.stdin.as_ref() { - stdin.try_clone()?.into_rt() + stdin.into_rt() } else if needs_stdin { - default.try_clone()?.into_rt() + default.into_rt() } else { Stdio::Null.into_rt() }; let stdout = if let Some(stdout) = self.stdout.as_ref() { - stdout.try_clone()?.into_rt() + stdout.into_rt() } else { - default.try_clone()?.into_rt() + default.into_rt() }; - let stderr = if let Some(stderr) = self.stdout.as_ref() { - stderr.try_clone()?.into_rt() + let stderr = if let Some(stderr) = self.stderr.as_ref() { + stderr.into_rt() } else { - default.try_clone()?.into_rt() + default.into_rt() }; let mut env = Vec::<(String, String)>::new(); @@ -146,11 +140,11 @@ impl Command { stderr, }; - let (handle, stdin, stdout, stderr) = - moto_rt::process::spawn(args).map_err(map_motor_error)?; + let res = moto_rt::process::spawn(args).map_err(map_motor_error)?; + let (handle, stdin, stdout, stderr) = (res.handle, res.stdin, res.stdout, res.stderr); Ok(( - Process { handle }, + Process { handle, pid: res.pid as u32 }, StdioPipes { stdin: if stdin >= 0 { Some(unsafe { ChildPipe::from_raw_fd(stdin) }) @@ -172,6 +166,29 @@ impl Command { } } +pub fn output(cmd: &mut Command) -> io::Result<(ExitStatus, Vec, Vec)> { + let (mut process, mut pipes) = cmd.spawn(Stdio::MakePipe, false)?; + + drop(pipes.stdin.take()); + let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); + crate::thread::scope(|scope| { + let waiter = scope.spawn(move || { + let status = process.wait(); + drop(process); + status + }); + let read_result = match (pipes.stdout.take(), pipes.stderr.take()) { + (None, None) => Ok(()), + (Some(out), None) => out.read_to_end(&mut stdout).map(|_| ()), + (None, Some(err)) => err.read_to_end(&mut stderr).map(|_| ()), + (Some(out), Some(err)) => read_output(out, &mut stdout, err, &mut stderr), + }; + let status = waiter.join().expect("child wait thread panicked"); + read_result?; + Ok((status?, stdout, stderr)) + }) +} + impl From for Stdio { fn from(fd: crate::sys::fd::FileDesc) -> Stdio { Stdio::Fd(fd) @@ -179,20 +196,20 @@ impl From for Stdio { } impl From for Stdio { - fn from(_file: File) -> Stdio { - panic!("Not implemented") + fn from(file: File) -> Stdio { + Stdio::Fd(file.into_inner()) } } impl From for Stdio { fn from(_: io::Stdout) -> Stdio { - panic!("Not implemented") + Stdio::ParentStdout } } impl From for Stdio { fn from(_: io::Stderr) -> Stdio { - panic!("Not implemented") + Stdio::ParentStderr } } @@ -255,6 +272,7 @@ impl From for ExitCode { pub struct Process { handle: u64, + pid: u32, } impl Drop for Process { @@ -265,7 +283,9 @@ impl Drop for Process { impl Process { pub fn id(&self) -> u32 { - 0 + // The kernel bounds pids to the i32-positive range, so the pid the + // runtime reported at spawn is exact (pid-refactoring-design.md). + self.pid } pub fn kill(&mut self) -> io::Result<()> { @@ -324,14 +344,25 @@ impl<'a> fmt::Debug for CommandArgs<'a> { pub type ChildPipe = crate::sys::pipe::Pipe; pub fn read_output( - _out: ChildPipe, - _stdout: &mut Vec, - _err: ChildPipe, - _stderr: &mut Vec, + out: ChildPipe, + stdout: &mut Vec, + err: ChildPipe, + stderr: &mut Vec, ) -> io::Result<()> { - Err(io::Error::from_raw_os_error(moto_rt::E_NOT_IMPLEMENTED.into())) + // Drain both pipes concurrently so the child can't deadlock filling one + // pipe while we block reading the other. + crate::thread::scope(|s| { + let err_reader = s.spawn(move || err.read_to_end(stderr)); + let out_res = out.read_to_end(stdout); + let err_res = err_reader.join().expect("stderr reader thread panicked"); + out_res?; + err_res?; + Ok(()) + }) } pub fn getpid() -> u32 { - panic!("Pids on Motor OS are u64.") + // Motor OS pids are u64 in the ABI, but the kernel bounds them to i32 + // to be compatible with the wider ecosystem. + moto_rt::process::current_pid().try_into().expect("current_pid() too large") } diff --git a/library/std/src/sys/stdio/motor.rs b/library/std/src/sys/stdio/motor.rs index acc30b134d828..24a9dd830437f 100644 --- a/library/std/src/sys/stdio/motor.rs +++ b/library/std/src/sys/stdio/motor.rs @@ -1,5 +1,5 @@ use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::{AsInner, FromInner, IntoInner, map_motor_error}; +use crate::sys::{AsInner, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error}; use crate::{io, process, sys}; pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; @@ -38,6 +38,15 @@ impl io::Read for Stdin { fn read(&mut self, buf: &mut [u8]) -> io::Result { moto_rt::fs::read(moto_rt::FD_STDIN, buf).map_err(map_motor_error) } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + moto_rt::fs::read_vectored(moto_rt::FD_STDIN, &mut io_slices_mut(bufs)) + .map_err(map_motor_error) + } + + fn is_read_vectored(&self) -> bool { + true + } } impl io::Write for Stdout { @@ -48,6 +57,14 @@ impl io::Write for Stdout { fn flush(&mut self) -> io::Result<()> { moto_rt::fs::flush(moto_rt::FD_STDOUT).map_err(map_motor_error) } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + moto_rt::fs::write_vectored(moto_rt::FD_STDOUT, &io_slices(bufs)).map_err(map_motor_error) + } + + fn is_write_vectored(&self) -> bool { + true + } } impl io::Write for Stderr { @@ -58,6 +75,14 @@ impl io::Write for Stderr { fn flush(&mut self) -> io::Result<()> { moto_rt::fs::flush(moto_rt::FD_STDERR).map_err(map_motor_error) } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + moto_rt::fs::write_vectored(moto_rt::FD_STDERR, &io_slices(bufs)).map_err(map_motor_error) + } + + fn is_write_vectored(&self) -> bool { + true + } } pub fn panic_output() -> Option { From da0dbddf77e463f82e3e3f1c88ae898a1a246cfa Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 23 Aug 2026 13:36:20 +0200 Subject: [PATCH 04/19] formatting --- tests/ui/float/classify-runtime-const.rs | 54 +++++++++++------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/tests/ui/float/classify-runtime-const.rs b/tests/ui/float/classify-runtime-const.rs index e3b97386a3124..55579dffd4c03 100644 --- a/tests/ui/float/classify-runtime-const.rs +++ b/tests/ui/float/classify-runtime-const.rs @@ -10,46 +10,40 @@ // This tests the float classification functions, for regular runtime code and for const evaluation. - -use std::num::FpCategory::*; - #[cfg(not(ctfe))] use std::hint::black_box; +use std::num::FpCategory::*; #[cfg(ctfe)] #[allow(unused)] -const fn black_box(x: T) -> T { x } +const fn black_box(x: T) -> T { + x +} #[cfg(not(ctfe))] macro_rules! assert_test { - ($a:expr, NonDet) => { - { - // Compute `a`, but do not compare with anything as the result is non-deterministic. - let _val = $a; - } - }; - ($a:expr, $b:ident) => { - { - // Let-bind to avoid promotion. - // No black_box here! That can mask x87 failures. - let a = $a; - let b = $b; - assert_eq!(a, b, "{} produces wrong result", stringify!($a)); - } - }; + ($a:expr, NonDet) => {{ + // Compute `a`, but do not compare with anything as the result is non-deterministic. + let _val = $a; + }}; + ($a:expr, $b:ident) => {{ + // Let-bind to avoid promotion. + // No black_box here! That can mask x87 failures. + let a = $a; + let b = $b; + assert_eq!(a, b, "{} produces wrong result", stringify!($a)); + }}; } #[cfg(ctfe)] macro_rules! assert_test { - ($a:expr, NonDet) => { - { - // Compute `a`, but do not compare with anything as the result is non-deterministic. - const _: () = { let _val = $a; }; - } - }; - ($a:expr, $b:ident) => { - { - const _: () = assert!(matches!($a, $b)); - } - }; + ($a:expr, NonDet) => {{ + // Compute `a`, but do not compare with anything as the result is non-deterministic. + const _: () = { + let _val = $a; + }; + }}; + ($a:expr, $b:ident) => {{ + const _: () = assert!(matches!($a, $b)); + }}; } macro_rules! suite { From 50cfb9c0fa474859c8c6f1fb4ceb7c2623aa0a1d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 23 Aug 2026 13:40:18 +0200 Subject: [PATCH 05/19] use `std::hint::black_box` even for ctfe --- tests/ui/float/classify-runtime-const.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ui/float/classify-runtime-const.rs b/tests/ui/float/classify-runtime-const.rs index 55579dffd4c03..bca3ca283efa0 100644 --- a/tests/ui/float/classify-runtime-const.rs +++ b/tests/ui/float/classify-runtime-const.rs @@ -10,14 +10,8 @@ // This tests the float classification functions, for regular runtime code and for const evaluation. -#[cfg(not(ctfe))] use std::hint::black_box; use std::num::FpCategory::*; -#[cfg(ctfe)] -#[allow(unused)] -const fn black_box(x: T) -> T { - x -} #[cfg(not(ctfe))] macro_rules! assert_test { From 8227c58713c2948671059913fe7a5f56b509bbe1 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 23 Aug 2026 13:36:43 +0200 Subject: [PATCH 06/19] run `classify-runtime-const` for `f128` --- tests/ui/float/classify-runtime-const.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/ui/float/classify-runtime-const.rs b/tests/ui/float/classify-runtime-const.rs index bca3ca283efa0..7a06594eb7d42 100644 --- a/tests/ui/float/classify-runtime-const.rs +++ b/tests/ui/float/classify-runtime-const.rs @@ -2,11 +2,12 @@ //@ revisions: opt noopt ctfe //@[opt] compile-flags: -O //@[noopt] compile-flags: -Zmir-opt-level=0 -//@ min-llvm-version: 22 -//@ compile-flags: --check-cfg=cfg(target_has_reliable_f16) +//@ min-llvm-version: 23 +//@ compile-flags: --check-cfg=cfg(target_has_reliable_f16,target_has_reliable_f128) // ignore-tidy-file-linelength #![feature(cfg_target_has_reliable_f16_f128)] #![cfg_attr(target_has_reliable_f16, feature(f16))] +#![cfg_attr(target_has_reliable_f128, feature(f128))] // This tests the float classification functions, for regular runtime code and for const evaluation. @@ -60,6 +61,13 @@ macro_rules! suite { type $tyname = f64; suite_inner!(f64 => $($tt)*); } + + #[cfg(target_has_reliable_f128)] + fn f128() { + #[allow(unused)] + type $tyname = f128; + suite_inner!(f128 => $($tt)*); + } } } @@ -124,5 +132,6 @@ fn main() { f16(); f32(); f64(); - // FIXME(f128): also test f128 + #[cfg(target_has_reliable_f128)] + f128(); } From af7623a1804bf9045ca3dacb34ad0fb30caa27b4 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Tue, 18 Aug 2026 12:26:29 +0000 Subject: [PATCH 07/19] add crashtests --- tests/crashes/{108428.rs => 108248.rs} | 2 +- tests/crashes/138262.rs | 12 ++++++++++++ tests/crashes/142155.rs | 12 ++++++++++++ tests/crashes/144241.rs | 4 ++++ tests/crashes/149562.rs | 10 ++++++++++ tests/crashes/152414.rs | 6 ++++++ tests/crashes/152416.rs | 17 +++++++++++++++++ tests/crashes/152626.rs | 7 +++++++ tests/crashes/154903.rs | 7 +++++++ tests/crashes/154963.rs | 10 ++++++++++ tests/crashes/155053.rs | 11 +++++++++++ tests/crashes/156101.rs | 4 ++++ tests/crashes/156288.rs | 3 +++ 13 files changed, 104 insertions(+), 1 deletion(-) rename tests/crashes/{108428.rs => 108248.rs} (84%) create mode 100644 tests/crashes/138262.rs create mode 100644 tests/crashes/142155.rs create mode 100644 tests/crashes/144241.rs create mode 100644 tests/crashes/149562.rs create mode 100644 tests/crashes/152414.rs create mode 100644 tests/crashes/152416.rs create mode 100644 tests/crashes/152626.rs create mode 100644 tests/crashes/154903.rs create mode 100644 tests/crashes/154963.rs create mode 100644 tests/crashes/155053.rs create mode 100644 tests/crashes/156101.rs create mode 100644 tests/crashes/156288.rs diff --git a/tests/crashes/108428.rs b/tests/crashes/108248.rs similarity index 84% rename from tests/crashes/108428.rs rename to tests/crashes/108248.rs index b18123b6a7c40..36252e29d33f0 100644 --- a/tests/crashes/108428.rs +++ b/tests/crashes/108248.rs @@ -1,4 +1,4 @@ -//@ known-bug: #108428 +//@ known-bug: #108248 //@ needs-rustc-debug-assertions //@ compile-flags: -Wunused-lifetimes fn main() { diff --git a/tests/crashes/138262.rs b/tests/crashes/138262.rs new file mode 100644 index 0000000000000..ce5b3bb257e5d --- /dev/null +++ b/tests/crashes/138262.rs @@ -0,0 +1,12 @@ +//@ known-bug: #138262 +//@ compile-flags: -Zsanitizer=cfi -Ccodegen-units=1 -Clto -Clink-dead-code=true -Cunsafe-allow-abi-mismatch=sanitizer -Ctarget-feature=-crt-static +//@ ignore-backends: gcc +//@ needs-sanitizer-cfi +fn foo() {} + +core::arch::global_asm!("/* {} */", sym foo::<{ + || {}; + 0 +}>); + +fn main() {} diff --git a/tests/crashes/142155.rs b/tests/crashes/142155.rs new file mode 100644 index 0000000000000..8c0769bf2b586 --- /dev/null +++ b/tests/crashes/142155.rs @@ -0,0 +1,12 @@ +//@ known-bug: #142155 +//@ needs-rustc-debug-assertions +//@ edition: 2021 + +#![warn(tail_expr_drop_order)] +use core::future::Future; + +fn f() -> impl Future> { + async { Some("nope".into()) } +} + +fn main() {} diff --git a/tests/crashes/144241.rs b/tests/crashes/144241.rs new file mode 100644 index 0000000000000..3f91fcc7c6275 --- /dev/null +++ b/tests/crashes/144241.rs @@ -0,0 +1,4 @@ +//@ known-bug: #144241 +fn main() { + |_: dyn ?Sized + !Send| {} +} diff --git a/tests/crashes/149562.rs b/tests/crashes/149562.rs new file mode 100644 index 0000000000000..4d032a0af5c3e --- /dev/null +++ b/tests/crashes/149562.rs @@ -0,0 +1,10 @@ +//@ known-bug: #149562 +//@ needs-rustc-debug-assertions +fn a() -> T +where + T: ?Sized, + T: ?Sized, +{ +} + +fn main() {} diff --git a/tests/crashes/152414.rs b/tests/crashes/152414.rs new file mode 100644 index 0000000000000..226f9e29faad6 --- /dev/null +++ b/tests/crashes/152414.rs @@ -0,0 +1,6 @@ +//@ known-bug: #152414 +//@ needs-rustc-debug-assertions +#![feature(generic_assert)] +fn main() { + assert!(size_of(val, 1) >= 1); +} diff --git a/tests/crashes/152416.rs b/tests/crashes/152416.rs new file mode 100644 index 0000000000000..9ca418cce3628 --- /dev/null +++ b/tests/crashes/152416.rs @@ -0,0 +1,17 @@ +//@ known-bug: #152416 +//@ needs-rustc-debug-assertions +//@ compile-flags: -Zunstable-options + +trait AssetID {} +trait Archive { + fn name(&self); +} +struct NorthlightAssetID; +impl AssetID for NorthlightAssetID {} +fn get() -> Box> { + let x: Box> = todo!(); + x +} +fn main() { + get().name(); +} diff --git a/tests/crashes/152626.rs b/tests/crashes/152626.rs new file mode 100644 index 0000000000000..eafb714c2f5c2 --- /dev/null +++ b/tests/crashes/152626.rs @@ -0,0 +1,7 @@ +//@ known-bug: #152626 +//@ needs-rustc-debug-assertions +struct A>(T); +fn f() -> A<&'static ()> { + todo!() +} +fn main() {} diff --git a/tests/crashes/154903.rs b/tests/crashes/154903.rs new file mode 100644 index 0000000000000..63e80d8f9e251 --- /dev/null +++ b/tests/crashes/154903.rs @@ -0,0 +1,7 @@ +//@ known-bug: #154903 +//@ compile-flags: -Zlint-mir +#![feature(guard_patterns)] + +fn a(((x if true, _) | (_, x)): (i32, i32)) {} + +fn main() {} diff --git a/tests/crashes/154963.rs b/tests/crashes/154963.rs new file mode 100644 index 0000000000000..8fafc29c48342 --- /dev/null +++ b/tests/crashes/154963.rs @@ -0,0 +1,10 @@ +//@ known-bug: #154963 +#![feature(extern_types, negative_impls)] + +unsafe extern "C" { + type ExternType; +} + +impl !Unpin for ExternType {} + +fn main() {} diff --git a/tests/crashes/155053.rs b/tests/crashes/155053.rs new file mode 100644 index 0000000000000..31b9ccaf20540 --- /dev/null +++ b/tests/crashes/155053.rs @@ -0,0 +1,11 @@ +//@ known-bug: #155053 +#![feature(pin_ergonomics)] +#![feature(extern_types)] + +unsafe extern "C" { + type ExternType; +} + +impl Unpin for ExternType {} + +fn main() {} diff --git a/tests/crashes/156101.rs b/tests/crashes/156101.rs new file mode 100644 index 0000000000000..c95361fab2ecc --- /dev/null +++ b/tests/crashes/156101.rs @@ -0,0 +1,4 @@ +//@ known-bug: #156101 +fn main() { + format_args!(concat!("𐏿", "{f:?#}")); +} diff --git a/tests/crashes/156288.rs b/tests/crashes/156288.rs new file mode 100644 index 0000000000000..b745cfe063dda --- /dev/null +++ b/tests/crashes/156288.rs @@ -0,0 +1,3 @@ +//@ known-bug: #156288 +#[warn(rust_2021_incompatible_closure_captures)] +const _: () = |b| move || b; From f0a7f8db522c3b97e01f3a2f02a2d266a28989bc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Aug 2026 08:38:24 +0200 Subject: [PATCH 08/19] interpret: fix after_local_read handling --- .../rustc_const_eval/src/interpret/machine.rs | 8 ++---- .../rustc_const_eval/src/interpret/operand.rs | 28 ++++++------------- src/tools/miri/src/machine.rs | 8 ++---- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 0528fee35031c..8d9b26cd18423 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -621,14 +621,10 @@ pub trait Machine<'tcx>: Sized { interp_ok(ReturnAction::Normal) } - /// Called immediately after an "immediate" local variable is read in a given frame + /// Called immediately after an "immediate" local variable is read /// (i.e., this is called for reads that do not end up accessing addressable memory). #[inline(always)] - fn after_local_read( - _ecx: &InterpCx<'tcx, Self>, - _frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>, - _local: mir::Local, - ) -> InterpResult<'tcx> { + fn after_local_read(_ecx: &InterpCx<'tcx, Self>, _local: mir::Local) -> InterpResult<'tcx> { interp_ok(()) } diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 4bafea98cd569..91c0dd9b6c3ab 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -16,9 +16,9 @@ use tracing::field::Empty; use tracing::trace; use super::{ - CtfeProvenance, Frame, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, - OffsetMode, PlaceTy, Pointer, Projectable, Provenance, Scalar, alloc_range, err_ub, - from_known_layout, interp_ok, mir_assign_valid_types, throw_ub, + CtfeProvenance, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, OffsetMode, + PlaceTy, Pointer, Projectable, Provenance, Scalar, alloc_range, err_ub, from_known_layout, + interp_ok, mir_assign_valid_types, throw_ub, }; use crate::enter_trace_span; @@ -722,32 +722,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(s) } - /// Read from a local of the current frame. Convenience method for [`InterpCx::local_at_frame_to_op`]. - pub fn local_to_op( - &self, - local: mir::Local, - layout: Option>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - self.local_at_frame_to_op(self.frame(), local, layout) - } - - /// Read from a local of a given frame. + /// Read from a local of a current frame. /// Will not access memory, instead an indirect `Operand` is returned. - /// - /// This is public because it is used by [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) - /// to get an OpTy from a local. - pub fn local_at_frame_to_op( + pub fn local_to_op( &self, - frame: &Frame<'tcx, M::Provenance, M::FrameExtra>, local: mir::Local, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + let frame = self.frame(); let layout = self.layout_of_local(frame, local, layout)?; let op = *frame.locals[local].access()?; if matches!(op, Operand::Immediate(_)) { assert!(!layout.is_unsized()); + if !self.validation_in_progress() { + M::after_local_read(self, local)?; + } } - M::after_local_read(self, frame, local)?; interp_ok(OpTy { op, layout }) } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 361ebef73b357..076738a5db08e 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -2000,12 +2000,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { res } - fn after_local_read( - ecx: &InterpCx<'tcx, Self>, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - local: mir::Local, - ) -> InterpResult<'tcx> { - if let Some(data_race) = &frame.extra.data_race { + fn after_local_read(ecx: &InterpCx<'tcx, Self>, local: mir::Local) -> InterpResult<'tcx> { + if let Some(data_race) = &ecx.frame().extra.data_race { let _trace = enter_trace_span!(data_race::after_local_read); data_race.local_read(local, &ecx.machine); } From 1071f5082ab2b095f4633c7ea5074e5d67300be0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Aug 2026 17:19:09 +0200 Subject: [PATCH 09/19] do not let outside code mutate locals --- compiler/rustc_const_eval/src/interpret/stack.rs | 11 ++++++++--- src/tools/miri/priroda/src/debugger.rs | 2 +- src/tools/miri/src/concurrency/thread.rs | 5 +++-- src/tools/miri/src/diagnostics.rs | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index d291f1f6fdcbc..b59122ed91ab8 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -90,7 +90,7 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> { /// can either directly contain `Scalar` or refer to some part of an `Allocation`. /// /// Do *not* access this directly; always go through the machine hook! - pub locals: IndexVec>, + pub(super) locals: IndexVec>, /// The complete variable argument list of this frame. Its elements must be dropped when the /// frame is popped. @@ -168,8 +168,9 @@ impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { /// This is a hack because Miri needs a way to visit all the provenance in a `LocalState` /// without having a layout or `TyCtxt` available, and we want to keep the `Operand` type - /// private. - pub fn as_mplace_or_imm( + /// private. Does not count as a read of the local for the AM! It's a "ghost" read, like for + /// validation or similar purposes. + pub fn as_mplace_or_imm_for_validation( &self, ) -> Option>, MemPlaceMeta), Immediate>> { match self.value { @@ -293,6 +294,10 @@ impl<'tcx, Prov: Provenance, Extra> Frame<'tcx, Prov, Extra> { self.return_cont } + pub fn locals(&self) -> &IndexVec> { + &self.locals + } + /// Return the `SourceInfo` of the current instruction. pub fn current_source_info(&self) -> Option<&mir::SourceInfo> { self.loc.left().map(|loc| self.body.source_info(loc)) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 4c4c811c01358..dbfbe34e79bd8 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -890,7 +890,7 @@ impl<'tcx> PrirodaContext<'tcx> { value: "".to_string(), }; - match &frame.locals[local].as_mplace_or_imm() { + match &frame.locals()[local].as_mplace_or_imm_for_validation() { None => { local_desc.value = "".to_string(); } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 5b3b56760a6c4..c1cf199e7fcfa 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -345,8 +345,8 @@ impl VisitProvenance for Thread<'_> { impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { let return_place = self.return_place(); + let locals = self.locals(); let Frame { - locals, extra, // There are some private fields we cannot access; they contain no tags. .. @@ -356,7 +356,8 @@ impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { return_place.visit_provenance(visit); // Locals. for local in locals.iter() { - match local.as_mplace_or_imm() { + // We only need the provenance so it's good for this to not be a real read. + match local.as_mplace_or_imm_for_validation() { None => {} Some(Either::Left((ptr, meta))) => { ptr.visit_provenance(visit); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 4a0869da63bff..1d88a792734f9 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -500,7 +500,7 @@ pub fn report_result<'tcx>( trace!("-------------------"); trace!("Frame {}", i); trace!(" return: {:?}", frame.return_place()); - for (i, local) in frame.locals.iter().enumerate() { + for (i, local) in frame.locals().iter().enumerate() { trace!(" local {}: {:?}", i, local); } } From e8c938fdb982cafcfc2e0dbf6f1d0810e4068bf8 Mon Sep 17 00:00:00 2001 From: MarcoIeni <11428655+MarcoIeni@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:50:29 +0200 Subject: [PATCH 10/19] renovate: add lockfile update warning --- .github/renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e0ddb3670fa83..0ab5f5eeb57a3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -48,8 +48,8 @@ "groupName": "Cargo lock file maintenance", "commitMessageAction": "Compiler and tools lock file update", // Renovate merges all matching rules, so the lockfiles rules below - // also inherits this note and asks Triagebot for a dep-bumps reviewer. - "prBodyNotes": ["r? dep-bumps"] + // also inherits these notes and asks Triagebot for a dep-bumps reviewer. + "prBodyNotes": ["r? dep-bumps", "⚠️ Supply chain security safety: wait 3 days until the last commit to this PR before running `bors try` or `bors r+`"] }, { // Update library/Cargo.lock in a dedicated PR. From 57dd60c536d1d51f3bbb26b9381b01cdd319f0b2 Mon Sep 17 00:00:00 2001 From: rustbot <47979223+rustbot@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:01:02 +0200 Subject: [PATCH 11/19] Update books --- src/doc/reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/reference b/src/doc/reference index eda708334abad..3b38834b39f73 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit eda708334abad285d63bc1f3558a51e8b790346e +Subproject commit 3b38834b39f732c64686f7c64aa29dcf3cd83ba5 From a829ab6166ec393202cfe6e5af09e0af6e2c485b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Aug 2026 17:49:25 +0200 Subject: [PATCH 12/19] rename the sans-hook accesses into 'ghost' accesses --- .../rustc_const_eval/src/interpret/memory.rs | 50 +++++++++---------- .../rustc_const_eval/src/interpret/stack.rs | 2 +- .../src/interpret/validity.rs | 2 +- src/tools/miri/priroda/src/debugger.rs | 3 +- src/tools/miri/src/concurrency/data_race.rs | 4 +- src/tools/miri/src/concurrency/thread.rs | 2 +- 6 files changed, 30 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 96152737d3d1e..03a621812eb57 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -135,10 +135,10 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { // FIXME: this should not be public, but interning currently needs access to it pub(super) dead_alloc_map: FxIndexMap, - /// This stores whether we are currently doing reads purely for the purpose of validation. - /// Those reads do not trigger the machine's hooks for memory reads. + /// This stores whether we are currently doing reads/writes that aren't "real". + /// Those accesses do not trigger the machine's hooks. /// Needless to say, this must only be set with great care! - validation_in_progress: Cell, + ghost_mode: Cell, } /// A reference to some allocation that was already bounds-checked for the given region @@ -166,7 +166,7 @@ impl<'tcx, M: Machine<'tcx>> Memory<'tcx, M> { extra_fn_ptr_map: FxIndexMap::default(), va_list_map: FxIndexMap::default(), dead_alloc_map: FxIndexMap::default(), - validation_in_progress: Cell::new(false), + ghost_mode: Cell::new(false), } } @@ -768,7 +768,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // We want to call the hook on *all* accesses that involve an AllocId, including zero-sized // accesses. That means we cannot rely on the closure above or the `Some` branch below. We // do this after `check_and_deref_ptr` to ensure some basic sanity has already been checked. - if !self.memory.validation_in_progress.get() { + if !self.memory.ghost_mode.get() { if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(ptr, size_i64) { M::before_alloc_access(self.tcx, &self.machine, alloc_id)?; } @@ -776,7 +776,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc { let range = alloc_range(offset, size); - if !self.memory.validation_in_progress.get() { + if !self.memory.ghost_mode.get() { M::before_memory_read( self.tcx, &self.machine, @@ -856,7 +856,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx, Option>> { let tcx = self.tcx; - let validation_in_progress = self.memory.validation_in_progress.get(); + let validation_in_progress = self.memory.ghost_mode.get(); let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes let ptr_and_alloc = Self::check_and_deref_ptr( @@ -1204,48 +1204,44 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { result } - /// Runs the closure in "validation" mode, which means the machine's memory read hooks will be + /// Runs the closure in "ghost" mode, which means the machine's memory read hooks will be /// suppressed. Needless to say, this must only be set with great care! Cannot be nested. /// /// We do this so Miri's allocation access tracking does not show the validation - /// reads as spurious accesses. - pub fn run_for_validation_mut(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + /// reads as spurious accesses as those aren't "real" reads. Also useful for debuggers + /// that want to just display the Miri machine state. + pub fn ghost_run_mut(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { // This deliberately uses `==` on `bool` to follow the pattern // `assert!(val.replace(new) == old)`. - assert!( - self.memory.validation_in_progress.replace(true) == false, - "`validation_in_progress` was already set" - ); + assert!(self.memory.ghost_mode.replace(true) == false, "`ghost_mode` was already set"); let res = f(self); assert!( - self.memory.validation_in_progress.replace(false) == true, - "`validation_in_progress` was unset by someone else" + self.memory.ghost_mode.replace(false) == true, + "`ghost_mode` was unset by someone else" ); res } - /// Runs the closure in "validation" mode, which means the machine's memory read hooks will be + /// Runs the closure in "ghost" mode, which means the machine's memory read hooks will be /// suppressed. Needless to say, this must only be set with great care! Cannot be nested. /// /// We do this so Miri's allocation access tracking does not show the validation - /// reads as spurious accesses. - pub fn run_for_validation_ref(&self, f: impl FnOnce(&Self) -> R) -> R { + /// reads as spurious accesses as those aren't "real" reads. Also useful for debuggers + /// that want to just display the Miri machine state. + pub fn ghost_run(&self, f: impl FnOnce(&Self) -> R) -> R { // This deliberately uses `==` on `bool` to follow the pattern // `assert!(val.replace(new) == old)`. - assert!( - self.memory.validation_in_progress.replace(true) == false, - "`validation_in_progress` was already set" - ); + assert!(self.memory.ghost_mode.replace(true) == false, "`ghost_mode` was already set"); let res = f(self); assert!( - self.memory.validation_in_progress.replace(false) == true, - "`validation_in_progress` was unset by someone else" + self.memory.ghost_mode.replace(false) == true, + "`ghost_mode` was unset by someone else" ); res } pub(super) fn validation_in_progress(&self) -> bool { - self.memory.validation_in_progress.get() + self.memory.ghost_mode.get() } } @@ -1516,7 +1512,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; let src_alloc = self.get_alloc_raw(src_alloc_id)?; let src_range = alloc_range(src_offset, size); - assert!(!self.memory.validation_in_progress.get(), "we can't be copying during validation"); + assert!(!self.memory.ghost_mode.get(), "we can't be copying during validation"); // Trigger read hook. // For the overlapping case, it is crucial that we trigger the read hook diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index b59122ed91ab8..5686413915ca5 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -170,7 +170,7 @@ impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { /// without having a layout or `TyCtxt` available, and we want to keep the `Operand` type /// private. Does not count as a read of the local for the AM! It's a "ghost" read, like for /// validation or similar purposes. - pub fn as_mplace_or_imm_for_validation( + pub fn as_mplace_or_imm_ghost( &self, ) -> Option>, MemPlaceMeta), Immediate>> { match self.value { diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 5f0799c70aa0b..2cd4caf5ba250 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -1611,7 +1611,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { trace!("validate_place_internal: {:?}, {:?}", *val, val.layout.ty); // Run the visitor. - self.run_for_validation_mut(|ecx| { + self.ghost_run_mut(|ecx| { let reset_padding = reset_provenance_and_padding && { // Check if `val` is actually stored in memory. If not, padding is not even // represented and we need not reset it. diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index dbfbe34e79bd8..a4766e53314cb 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -890,13 +890,14 @@ impl<'tcx> PrirodaContext<'tcx> { value: "".to_string(), }; - match &frame.locals()[local].as_mplace_or_imm_for_validation() { + match &frame.locals()[local].as_mplace_or_imm_ghost() { None => { local_desc.value = "".to_string(); } Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), Some(Either::Left(_) | Either::Right(_)) => { + // FIXME: This seems wrong, it ignore the frame. let op = self .ecx .local_to_op(local, None) diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index a1102a9642439..c086f71c2dcaa 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -779,7 +779,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { // Only metadata on the location itself is used. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let old_val = this.run_for_validation_ref(|this| this.read_scalar(place)).discard_err(); + let old_val = this.ghost_run(|this| this.read_scalar(place)).discard_err(); return genmc_ctx.atomic_load( this, place.ptr().addr(), @@ -811,7 +811,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { // Read the previous value so we can put it in the store buffer later. // Both GenMC and Miri need this. This value is nonsense if there are concurrent writes // but the code consuming the value is aware of that. - let old_val = this.run_for_validation_ref(|this| this.read_scalar(dest)).discard_err(); + let old_val = this.ghost_run(|this| this.read_scalar(dest)).discard_err(); // Inform GenMC about the atomic store. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index c1cf199e7fcfa..f5cfc62ad659a 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -357,7 +357,7 @@ impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> { // Locals. for local in locals.iter() { // We only need the provenance so it's good for this to not be a real read. - match local.as_mplace_or_imm_for_validation() { + match local.as_mplace_or_imm_ghost() { None => {} Some(Either::Left((ptr, meta))) => { ptr.visit_provenance(visit); From 531e8121278a8c295e41ad93beada0790766fcad Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Aug 2026 19:00:57 +0200 Subject: [PATCH 13/19] add back a way to get an OpTy for a local in any frame --- .../rustc_const_eval/src/interpret/operand.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 91c0dd9b6c3ab..81f26cd7e5479 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -16,9 +16,9 @@ use tracing::field::Empty; use tracing::trace; use super::{ - CtfeProvenance, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, OffsetMode, - PlaceTy, Pointer, Projectable, Provenance, Scalar, alloc_range, err_ub, from_known_layout, - interp_ok, mir_assign_valid_types, throw_ub, + CtfeProvenance, Frame, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, + OffsetMode, PlaceTy, Pointer, Projectable, Provenance, Scalar, alloc_range, err_ub, + from_known_layout, interp_ok, mir_assign_valid_types, throw_ub, }; use crate::enter_trace_span; @@ -741,6 +741,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(OpTy { op, layout }) } + /// Tools like Priroda and [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) + /// need to access any local without triggering any access hook, since these are not actual + /// AM-level accesses. Do not call this from inside the interpreter! + /// + /// Remember to use `ghost_run` when accessing memory for such purposes, to suppress + /// the access hooks for that as well. + pub fn ghost_local_in_frame_to_op( + &self, + frame: &Frame<'tcx, M::Provenance, M::FrameExtra>, + local: mir::Local, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + let layout = self.layout_of_local(frame, local, None)?; + let op = *frame.locals[local].access()?; + interp_ok(OpTy { op, layout }) + } + /// Every place can be read from, so we can turn them into an operand. /// This will definitely return `Indirect` if the place is a `Ptr`, i.e., this /// will never actually read from memory. From c2b76de22337390547a1d2eb20ca40e55a0b88d7 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Tue, 4 Aug 2026 19:05:06 -0400 Subject: [PATCH 14/19] add dir operations --- library/std/src/fs.rs | 150 ++++++++++++++++++++++++++ library/std/src/fs/tests.rs | 35 ++++++ library/std/src/sys/fs/common.rs | 20 +++- library/std/src/sys/fs/unix/dir.rs | 54 ++++++++-- library/std/src/sys/fs/windows/dir.rs | 24 +++++ 5 files changed, 276 insertions(+), 7 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 1e46ddb99e010..3cc375b7290da 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1578,6 +1578,63 @@ impl Dir { .map(|inner| Self { inner }) } + /// Attempts to open a directory at `path` according to `opts`. + /// + /// This function opens a directory. To open a file instead, see [`File::open`]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::{Dir, OpenOptions}, io}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open_with("foo", &OpenOptions::new().read(true))?; + /// let mut f = dir.open_file("bar.txt")?; + /// let contents = io::read_to_string(f)?; + /// assert_eq!(contents, "Hello, world!"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_with>(path: P, opts: &OpenOptions) -> io::Result { + fs_imp::Dir::open(path.as_ref(), &opts.0).map(|inner| Self { inner }) + } + + /// Attempts to open a directory at `path` with the minimum permissions for traversal. + /// + /// The permissions requested by this function are guaranteed to be sufficient to open a child + /// file or folder, but not necessarily to list all children. + /// + /// # Errors + /// + /// This function may return an error according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir, io}; + /// + /// fn main() -> std::io::Result<()> { + /// let foo = Dir::open_for_traversal("foo")?; + /// let foobar = foo.open_dir("bar")?; + /// let mut foobarbaz = foobar.open_file("baz")?; + /// let contents = io::read_to_string(foobarbaz)?; + /// assert_eq!(contents, "Hello, world!"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_for_traversal>(path: P) -> io::Result { + fs_imp::Dir::open_for_traversal(path.as_ref()).map(|inner| Self { inner }) + } + /// Queries metadata about the underlying directory. /// /// # Examples @@ -1719,6 +1776,99 @@ impl Dir { ) -> io::Result<()> { self.inner.rename(from.as_ref(), &to_dir.inner, to.as_ref()) } + + /// Attempts to create a directory relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To create a directory + /// relative to the current working directory, or at an absolute path, see + /// [`fs::create_dir`][crate::fs::create_dir]. + #[unstable(feature = "dirfd", issue = "120426")] + pub fn create_dir>(&self, path: P) -> io::Result<()> { + self.inner.create_dir(path.as_ref()) + } + + /// Attempts to open a directory in read-only mode relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To open a directory + /// relative to the current working directory, or at an absolute path, see [`Dir::open`]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let foobar = dir.open_dir("bar")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir>(&self, path: P) -> io::Result { + self.inner + .open_dir(path.as_ref(), &OpenOptions::new().read(true).0) + .map(|inner| Self { inner }) + } + + /// Attempts to open a directory relative to this directory according to `opts`. + /// + /// This function interprets `path` relative to the directory provided by `self`. To open a directory + /// relative to the current working directory, or at an absolute path, see [`Dir::open`]. + /// + /// # Errors + /// + /// This function will return errors according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::{Dir, OpenOptions}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let foobar_w = dir.open_dir_with("bar", &OpenOptions::new().write(true))?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn open_dir_with>(&self, path: P, opts: &OpenOptions) -> io::Result { + self.inner.open_dir(path.as_ref(), &opts.0).map(|inner| Self { inner }) + } + + /// Attempts to remove a directory relative to this directory. + /// + /// This function interprets `path` relative to the directory provided by `self`. To remove a directory + /// relative to the current working directory, or at an absolute path, see + /// [`fs::remove_dir`][crate::fs::remove_dir]. + /// + /// # Errors + /// + /// This function will return an error if `path` does not point to an existing directory. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::{fs::Dir}; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// dir.remove_dir("bar")?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn remove_dir>(&self, path: P) -> io::Result<()> { + self.inner.remove_dir(path.as_ref()) + } } impl AsInner for Dir { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 3a6c04146922a..075814b379027 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2756,3 +2756,38 @@ fn test_dir_rename_file() { check!(f.read_exact(&mut buf)); assert_eq!(b"bar", &buf); } + +#[test] +fn test_dir_remove_dir() { + let tmpdir = tmpdir(); + check!(fs::create_dir(tmpdir.join("foo"))); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.remove_dir("foo")); + assert!(!matches!(exists(tmpdir.join("foo")), Ok(true))); +} + +#[test] +fn test_dir_create_dir() { + let tmpdir = tmpdir(); + let dir = check!(Dir::open(tmpdir.path())); + check!(dir.create_dir("foo")); + check!(Dir::open(tmpdir.join("foo"))); +} + +#[test] +fn test_dir_open_dir() { + let tmpdir = tmpdir(); + let dir1 = check!(Dir::open(tmpdir.path())); + check!(dir1.create_dir("foo")); + let dir2 = check!(Dir::open(tmpdir.path().join("foo"))); + let mut f = + check!(dir2.open_file_with("bar.txt", &OpenOptions::new().create(true).write(true))); + check!(f.write(b"baz")); + check!(f.flush()); + drop(f); + let dir3 = check!(dir1.open_dir("foo")); + let mut f = check!(dir3.open_file("bar.txt")); + let mut buf = [0u8; 3]; + check!(f.read_exact(&mut buf)); + assert_eq!(b"baz", &buf); +} diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index edc31d21ca1fa..17b98a4506544 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] // not used on all platforms -use crate::fs::{remove_file, rename}; +use crate::fs::{create_dir, remove_dir, remove_file, rename}; use crate::io::{self, Error, ErrorKind}; use crate::path::{Path, PathBuf}; use crate::sys::IntoInner; @@ -71,6 +71,12 @@ impl Dir { path.canonicalize().map(|path| Self { path }) } + pub fn open_for_traversal(path: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.read(true); + Self::open(path, &opts) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { File::open(&self.path.join(path), opts) } @@ -86,6 +92,18 @@ impl Dir { pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> { rename(self.path.join(from), to_dir.path.join(to)) } + + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + create_dir(self.path.join(path)) + } + + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + Self::open(&self.path.join(path), opts) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + remove_dir(path) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index 84c5c24668bff..aad309362127b 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -1,4 +1,4 @@ -use libc::{c_int, renameat, unlinkat}; +use libc::{c_int, mkdirat, renameat, unlinkat}; cfg_select! { not(any( @@ -28,6 +28,14 @@ use crate::sys::helpers::run_path_with_cstr; use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r}; use crate::{fmt, fs, io}; +const TRAVERSE_DIRECTORY: i32 = + cfg_select! { + any(target_os = "freebsd", target_os = "aix") => libc::O_EXEC, + any(target_os = "linux", target_os = "android", target_os = "l4re") => libc::O_PATH, + target_os = "illumos" => libc::O_SEARCH, + _ => libc::O_RDONLY, + }; + pub struct Dir(OwnedFd); impl Dir { @@ -35,8 +43,14 @@ impl Dir { run_path_with_cstr(path, &|path| Self::open_with_c(path, opts)) } + pub fn open_for_traversal(path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| Self::open_traversal_c(path)) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { - run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, opts)) + run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, opts, 0)) + .map(|fd| FileDesc::from_inner(fd)) + .map(File) } pub fn metadata(&self) -> io::Result { @@ -59,7 +73,19 @@ impl Dir { }) } - pub fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path, &|path| self.open_file_c(path, opts, libc::O_DIRECTORY)).map(Self) + } + + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path.as_ref(), &|path| self.create_dir_c(path)) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path, &|path| self.remove_c(path, true)) + } + + fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result { let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | opts.get_access_mode()? @@ -69,15 +95,27 @@ impl Dir { Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) } - fn open_file_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result { + fn open_traversal_c(path: &CStr) -> io::Result { + let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | TRAVERSE_DIRECTORY; + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, 0) })?; + Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) })) + } + + fn open_file_c( + &self, + path: &CStr, + opts: &OpenOptions, + extra_flags: c_int, + ) -> io::Result { let flags = libc::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? - | (opts.custom_flags as c_int & !libc::O_ACCMODE); + | (opts.custom_flags as c_int & !libc::O_ACCMODE) + | extra_flags; let fd = cvt_r(|| unsafe { openat64(self.0.as_raw_fd(), path.as_ptr(), flags, opts.mode as c_int) })?; - Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) } fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> { @@ -97,6 +135,10 @@ impl Dir { }) .map(|_| ()) } + + fn create_dir_c(&self, path: &CStr) -> io::Result<()> { + cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ()) + } } impl fmt::Debug for Dir { diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 5e69515b66599..4fe0062af821b 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -66,6 +66,12 @@ impl Dir { with_native_path(path, &|path| Self::open_with_native(path, opts)) } + pub fn open_for_traversal(path: &Path) -> io::Result { + let mut opts = OpenOptions::new(); + opts.access_mode(c::FILE_TRAVERSE); + with_native_path(path, &|path| Self::open_with_native(path, &opts)) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { // NtCreateFile will fail if given an absolute path and a non-null RootDirectory if path.is_absolute() { @@ -87,6 +93,24 @@ impl Dir { self.rename_native(&from, to_dir, &to, is_dir) } + pub fn create_dir(&self, path: &Path) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.read(true); + opts.write(true); + opts.create_new(true); + self.open_dir(path, &opts).map(|_| ()) + } + + pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result { + let path = to_u16s_without_nul(&path)?; + self.open_file_native(&path, &opts, true).map(|handle| Self { handle }) + } + + pub fn remove_dir(&self, path: &Path) -> io::Result<()> { + let path = to_u16s_without_nul(&path)?; + self.remove_native(&path, true) + } + fn open_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result { let creation = opts.get_creation_mode()?; let sa = c::SECURITY_ATTRIBUTES { From 95b56fe397676cc327ccb8bab689940e33b864c6 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:12:35 +0800 Subject: [PATCH 15/19] Fix --- src/tools/rustfmt/src/items.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 6a68e2ec8c502..a4719334fd5e6 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -2361,7 +2361,14 @@ impl Rewrite for ast::Param { Ok(result) } else { - self.ty.rewrite_result(context, shape) + combine_strs_with_missing_comments( + context, + ¶m_attrs_result, + &self.ty.rewrite_result(context, shape)?, + span, + shape, + !has_multiple_attr_lines && !has_doc_comments, + ) } } } From 27f59f4998449897e307ec9c55df58439193e906 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:12:43 +0800 Subject: [PATCH 16/19] Add variadic test --- src/tools/rustfmt/tests/source/issue-6561/variadic.rs | 5 +++++ src/tools/rustfmt/tests/target/issue-6561/variadic.rs | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/issue-6561/variadic.rs create mode 100644 src/tools/rustfmt/tests/target/issue-6561/variadic.rs diff --git a/src/tools/rustfmt/tests/source/issue-6561/variadic.rs b/src/tools/rustfmt/tests/source/issue-6561/variadic.rs new file mode 100644 index 0000000000000..6108d584d30d8 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()]...); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/target/issue-6561/variadic.rs b/src/tools/rustfmt/tests/target/issue-6561/variadic.rs new file mode 100644 index 0000000000000..bf5274f31e1da --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6561/variadic.rs @@ -0,0 +1,5 @@ +#[allow()] +unsafe extern "C" { + #[allow()] + pub fn foo(#[allow()] arg: *mut u8, #[allow()] ...); +} From 3159d9fe032f4be608e8f90706ce114932a91a5b Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Sat, 28 Jun 2025 23:17:40 +0800 Subject: [PATCH 17/19] Add trait fn tests --- src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs | 6 ++++++ src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs create mode 100644 src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs diff --git a/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs b/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..f88c69daddafb --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs b/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs new file mode 100644 index 0000000000000..a30396be56126 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6561/trait-fn.rs @@ -0,0 +1,6 @@ +// rustfmt-edition: 2015 + +trait A { + fn f1(#[allow()] u32); + fn f2(#[allow()] u32, #[allow()] u32); +} From 6a1074eb6006ca698c52951ad742cc6451f16884 Mon Sep 17 00:00:00 2001 From: CPunisher <1343316114@qq.com> Date: Mon, 21 Jul 2025 12:10:48 +0800 Subject: [PATCH 18/19] Add test for issue 6607 --- src/tools/rustfmt/tests/source/issue-6607/fn-type.rs | 3 +++ src/tools/rustfmt/tests/target/issue-6607/fn-type.rs | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 src/tools/rustfmt/tests/source/issue-6607/fn-type.rs create mode 100644 src/tools/rustfmt/tests/target/issue-6607/fn-type.rs diff --git a/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs b/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..e85d04d6041e1 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} \ No newline at end of file diff --git a/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs b/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs new file mode 100644 index 0000000000000..7f89d12be8d63 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-6607/fn-type.rs @@ -0,0 +1,3 @@ +struct Foo { + v: fn(#[cfg(false)] i32), +} From 320b5e08c45f948f859c0705f33b5b3a5486dd98 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 16 Aug 2026 19:06:14 -0400 Subject: [PATCH 19/19] core: Make funnel shifts panic only if overflow checks are enabled Change from the existing behavior of panicking unconditionally to panicking only if debug assertions are enabled, wrapping otherwise. This is more consistent with other operations, and gives slightly better codegen [1]. Suggested in the stabilization PR, RUST-161015. [1]: https://rust.godbolt.org/z/Yz81zhbno --- library/core/src/num/uint_macros.rs | 54 +++++++++++++++++----- library/coretests/tests/lib.rs | 1 + library/coretests/tests/num/uint_macros.rs | 14 ++++++ tests/ui/std/overflow-check-ops.rs | 3 ++ 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 1664ce83aef72..6fac5cc11e1e4 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -515,8 +515,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shl` when stable. /// /// # Examples /// @@ -543,21 +548,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_left(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shl(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shl(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shl(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shl(right, n) + } } /// Performs a right funnel shift. @@ -571,8 +586,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shr` when stable. /// /// # Examples /// @@ -599,21 +619,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_right(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shr(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shr(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shr(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shr(right, n) + } } /// Unchecked funnel shift left. diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 35327264ccd77..3d23e7bb7ee24 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -11,6 +11,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(casefold)] +#![feature(cfg_overflow_checks)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(clone_to_uninit)] diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 8189776807915..4dfcb1b8b4688 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -216,17 +216,31 @@ macro_rules! uint_module { } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift left with overflow"] fn test_funnel_shl_overflow() { let _ = <$T>::funnel_shl(A, B, $T::BITS); } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift right with overflow"] fn test_funnel_shr_overflow() { let _ = <$T>::funnel_shr(A, B, $T::BITS); } + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shl_overflow() { + let _ = <$T>::funnel_shl(A, B, $T::BITS); + } + + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shr_overflow() { + let _ = <$T>::funnel_shr(A, B, $T::BITS); + } + #[test] fn test_funnel_shifts_runtime() { for i in 0..$T::BITS - 1 { diff --git a/tests/ui/std/overflow-check-ops.rs b/tests/ui/std/overflow-check-ops.rs index 9f79bb6505753..458c1b121664e 100644 --- a/tests/ui/std/overflow-check-ops.rs +++ b/tests/ui/std/overflow-check-ops.rs @@ -7,6 +7,7 @@ //@[WRAP] compile-flags: -C overflow-checks=false #![feature(cfg_overflow_checks)] +#![feature(funnel_shifts)] use std::hint::black_box as bb; use std::{assert_matches, fmt, panic}; @@ -28,6 +29,8 @@ fn main() { check(|| bb(u32::MAX) * bb(2), u32::MAX << 1, "mul"); check(|| bb(1u32) << bb(32), 1, "shl"); check(|| bb(u32::MAX) >> bb(32), u32::MAX, "shr"); + check(|| bb(1234u32).funnel_shl(4567, bb(32)), 1234, "funnel_shl"); + check(|| bb(1234u32).funnel_shr(4567, bb(32)), 4567, "funnel_shr"); check(|| bb(u32::MAX).pow(bb(2)), 1, "pow"); check(|| bb(u32::MAX).next_power_of_two(), 0, "next_power_of_two");