From 584a7104982f342831c45277c11e6bea6300a28b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 30 Sep 2026 23:40:29 +0200 Subject: [PATCH 01/28] dynamically test for symlink permission --- src/tools/miri/ci/ci.sh | 2 + .../pass-dep/libc/libc-fs-permissions.rs | 2 +- .../pass-dep/libc/libc-fs-with-isolation.rs | 2 +- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 144 ++++++++++-------- .../pass-dep/libc/libc-fstat-non-file.rs | 2 +- src/tools/miri/tests/pass/shims/fs-symlink.rs | 50 ------ src/tools/miri/tests/pass/shims/fs.rs | 34 +++++ src/tools/miri/tests/utils/fs.rs | 37 ++++- 8 files changed, 157 insertions(+), 116 deletions(-) delete mode 100644 src/tools/miri/tests/pass/shims/fs-symlink.rs diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 0cf2c5293c339..52d634ba7c860 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -20,6 +20,8 @@ if ! rustc -vV | grep -q "^host: $HOST_TARGET\$"; then exit 1 fi +export CI=true # just to be sure that it is truly set + endgroup begingroup "Building Miri" diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs index dd79c96e179c8..7d81b56f8846f 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-permissions.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # no libc +//@ignore-target: windows # libc bits exist, but we don't support them //@ignore-host: windows # needs unix PermissionExt //@compile-flags: -Zmiri-disable-isolation //@run-native diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs index 06a8cc7f4879a..d1a0a61a8ea36 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-with-isolation.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # File handling is not implemented yet +//@ignore-target: windows # libc bits exist, but we don't support them //@compile-flags: -Zmiri-isolation-error=warn-nobacktrace //@normalize-stderr-test: "(stat(x)?)" -> "$$STAT" diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index b3f063ef07cba..c1f6a20b75dfc 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # no libc +//@ignore-target: windows # libc bits exist, but we don't support them //@compile-flags: -Zmiri-disable-isolation //@run-native @@ -173,35 +173,38 @@ fn test_statx() { } // Symlink following. - let symlinkpath = utils::prepare("miri_test_libc_statx.link"); - let c_symlinkpath = utils::into_c_string(&symlinkpath); - std::os::unix::fs::symlink(&path, &symlinkpath).unwrap(); - unsafe { - let mut stx = MaybeUninit::::zeroed(); - errno_check(libc::statx( - 999, // dirfd - c_symlinkpath.as_ptr(), - libc::AT_EMPTY_PATH, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - stx.as_mut_ptr(), - )); - let stx = stx.assume_init(); - assert_statx_matches_metadata(&stx, &meta, bytes.len() as u64); - } - unsafe { - let mut stx = MaybeUninit::::zeroed(); - errno_check(libc::statx( - 999, // dirfd - c_symlinkpath.as_ptr(), - libc::AT_SYMLINK_NOFOLLOW, - libc::STATX_BASIC_STATS | libc::STATX_BTIME, - stx.as_mut_ptr(), - )); - let stx = stx.assume_init(); - assert!(stx.stx_mask & libc::STATX_TYPE != 0); - assert_eq!((stx.stx_mode as libc::mode_t) & libc::S_IFMT, libc::S_IFLNK); - assert!(stx.stx_mask & libc::STATX_MODE != 0); - assert_ne!((stx.stx_mode as libc::mode_t) & !libc::S_IFMT, 0); + if utils::have_symlink_permission() { + let symlinkpath = utils::prepare("miri_test_libc_statx.link"); + let c_symlinkpath = utils::into_c_string(&symlinkpath); + std::os::unix::fs::symlink(&path, &symlinkpath).unwrap(); + unsafe { + let mut stx = MaybeUninit::::zeroed(); + errno_check(libc::statx( + 999, // dirfd + c_symlinkpath.as_ptr(), + libc::AT_EMPTY_PATH, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + stx.as_mut_ptr(), + )); + let stx = stx.assume_init(); + assert_statx_matches_metadata(&stx, &meta, bytes.len() as u64); + } + unsafe { + let mut stx = MaybeUninit::::zeroed(); + errno_check(libc::statx( + 999, // dirfd + c_symlinkpath.as_ptr(), + libc::AT_SYMLINK_NOFOLLOW, + libc::STATX_BASIC_STATS | libc::STATX_BTIME, + stx.as_mut_ptr(), + )); + let stx = stx.assume_init(); + assert!(stx.stx_mask & libc::STATX_TYPE != 0); + assert_eq!((stx.stx_mode as libc::mode_t) & libc::S_IFMT, libc::S_IFLNK); + assert!(stx.stx_mask & libc::STATX_MODE != 0); + assert_ne!((stx.stx_mode as libc::mode_t) & !libc::S_IFMT, 0); + } + remove_file(&symlinkpath).unwrap(); } // Relative to a dirfd. @@ -225,7 +228,6 @@ fn test_statx() { errno_check(unsafe { libc::closedir(dirstream) }); remove_file(&path).unwrap(); - remove_file(&symlinkpath).unwrap(); } #[cfg(target_os = "linux")] @@ -328,26 +330,29 @@ fn test_file_open_extra_third_arg() { } fn test_file_open_nofollow() { + // Regular files work like normal. let bytes = b"Hello, World!\n"; let path = utils::prepare_with_content("miri_test_nofollow_not_symlink.txt", bytes); let cpath = utils::into_c_string(path); let fd = errno_result(unsafe { libc::open(cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) }) .unwrap(); + let data = libc_utils::read_exact_array::<5>(fd).unwrap(); + assert!(bytes.starts_with(&data)); errno_check(unsafe { libc::close(fd) }); - let path = utils::prepare_with_content("miri_test_open_nofollow_symlink_target.txt", bytes); - - let symlink_path = utils::prepare("miri_test_open_nofollow_symlink.txt"); - std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); - - let symlink_cpath = utils::into_c_string(symlink_path); - - let err = errno_result(unsafe { - libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) - }) - .unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::ELOOP)); + // But trying to open a symlink errors. + if utils::have_symlink_permission() { + let path = utils::prepare_with_content("miri_test_open_nofollow_symlink_target.txt", bytes); + let symlink_path = utils::prepare("miri_test_open_nofollow_symlink.txt"); + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + let symlink_cpath = utils::into_c_string(symlink_path); + let err = errno_result(unsafe { + libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) + }) + .unwrap_err(); + assert_eq!(err.raw_os_error(), Some(libc::ELOOP)); + } } fn test_dup_stdout_stderr() { @@ -901,32 +906,39 @@ fn test_fstatat() { errno_check(unsafe { libc::fstatat(dirfd, cfilename.as_ptr(), stat.as_mut_ptr(), 0) }); checkstat(unsafe { stat.assume_init_ref() }); - // Symlink following. - let linkname = testdir.join("link"); - std::os::unix::fs::symlink(&absfilename, &linkname).unwrap(); - let mut stat = MaybeUninit::::uninit(); - errno_check(unsafe { libc::fstatat(dirfd, c"link".as_ptr(), stat.as_mut_ptr(), 0) }); - checkstat(unsafe { stat.assume_init_ref() }); - let mut stat = MaybeUninit::::uninit(); - errno_check(unsafe { - libc::fstatat(dirfd, c"link".as_ptr(), stat.as_mut_ptr(), libc::AT_SYMLINK_NOFOLLOW) - }); - let stat = unsafe { stat.assume_init_ref() }; - assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFLNK); // not S_IFREG! - assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); - check_stat_fields(stat); + if utils::have_symlink_permission() { + // Symlink following. + let linkname = testdir.join("link"); + std::os::unix::fs::symlink(&absfilename, &linkname).unwrap(); + let mut stat = MaybeUninit::::uninit(); + errno_check(unsafe { libc::fstatat(dirfd, c"link".as_ptr(), stat.as_mut_ptr(), 0) }); + checkstat(unsafe { stat.assume_init_ref() }); + let mut stat = MaybeUninit::::uninit(); + errno_check(unsafe { + libc::fstatat(dirfd, c"link".as_ptr(), stat.as_mut_ptr(), libc::AT_SYMLINK_NOFOLLOW) + }); + let stat = unsafe { stat.assume_init_ref() }; + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFLNK); // not S_IFREG! + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); + check_stat_fields(stat); + } errno_check(unsafe { libc::closedir(dirstream) }); } fn test_stat() { - // Also make sure we *do* follow symlinks. - let path = utils::prepare_with_content("miri_test_libc_stat.txt", b"hello"); - let symlink_path = utils::prepare("miri_test_libc_lstat_symlink.txt"); - std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + let path = if utils::have_symlink_permission() { + // Also make sure we *do* follow symlinks. + let symlink_path = utils::prepare("miri_test_libc_lstat_symlink.txt"); + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + symlink_path + } else { + // Backup plan: just open the file directly. + path + }; - let cpath = utils::into_c_string(symlink_path); + let cpath = utils::into_c_string(&path); let mut stat = MaybeUninit::::uninit(); errno_check(unsafe { libc::stat(cpath.as_ptr(), stat.as_mut_ptr()) }); @@ -943,6 +955,10 @@ fn test_stat() { } fn test_lstat() { + if !utils::have_symlink_permission() { + return; + } + let path = utils::prepare_with_content("miri_test_libc_lstat.txt", b"hello"); let symlink_path = utils::prepare("miri_test_libc_lstat_symlink.txt"); std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); @@ -1495,6 +1511,10 @@ fn test_pwrite() { } fn test_readlink() { + if !utils::have_symlink_permission() { + return; + } + let bytes = b"Hello, World!\n"; let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes); let expected_path = path.as_os_str().as_bytes(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs index cf848f1903311..097bb5a7699ec 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs @@ -1,4 +1,4 @@ -//@ignore-target: windows # No libc fstat on non-file FDs on Windows +//@ignore-target: windows # libc bits exist, but we don't support them //@compile-flags: -Zmiri-disable-isolation use std::mem::MaybeUninit; diff --git a/src/tools/miri/tests/pass/shims/fs-symlink.rs b/src/tools/miri/tests/pass/shims/fs-symlink.rs deleted file mode 100644 index 30cf234e7a0d1..0000000000000 --- a/src/tools/miri/tests/pass/shims/fs-symlink.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Symlink tests are separate since they don't in general work on a Windows host. -//@ignore-host: windows # creating symlinks requires admin permissions on Windows -//@ignore-target: windows # File handling is not implemented yet -//@compile-flags: -Zmiri-disable-isolation - -use std::fs::{File, read_link, remove_file}; -use std::io::{Read, Result}; -use std::path::Path; - -#[path = "../../utils/mod.rs"] -mod utils; - -fn check_metadata(bytes: &[u8], path: &Path) -> Result<()> { - // Test that the file metadata is correct. - let metadata = path.metadata()?; - // `path` should point to a file. - assert!(metadata.is_file()); - // The size of the file must be equal to the number of written bytes. - assert_eq!(bytes.len() as u64, metadata.len()); - Ok(()) -} - -fn main() { - let bytes = b"Hello, World!\n"; - let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes); - let symlink_path = utils::prepare("miri_test_fs_symlink.txt"); - - // Creating a symbolic link should succeed. - #[cfg(unix)] - std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); - #[cfg(windows)] - std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap(); - // Test that the symbolic link has the same contents as the file. - let mut symlink_file = File::open(&symlink_path).unwrap(); - let mut contents = Vec::new(); - symlink_file.read_to_end(&mut contents).unwrap(); - assert_eq!(bytes, contents.as_slice()); - - // Test that metadata of a symbolic link (i.e., the file it points to) is correct. - check_metadata(bytes, &symlink_path).unwrap(); - // Test that the metadata of a symbolic link is correct when not following it. - assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); - // Check that we can follow the link. - assert_eq!(read_link(&symlink_path).unwrap(), path); - // Removing symbolic link should succeed. - remove_file(&symlink_path).unwrap(); - - // Removing file should succeed. - remove_file(&path).unwrap(); -} diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 1317c08376467..7466a534b0ca4 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -52,6 +52,7 @@ fn main() { test_canonicalize(); #[cfg(not(target_os = "solaris"))] // does not have flock test_flock(); + test_symlink(); test_hard_link(); test_readv_writev(); @@ -557,6 +558,39 @@ fn test_preadv_pwritev() { assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]); } +fn test_symlink() { + if !utils::have_symlink_permission() { + return; + } + + let bytes = b"Hello, World!\n"; + let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes); + let symlink_path = utils::prepare("miri_test_fs_symlink.txt"); + + // Creating a symbolic link should succeed. + #[cfg(unix)] + std::os::unix::fs::symlink(&path, &symlink_path).unwrap(); + #[cfg(windows)] + std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap(); + // Test that the symbolic link has the same contents as the file. + let mut symlink_file = File::open(&symlink_path).unwrap(); + let mut contents = Vec::new(); + symlink_file.read_to_end(&mut contents).unwrap(); + assert_eq!(bytes, contents.as_slice()); + + // Test that metadata of a symbolic link (i.e., the file it points to) is correct. + check_metadata(bytes, &symlink_path).unwrap(); + // Test that the metadata of a symbolic link is correct when not following it. + assert!(symlink_path.symlink_metadata().unwrap().file_type().is_symlink()); + // Check that we can follow the link. + assert_eq!(fs::read_link(&symlink_path).unwrap(), path); + // Removing symbolic link should succeed. + remove_file(&symlink_path).unwrap(); + + // Removing file should succeed. + remove_file(&path).unwrap(); +} + fn test_hard_link() { let source = utils::prepare_with_content("miri_test_fs_hard_link_source.txt", b"hello"); let link = utils::prepare("miri_test_fs_hard_link_link.txt"); diff --git a/src/tools/miri/tests/utils/fs.rs b/src/tools/miri/tests/utils/fs.rs index afcd66dd16e58..9661b55ba43a8 100644 --- a/src/tools/miri/tests/utils/fs.rs +++ b/src/tools/miri/tests/utils/fs.rs @@ -1,6 +1,6 @@ use std::ffi::{CStr, OsString}; use std::path::PathBuf; -use std::{fs, io}; +use std::{env, fs, io}; use super::{into_c_string, miri_extern}; @@ -52,3 +52,38 @@ pub fn prepare_dir(dirname: &str) -> PathBuf { fs::remove_dir_all(&path).ok(); path } + +/// Windows makes things difficult by refusing create symlinks per default. GHA is configured to +/// allow them, but when people run the tests on their systems we'd prefer them to pass without +/// special setup. So we try to detect whether symlinks are working. To make things extra fun, this +/// can happen even if we *think* we are on Unix since the host could still be Windows. +pub fn have_symlink_permission() -> bool { + use std::sync::LazyLock; + + static HAVE_SYMLINK_PERMISSION: LazyLock = LazyLock::new(|| { + // Never skip any tests on CI. + if env::var_os("CI").is_some() { + return true; + } + + #[cfg(unix)] + use std::os::unix::fs::symlink as symlink_file; + #[cfg(windows)] + use std::os::windows::fs::symlink_file; + + let link = prepare("miri_have_symlink_permission_check_file"); + if symlink_file(r"nonexisting_target", &link).is_ok() { + // Looking pretty good. + fs::remove_file(link).unwrap(); + return true; + } + // Looking bad. But just to confirm, could we create a normal file? + if fs::write(&link, &[]).is_ok() { + // Normal file works, symlink did not -- looks like the Windows issue. + fs::remove_file(link).unwrap(); + return false; + } + panic!("unable to create files in tempdir"); + }); + *HAVE_SYMLINK_PERMISSION +} From cef096d8b81d8e887713430f4d211c43e165729d Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:38:30 +0200 Subject: [PATCH 02/28] Add native FreeBSD runner to CI --- src/tools/miri/.github/workflows/ci.yml | 25 ++++++++- src/tools/miri/ci/ci.sh | 9 +++- src/tools/miri/ci/vm-prepare.sh | 53 +++++++++++++++++++ src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 22 ++++++-- .../miri/tests/pass-dep/libc/libc-pipe.rs | 22 ++++++-- .../miri/tests/pass/shims/env/current_exe.rs | 1 + 6 files changed, 121 insertions(+), 11 deletions(-) create mode 100755 src/tools/miri/ci/vm-prepare.sh diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 8befdc5db3331..d21198cc60090 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -107,6 +107,27 @@ jobs: - name: clippy run: ./miri clippy -- -D warnings + test-vm-freebsd: + name: test (x86_64-unknown-freebsd) + env: + HOST_TARGET: x86_64-unknown-freebsd + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: vmactions/freebsd-vm@v1 + with: + # Use FreeBSD 15.x + release: "15" + # We don't want to copy the modified source tree back to the host + # file system as this would just be a waste of time. + copyback: false + envs: HOST_TARGET + prepare: ./ci/vm-prepare.sh + run: | + # Source the cargo environment. + . "$HOME/.cargo/env" + ./ci/ci.sh + style: name: style checks runs-on: ubuntu-latest @@ -194,7 +215,7 @@ jobs: # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # And they should be added below in `cron-fail-notify` as well. conclusion: - needs: [test, style, bootstrap, coverage, priroda] + needs: [test, test-vm-freebsd, style, bootstrap, coverage, priroda] # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run @@ -278,7 +299,7 @@ jobs: cron-fail-notify: name: cronjob failure notification runs-on: ubuntu-latest - needs: [test, style, bootstrap, coverage, priroda] + needs: [test, test-vm-freebsd, style, bootstrap, coverage, priroda] if: ${{ github.event_name == 'schedule' && failure() }} steps: # Send a Zulip notification diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 52d634ba7c860..4fa2d4d91e094 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -1,4 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash +# Since FreeBSD stores user-installed binaries in `/usr/local/bin` we +# cannot use the `/bin/bash` shebang and instead use the the bash from PATH. + set -eu function begingroup { @@ -202,6 +205,10 @@ case $HOST_TARGET in # Extra tier 1 MANY_SEEDS=64 TEST_TARGET=i686-unknown-linux-gnu run_tests ;; + x86_64-unknown-freebsd) + # Host + MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests + ;; *) echo "FATAL: unknown host target: $HOST_TARGET" exit 1 diff --git a/src/tools/miri/ci/vm-prepare.sh b/src/tools/miri/ci/vm-prepare.sh new file mode 100755 index 0000000000000..12a8e0c67a82b --- /dev/null +++ b/src/tools/miri/ci/vm-prepare.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Prepare an environment in vmactions containers, which don't have many tools +# installed by default. +# +# This file is inspired from . +# It duplicates a lot of the logic in `.github/workflows/setup/action.yml`. + +set -eux + +uname -a +case $HOST_TARGET in + x86_64-unknown-freebsd) deps="pkg install -y libnghttp2 curl bash python" ;; + *) + echo "FATAL: unknown VM host target: $HOST_TARGET" + exit 1 + ;; +esac + +# Installs have been flaky so give them a retry. +count=0 +success=false +while [ $count -lt 3 ]; do + $deps && success=true || true + [ $success = true ] && break + sleep 3s + count=$(( count + 1)) +done + +if [ "$success" != true ]; then + echo "failed to install dependencies" + exit 1 +fi + +# Install rustup and latest stable toolchain. +curl --proto '=https' --tlsv1.2 -sSf --retry 5 https://sh.rustup.rs | sh -s -- \ + --profile minimal \ + --default-toolchain stable \ + --target "$HOST_TARGET" \ + -y + +# Source the cargo environment. +. "$HOME/.cargo/env" + +# Install the tools we need. +cargo install --locked -f rustup-toolchain-install-master hyperfine + +# Install "master" toolchain. +./miri toolchain + +# Show Rust version (miri toolchain). +rustup show +rustc -Vv +cargo -V diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index c1f6a20b75dfc..1745a75830a3f 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -351,7 +351,10 @@ fn test_file_open_nofollow() { libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) }) .unwrap_err(); - assert_eq!(err.raw_os_error(), Some(libc::ELOOP)); + // FreeBSD returns EMLINK when encountering a symlink on the final path segment with O_NOFOLLOW + // while POSIX specifies returning ELOOP. Since this test is run on both native FreeBSD and native + // Linux hosts, we just assert that its either of those error codes. + assert!([libc::ELOOP, libc::EMLINK].contains(&err.raw_os_error().unwrap())); } } @@ -542,9 +545,9 @@ fn test_posix_mkstemp() { drop(file); remove_file(path).unwrap(); - // Test invalid inputs. We skip this on native macOS since macOS apparently does - // not bother to validate inputs. - if !cfg!(all(not(miri), target_vendor = "apple")) { + // Test invalid inputs. We skip this on native macOS and FreeBSD since those apparently + // don't bother to validate inputs. + if cfg!(miri) || !cfg!(any(target_vendor = "apple", target_os = "freebsd")) { let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"]; for t in invalid_templates { let ptr = CString::new(t).unwrap().into_raw(); @@ -687,6 +690,17 @@ fn test_posix_fallocate>( // Allocate to a bigger size from offset 0 let mut res = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) }; + + if cfg!(all(not(miri), target_os = "freebsd")) { + // On native FreeBSD with a ZFS file system `posix_fallocate` isn't implemented + // and thus always returns EOPNOTSUPP. + if res == libc::EOPNOTSUPP { + // We don't have to execute the remaining tests since we'll always get EOPNOTSUPP. + // Thus, we exit early here. + return; + } + } + assert_eq!(res, 0); assert_eq!(file.metadata().unwrap().len(), 10); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs index e6d833b58defc..c42a850088760 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs @@ -115,6 +115,14 @@ fn test_pipe2() { /// Basic test for pipe fcntl's F_SETFL and F_GETFL flag. fn test_pipe_setfl_getfl() { + if cfg!(all(not(miri), target_os = "freebsd")) { + // FIXME: Pipes are bidirectional on FreeBSD but Miri's shim implements them as + // unidirectional channel. Until we implement FreeBSD's pipe extension in Miri + // we skip this test. + // See + return; + } + // Initialise pipe fds. let mut fds = [-1, -1]; errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); @@ -184,10 +192,16 @@ fn test_pipe_fcntl_threaded() { errno_check(unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) }); // Check the new flag value while the main thread is still blocked on fds[0]. - assert_eq!( - errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(), - libc::O_NONBLOCK - ); + let flags = errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(); + + if cfg!(target_os = "freebsd") { + // FreeBSD also reports readable/writable flags in F_GETFL. + // FIXME: check the exact flags, once Miri emulates them correctly. + // See . + assert!(flags & libc::O_NONBLOCK != 0); + } else { + assert_eq!(flags, libc::O_NONBLOCK) + } // The write below will unblock the `read` in main thread: even though // the socket is now "non-blocking", the shim needs to deal correctly diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 898a42b72d134..08eb1e949dfb7 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,4 +1,5 @@ //@only-on-host: the Linux std implementation opens /proc/self/exe, which doesn't work cross-target +//@ignore-target: freebsd # On FreeBSD the standard library uses `sysctl` for this, which is not shimmed //@compile-flags: -Zmiri-disable-isolation use std::env; From 3158ec2b73b405dcc1feee34cbe5b7955fd3c662 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 1 Oct 2026 13:52:37 +1000 Subject: [PATCH 03/28] Store `Span` instead of `SpanData` As the comments explain, `Span` does not implement `Send` and so Miri had to store `SpanData` (which does implement `Send`) instead, so certain other types are `Send`. At least, that used to be true. When the serial rustc front-end was removed `Span` became `Send`. So now Miri can store `Span`, which is much more standard than storing `SpanData`. --- .../stacked_borrows/diagnostics.rs | 22 ++++++++-------- .../tree_borrows/diagnostics.rs | 13 +++------- src/tools/miri/src/concurrency/data_race.rs | 4 +-- .../miri/src/concurrency/vector_clock.rs | 7 +---- src/tools/miri/src/diagnostics.rs | 26 +++++++++---------- src/tools/miri/src/machine.rs | 12 +++------ src/tools/miri/src/shims/foreign_items.rs | 8 +++--- src/tools/miri/src/shims/sig.rs | 2 +- 8 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 8f6a508e605a0..3dd8c710a0274 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -2,7 +2,7 @@ use std::fmt; use rustc_abi::Size; use rustc_data_structures::fx::FxHashSet; -use rustc_span::{Span, SpanData}; +use rustc_span::Span; use smallvec::SmallVec; use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind}; @@ -33,12 +33,12 @@ struct Creation { } impl Creation { - fn generate_diagnostic(&self) -> (String, SpanData) { + fn generate_diagnostic(&self) -> (String, Span) { let tag = self.retag.new_tag; if let Some(perm) = self.retag.permission { ( format!("{tag:?} was created by a {perm:?} retag at offsets {}", self.retag.range), - self.span.data(), + self.span, ) } else { assert!(self.retag.range.size == Size::ZERO); @@ -47,7 +47,7 @@ impl Creation { "{tag:?} would have been created here, but this is a zero-size retag ({}) so the tag in question does not exist anywhere", self.retag.range, ), - self.span.data(), + self.span, ) } } @@ -68,7 +68,7 @@ enum InvalidationCause { } impl Invalidation { - fn generate_diagnostic(&self) -> (String, SpanData) { + fn generate_diagnostic(&self) -> (String, Span) { let message = if matches!( self.cause, InvalidationCause::Retag(_, RetagInfo { cause: RetagCause::FnEntry, .. }) @@ -85,7 +85,7 @@ impl Invalidation { self.tag, self.range, self.cause ) }; - (message, self.span.data()) + (message, self.span) } } @@ -107,9 +107,9 @@ struct Protection { #[derive(Clone)] pub struct TagHistory { - pub created: (String, SpanData), - pub invalidated: Option<(String, SpanData)>, - pub protected: Option<(String, SpanData)>, + pub created: (String, Span), + pub invalidated: Option<(String, Span)>, + pub protected: Option<(String, Span)>, } pub struct DiagnosticCxBuilder<'ecx, 'tcx> { @@ -342,7 +342,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { "{tag:?} was created here, as the root tag for {}", self.history.id ), - self.history.root.1.data(), + self.history.root.1, )) } else { None @@ -364,7 +364,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { }) .map(|protection| { let protected_tag = protection.tag; - (format!("{protected_tag:?} is this argument"), protection.span.data()) + (format!("{protected_tag:?} is this argument"), protection.span) }); Some(TagHistory { created, invalidated, protected }) diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index 9d4c446b14f85..79c182f5b02a2 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -2,7 +2,7 @@ use std::fmt; use std::ops::Range; use rustc_data_structures::fx::FxHashMap; -use rustc_span::{Span, SpanData}; +use rustc_span::Span; use crate::borrow_tracker::tree_borrows::perms::{PermTransition, Permission}; use crate::borrow_tracker::tree_borrows::tree::LocationState; @@ -115,12 +115,9 @@ pub struct History { } /// History formatted for use by `src/diagnostics.rs`. -/// -/// NOTE: needs to be `Send` because of a bound on `MachineStopType`, hence -/// the use of `SpanData` rather than `Span`. #[derive(Debug, Clone, Default)] pub struct HistoryData { - pub events: Vec<(Option, String)>, // includes creation + pub events: Vec<(Option, String)>, // includes creation } impl History { @@ -132,8 +129,6 @@ impl History { impl HistoryData { // Format events from `new_history` into those recorded by `self`. - // - // NOTE: also converts `Span` to `SpanData`. fn extend(&mut self, new_history: History, tag_name: &'static str, show_initial_state: bool) { let History { tag, created, events } = new_history; let this = format!("the {tag_name} tag {tag:?}"); @@ -143,7 +138,7 @@ impl HistoryData { maybe_msg_initial_state = if show_initial_state { &msg_initial_state } else { "" }, ); - self.events.push((Some(created.0.data()), msg_creation)); + self.events.push((Some(created.0), msg_creation)); for &Event { transition, is_foreign, @@ -161,7 +156,7 @@ impl HistoryData { None => format!("on every location previously accessed by this tag"), }; self.events.push(( - Some(span.data()), + Some(span), format!( "{this} later transitioned to {endpoint} due to a {access} {access_range_text}", endpoint = transition.endpoint() diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index c086f71c2dcaa..f9b2a0eece9b8 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1184,12 +1184,12 @@ impl VClockAlloc { op1: RacingOp { action: other_access.description(None, other_size), thread_info: other_thread_info, - span: other_clock.as_slice()[other_thread.index()].span_data(), + span: other_clock.as_slice()[other_thread.index()].span, }, op2: RacingOp { action: access.description(ty, other_size.map(|_| access_size)), thread_info: active_thread_info, - span: active_clocks.clock.as_slice()[active_index.index()].span_data(), + span: active_clocks.clock.as_slice()[active_index.index()].span, }, }))? } diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs index d7ea3b0c6ce3d..723dfd2965152 100644 --- a/src/tools/miri/src/concurrency/vector_clock.rs +++ b/src/tools/miri/src/concurrency/vector_clock.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::ops::{Index, Shr}; use rustc_index::Idx; -use rustc_span::{DUMMY_SP, Span, SpanData}; +use rustc_span::{DUMMY_SP, Span}; use smallvec::SmallVec; use super::data_race::NaReadType; @@ -103,11 +103,6 @@ impl VTimestamp { pub(super) fn set_read_type(&mut self, read_type: NaReadType) { self.time_and_read_type = Self::encode_time_and_read_type(self.time(), read_type); } - - #[inline] - pub(super) fn span_data(&self) -> SpanData { - self.span.data() - } } impl PartialEq for VTimestamp { diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 793c7d6f0401b..7c5b38e9a4891 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use rustc_abi::{Align, Size}; use rustc_data_structures::fx::{FxBuildHasher, FxHashSet}; use rustc_errors::{Diag, Level}; -use rustc_span::{DUMMY_SP, Span, SpanData, Symbol}; +use rustc_span::{DUMMY_SP, Span, Symbol}; use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory; use crate::borrow_tracker::tree_borrows::diagnostics as tree_diagnostics; @@ -41,14 +41,14 @@ pub enum TerminationInfo { LocalDeadlock, MultipleSymbolDefinitions { link_name: Symbol, - first: SpanData, + first: Span, first_crate: Symbol, - second: SpanData, + second: Span, second_crate: Symbol, }, SymbolShimClashing { link_name: Symbol, - span: SpanData, + span: Span, }, DataRace { involves_non_atomic: bool, @@ -64,7 +64,7 @@ pub enum TerminationInfo { pub struct RacingOp { pub action: String, pub thread_info: String, - pub span: SpanData, + pub span: Span, } impl fmt::Display for TerminationInfo { @@ -554,8 +554,8 @@ fn report_msg<'tcx>( diag_level: DiagLevel, title: String, span_msg: Vec, - notes: Vec<(Option, String)>, - helps: Vec<(Option, String)>, + notes: Vec<(Option, String)>, + helps: Vec<(Option, String)>, stacktrace: &[FrameInfo<'tcx>], thread: Option, machine: &MiriMachine<'tcx>, @@ -581,16 +581,16 @@ fn report_msg<'tcx>( } // Show note and help messages. - for (span_data, note) in notes { - if let Some(span_data) = span_data { - err.span_note(span_data.span(), note); + for (span, note) in notes { + if let Some(span) = span { + err.span_note(span, note); } else { err.note(note); } } - for (span_data, help) in helps { - if let Some(span_data) = span_data { - err.span_help(span_data.span(), help); + for (span, help) in helps { + if let Some(span) = span { + err.span_help(span, help); } else { err.help(help); } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 65eb6e10d4518..cba1df6b1d7d2 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -30,7 +30,7 @@ use rustc_middle::ty::layout::{ use rustc_middle::ty::{self, AtomicOrdering, Instance, Ty, TyCtxt}; use rustc_session::config::InliningThreshold; use rustc_span::def_id::{CrateNum, DefId}; -use rustc_span::{Span, SpanData, Symbol}; +use rustc_span::{Span, Symbol}; use rustc_symbol_mangling::mangle_internal_symbol; use rustc_target::callconv::FnAbi; use rustc_target::spec::{Arch, Os}; @@ -950,19 +950,15 @@ impl<'tcx> MiriMachine<'tcx> { Align::from_bytes(self.page_size).unwrap() } - pub(crate) fn allocated_span(&self, alloc_id: AllocId) -> Option { - self.allocation_spans - .borrow() - .get(&alloc_id) - .map(|(allocated, _deallocated)| allocated.data()) + pub(crate) fn allocated_span(&self, alloc_id: AllocId) -> Option { + self.allocation_spans.borrow().get(&alloc_id).map(|(allocated, _deallocated)| *allocated) } - pub(crate) fn deallocated_span(&self, alloc_id: AllocId) -> Option { + pub(crate) fn deallocated_span(&self, alloc_id: AllocId) -> Option { self.allocation_spans .borrow() .get(&alloc_id) .and_then(|(_allocated, deallocated)| *deallocated) - .map(Span::data) } fn init_allocation( diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 66d65ead7c7bd..224cde622d17e 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -176,9 +176,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_machine_stop!( TerminationInfo::MultipleSymbolDefinitions { link_name, - first: original_span.data(), + first: original_span, first_crate: tcx.crate_name(original.cnum), - second: span.data(), + second: span, second_crate: tcx.crate_name(cnum), } ); @@ -186,9 +186,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_machine_stop!( TerminationInfo::MultipleSymbolDefinitions { link_name, - first: span.data(), + first: span, first_crate: tcx.crate_name(cnum), - second: original_span.data(), + second: original_span, second_crate: tcx.crate_name(original.cnum), } ); diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 853f7381ccdf5..091f1f80d82f1 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -284,7 +284,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_machine_stop!(TerminationInfo::SymbolShimClashing { link_name, - span: this.tcx.def_span(instance.def_id()).data(), + span: this.tcx.def_span(instance.def_id()), }) } interp_ok(()) From fdc7532b310f64d44fd35b0c48f6fbcbde65e92d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 1 Oct 2026 12:07:09 +0200 Subject: [PATCH 04/28] Prepare for merging from rust-lang/rust This updates the rust-version file to 837b18aae097503892d7aab6c7ddce332a991a21. --- 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 eb8c4fb20edfe..716d825e606bc 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -5c543b0b8c73c7b72bc8284ced4fb22ead15734d +837b18aae097503892d7aab6c7ddce332a991a21 From a8a811140a3fe3066859a8c2979142005b381641 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 1 Oct 2026 11:33:12 +0200 Subject: [PATCH 05/28] Revert "temporarily disable broken test on freebsd" This reverts commit ab956667801480a745969fedced5dd5c92372a49. --- .../tests/pass/shims/available-parallelism-miri-num-cpus.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/miri/tests/pass/shims/available-parallelism-miri-num-cpus.rs b/src/tools/miri/tests/pass/shims/available-parallelism-miri-num-cpus.rs index fa83de8bc127e..0d96bc2f5e737 100644 --- a/src/tools/miri/tests/pass/shims/available-parallelism-miri-num-cpus.rs +++ b/src/tools/miri/tests/pass/shims/available-parallelism-miri-num-cpus.rs @@ -1,6 +1,4 @@ //@compile-flags: -Zmiri-num-cpus=1024 -// FIXME: Disabled on FreeBSD due to -//@ignore-target: freebsd use std::num::NonZero; use std::thread::available_parallelism; From d4c82e70113dd1c14ae56930e744fd838a8217d9 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 1 Oct 2026 12:26:55 +0200 Subject: [PATCH 06/28] freebsd: add FIXME for st_mode on a pipe --- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 1745a75830a3f..5e3f1ffc0c491 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -866,7 +866,13 @@ fn test_fstat() { let stat = stat.assume_init_ref(); assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO); - assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); + if cfg!(target_os = "freebsd") { + // FIXME: Seems like FreeBSD does not set permissions for non-file-backed FDs. + // Miri currently gets this wrong. + // assert_eq!(stat.st_mode & !libc::S_IFMT, 0, "no permission should be set"); + } else { + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); + } assert_eq!(stat.st_size, 0); errno_check(libc::close(fds[0])); From 9cab556c5b6c2a59f4b54d8cefacfa36e0350a1c Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:01:40 +0200 Subject: [PATCH 07/28] Handle ENETUNREACH in socket tests which connect to blackhole addresses --- .../libc/libc-socket-no-blocking-epoll.rs | 2 +- .../pass-dep/libc/libc-socket-no-blocking.rs | 30 ++++++++++++++++--- src/tools/miri/tests/pass/shims/socket.rs | 5 ++++ 3 files changed, 32 insertions(+), 5 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 563c2a3842a19..628f7f9493567 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 @@ -148,7 +148,7 @@ fn test_connect_nonblock_err() { let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); // Since the address is not bound, the `connect` can never succeed // immediately and thus it always "fails" with EINPROGRESS. - assert!(err.kind() == ErrorKind::InProgress); + assert_eq!(err.kind(), ErrorKind::InProgress); // Add interest for client socket. epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | libc::EPOLLERR).unwrap(); 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 0325fe06dab82..bea3b984beb3a 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 @@ -678,9 +678,14 @@ fn test_getsockname_ipv4_connect_nonblock() { let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + if err.kind() == ErrorKind::NetworkUnreachable { + // Since blackhole addresses are not part of the local address range + // they might not be reachable without internet connectivity. + return; + } // Since we're connecting to a blackhole address, the `connect` can // never succeed immediately and thus it always "fails" with EINPROGRESS. - assert!(err.kind() == ErrorKind::InProgress); + assert_eq!(err.kind(), ErrorKind::InProgress); let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { libc::getsockname(client_sockfd, storage, len) @@ -782,9 +787,14 @@ fn test_getpeername_ipv4_nonblock_no_peer() { let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + if err.kind() == ErrorKind::NetworkUnreachable { + // Since blackhole addresses are not part of the local address range + // they might not be reachable without internet connectivity. + return; + } // Since we're connecting to a blackhole address, the `connect` can // never succeed immediately and thus it always "fails" with EINPROGRESS. - assert!(err.kind() == ErrorKind::InProgress); + assert_eq!(err.kind(), ErrorKind::InProgress); // There should be no error during async connection. let so_error = @@ -823,8 +833,14 @@ fn test_listen_connecting() { // a zero port. let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); - // Non-blocking connect should fail with EINPROGRESS. let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + if err.kind() == ErrorKind::NetworkUnreachable { + // Since blackhole addresses are not part of the local address range + // they might not be reachable without internet connectivity. + return; + } + // Since we're connecting to a blackhole address, the `connect` can + // never succeed immediately and thus it always "fails" with EINPROGRESS. assert_eq!(err.kind(), ErrorKind::InProgress); // There should be no error during async connection. @@ -861,8 +877,14 @@ fn test_connect_connecting() { // a zero port. let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); - // Non-blocking connect should fail with EINPROGRESS. let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + if err.kind() == ErrorKind::NetworkUnreachable { + // Since blackhole addresses are not part of the local address range + // they might not be reachable without internet connectivity. + return; + } + // Since we're connecting to a blackhole address, the `connect` can + // never succeed immediately and thus it always "fails" with EINPROGRESS. assert_eq!(err.kind(), ErrorKind::InProgress); // There should be no error during async connection. diff --git a/src/tools/miri/tests/pass/shims/socket.rs b/src/tools/miri/tests/pass/shims/socket.rs index 1891e770a017f..91e75d32891b9 100644 --- a/src/tools/miri/tests/pass/shims/socket.rs +++ b/src/tools/miri/tests/pass/shims/socket.rs @@ -73,6 +73,11 @@ fn test_connect_with_timeout_error() { let before = Instant::now(); let err = TcpStream::connect_timeout(&address, timeout).unwrap_err(); + if err.kind() == ErrorKind::NetworkUnreachable { + // Since blackhole addresses are not part of the local address range + // they might not be reachable without internet connectivity. + return; + } assert_eq!(err.kind(), ErrorKind::TimedOut); assert!(before.elapsed() >= timeout); } From 545a884e44b98de95cbbbc62c8d15eddf36edf58 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 1 Oct 2026 18:54:19 +0200 Subject: [PATCH 08/28] remove some unnecessary @only-on-host --- src/tools/miri/tests/native-lib/fail/function_not_in_so.rs | 1 - src/tools/miri/tests/native-lib/fail/invalid_retval.rs | 1 - src/tools/miri/tests/native-lib/fail/private_function.rs | 1 - src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/src/tools/miri/tests/native-lib/fail/function_not_in_so.rs b/src/tools/miri/tests/native-lib/fail/function_not_in_so.rs index a7df3e6118725..11a043156c07d 100644 --- a/src/tools/miri/tests/native-lib/fail/function_not_in_so.rs +++ b/src/tools/miri/tests/native-lib/fail/function_not_in_so.rs @@ -1,6 +1,5 @@ // Only works on Unix targets //@ignore-target: windows wasm -//@only-on-host //@normalize-stderr-test: "OS `.*`" -> "$$OS" extern "C" { diff --git a/src/tools/miri/tests/native-lib/fail/invalid_retval.rs b/src/tools/miri/tests/native-lib/fail/invalid_retval.rs index e4bcbeb10926a..434309bfd7fbb 100644 --- a/src/tools/miri/tests/native-lib/fail/invalid_retval.rs +++ b/src/tools/miri/tests/native-lib/fail/invalid_retval.rs @@ -1,6 +1,5 @@ // Only works on Unix targets //@ignore-target: windows wasm -//@only-on-host //@normalize-stderr-test: "OS `.*`" -> "$$OS" extern "C" { diff --git a/src/tools/miri/tests/native-lib/fail/private_function.rs b/src/tools/miri/tests/native-lib/fail/private_function.rs index e54880e97dabc..40c579645a8c1 100644 --- a/src/tools/miri/tests/native-lib/fail/private_function.rs +++ b/src/tools/miri/tests/native-lib/fail/private_function.rs @@ -1,6 +1,5 @@ // Only works on Unix targets //@ignore-target: windows wasm -//@only-on-host //@normalize-stderr-test: "OS `.*`" -> "$$OS" extern "C" { diff --git a/src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs b/src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs index 27ae6c477a841..e6ba056a0af2e 100644 --- a/src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs +++ b/src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs @@ -1,6 +1,5 @@ // Only works on Unix targets //@ignore-target: windows wasm -//@only-on-host #![allow(improper_ctypes)] From 4bf4c9478c083ec1ee3b0a375410cf545bf31eec Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 1 Oct 2026 19:14:29 +0200 Subject: [PATCH 09/28] make Linux current_exe work on non-Linux hosts --- src/tools/miri/src/shims/unix/fs.rs | 24 +++++++++++++------ .../miri/tests/pass/shims/env/current_exe.rs | 3 +-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 1b9133785ed4a..0bf28103d102f 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -15,7 +15,7 @@ use rustc_target::spec::Os; use self::shims::time::system_time_to_duration; use crate::shims::FdId; use crate::shims::files::{DirHandle, FdNum, FileHandle}; -use crate::shims::os_str::bytes_to_os_str; +use crate::shims::os_str::{PathConversion, bytes_to_os_str}; use crate::shims::sig::Varargs; use crate::shims::unix::fd::{EvalContextExt as _, FlockOp, UnixFileDescription}; use crate::*; @@ -1711,7 +1711,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, i64> { let this = self.eval_context_mut(); - let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?; + let pathname = this.read_os_str_from_c_str(this.read_pointer(pathname_op)?)?; let buf = this.read_pointer(buf_op)?; let bufsize = this.read_target_usize(bufsize_op)?; @@ -1722,16 +1722,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(-1); } - let result = std::fs::read_link(pathname); + // On Linux, special case `/proc/self/exe` so that `std::env::current_exe` works + // in cross-execution. + let result = if matches!(this.tcx.sess.target.os, Os::Linux) + && pathname.to_str() == Some("/proc/self/exe") + { + std::env::current_exe() + } else { + // We read `pathname` as `OsStr` above so we could do the /proc/self/exe check. + // But now we need a (host) path. + let pathname = this.convert_path(Cow::Borrowed(pathname), PathConversion::TargetToHost); + std::fs::read_link(pathname) + }; + match result { Ok(resolved) => { // 'readlink' truncates the resolved path if the provided buffer is not large // enough, and does *not* add a null terminator. That means we cannot use the usual // `write_path_to_c_str` and have to re-implement parts of it ourselves. - let resolved = this.convert_path( - Cow::Borrowed(resolved.as_ref()), - crate::shims::os_str::PathConversion::HostToTarget, - ); + let resolved = this + .convert_path(Cow::Borrowed(resolved.as_ref()), PathConversion::HostToTarget); let mut path_bytes = resolved.as_encoded_bytes(); let bufsize: usize = bufsize.try_into().unwrap(); if path_bytes.len() > bufsize { diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 08eb1e949dfb7..3c8cf8bcceebd 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,5 +1,4 @@ -//@only-on-host: the Linux std implementation opens /proc/self/exe, which doesn't work cross-target -//@ignore-target: freebsd # On FreeBSD the standard library uses `sysctl` for this, which is not shimmed +//@ignore-target: freebsd illumos solaris # we don't have the shims this needs here //@compile-flags: -Zmiri-disable-isolation use std::env; From da9fe25387b15599d714a6c93b38c49dc914cc2b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 2 Oct 2026 08:05:47 +0200 Subject: [PATCH 10/28] getaddrinfo test: ensure we see both IPv4 and IPv6 --- src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs index bb34c7de73a7f..a53f35ad197a1 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-address.rs @@ -34,7 +34,8 @@ fn test_getaddrinfo_freeaddrinfo() { )); } let start = res; - let mut addr_count = 0; + let mut addr_count_v4 = 0; + let mut addr_count_v6 = 0; loop { unsafe { @@ -43,9 +44,9 @@ fn test_getaddrinfo_freeaddrinfo() { break; }; - addr_count += 1; match (*cur).ai_family as libc::c_int { libc::AF_INET => { + addr_count_v4 += 1; let (_, addr) = net::sockname_ipv4(|storage, len| { *(storage as *mut libc::sockaddr_in) = *cur.ai_addr.cast(); *len = (*res).ai_addrlen; @@ -59,6 +60,7 @@ fn test_getaddrinfo_freeaddrinfo() { assert_eq!(localhost_ipv4.sin_addr.s_addr, addr.sin_addr.s_addr); } libc::AF_INET6 => { + addr_count_v6 += 1; let (_, addr) = net::sockname_ipv6(|storage, len| { *(storage as *mut libc::sockaddr_in6) = *cur.ai_addr.cast(); *len = (*res).ai_addrlen; @@ -81,7 +83,7 @@ fn test_getaddrinfo_freeaddrinfo() { } // We expect an IPv4 and an IPv6 address. - assert!(addr_count == 2); + assert!(addr_count_v4 == 1 && addr_count_v6 == 1); unsafe { libc::freeaddrinfo(start.cast()); From 422a96488a92ff006110cddcafc85dabbc5f94f3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 2 Oct 2026 08:49:20 +0200 Subject: [PATCH 11/28] bump dependencies --- src/tools/miri/Cargo.lock | 222 +++++++++++++-------------- src/tools/miri/cargo-miri/Cargo.lock | 72 ++++----- src/tools/miri/priroda/Cargo.lock | 162 +++++++++---------- src/tools/miri/tests/deps/Cargo.lock | 65 ++++---- 4 files changed, 234 insertions(+), 287 deletions(-) diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 8cffd4defac3d..5037ac5b2c217 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ "cipher", "cpubits", @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bstr" @@ -105,9 +105,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "camino" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" dependencies = [ "serde_core", ] @@ -151,14 +151,14 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] name = "cc" -version = "1.4.4" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" dependencies = [ "find-msvc-tools", "jobserver", @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -220,18 +220,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstyle", "clap_lex", @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cmake" @@ -259,7 +259,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -317,9 +317,9 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "console" -version = "0.16.4" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" dependencies = [ "encode_unicode", "libc", @@ -335,27 +335,27 @@ checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -368,9 +368,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.199" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824894a4a85dca76d4c95c2b9098c036f5a29f627b30c12780774f6654e60974" +checksum = "13f6de320895f42e6e081abb5c7983bedcf0b6d0ff9323de0d33f620c8ac1199" dependencies = [ "cc", "cxx-build", @@ -383,9 +383,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.199" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ae0b651ea5b0000b19513aef5a03f194d7e3486f2d9258b658da8677fe9036" +checksum = "4fde53ca86b9704a943fef0f1e1d836239a6aedca5de3c65fa9f97ec0bd46d39" dependencies = [ "cc", "codespan-reporting", @@ -393,39 +393,39 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.199" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb05f91d3fb8435d9bab6ac5ce6ac1868be774325fb7fb2a91be39393b21388e" +checksum = "07bae89236c811fd4d08ed3441759ac4ac98d752cbfd3de341315ba16ad20ec3" dependencies = [ "clap", "codespan-reporting", "indexmap", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] name = "cxxbridge-flags" -version = "1.0.199" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf293202e0e3e98495785745389e8d0755b217e66f19194a5c695c25e03282ef" +checksum = "49045042e5fced01b80742aba5508de82aa4f13677ed3fa2b4cda709c40c5918" [[package]] name = "cxxbridge-macro" -version = "1.0.199" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca001d746947c7249ed9d332a10f7a59daedbafeb0ec68c5c18a7db7a93f6ccc" +checksum = "b181252e2e3d3b5d183afbdc033a3958b445eb3e1a0ae65fc3d4f5259f5da6fd" dependencies = [ "indexmap", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -457,7 +457,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -513,9 +513,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "foldhash" @@ -567,17 +567,6 @@ dependencies = [ "git2", ] -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -631,9 +620,9 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -750,9 +739,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", @@ -794,7 +783,7 @@ dependencies = [ "rustc-hash 2.1.3", "serde_core", "tempfile", - "thiserror 2.0.20", + "thiserror 2.0.21", "uuid", "windows", ] @@ -817,9 +806,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -828,9 +817,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "levenshtein" @@ -888,9 +877,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ "libc", ] @@ -983,9 +972,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -1014,7 +1003,7 @@ dependencies = [ "measureme", "mio", "nix", - "rand 0.10.2", + "rand 0.10.3", "regex", "rustc_version", "serde", @@ -1088,9 +1077,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "owo-colors" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" [[package]] name = "parking_lot" @@ -1247,9 +1236,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -1292,13 +1281,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -1371,9 +1359,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -1437,7 +1425,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -1470,9 +1458,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "siphasher" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "slab" @@ -1482,9 +1470,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" @@ -1538,9 +1526,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -1549,13 +1537,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -1591,11 +1579,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -1611,13 +1599,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -1714,9 +1702,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-width" @@ -1750,9 +1738,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.25.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -1788,9 +1776,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -1801,9 +1789,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1811,22 +1799,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -2001,30 +1989,30 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", @@ -2042,13 +2030,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] @@ -2082,7 +2070,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock index 5deb41b66ab8e..1db0a0e7061e3 100644 --- a/src/tools/miri/cargo-miri/Cargo.lock +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -10,15 +10,15 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "camino" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" dependencies = [ "serde_core", ] @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "directories" @@ -109,17 +109,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -139,9 +128,9 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", @@ -161,9 +150,9 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ "libc", ] @@ -218,20 +207,19 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", "thiserror", ] [[package]] name = "rustc-build-sysroot" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569d545953ee9a1ab9d3e9112e961739ff0cfd50bce06b126937395940be69c9" +checksum = "0d0ed7817483b026efc0492f4ccca177f00fb711537f37943cc412bbf12682b4" dependencies = [ "anyhow", "rustc_version", @@ -257,9 +245,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -341,9 +329,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -357,7 +345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom", "once_cell", "rustix", "windows-sys", @@ -365,18 +353,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", @@ -385,9 +373,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "indexmap", "serde_core", @@ -424,9 +412,9 @@ checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "walkdir" @@ -438,12 +426,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "winapi-util" version = "0.1.11" diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock index a690264e158fc..a24d764a39d3a 100644 --- a/src/tools/miri/priroda/Cargo.lock +++ b/src/tools/miri/priroda/Cargo.lock @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ "cipher", "cpubits", @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bstr" @@ -105,9 +105,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "camino" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" dependencies = [ "serde_core", ] @@ -151,14 +151,14 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] name = "cc" -version = "1.4.4" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" dependencies = [ "find-msvc-tools", "shlex", @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -178,9 +178,9 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cpufeatures", @@ -222,7 +222,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -269,9 +269,9 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "console" -version = "0.16.4" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" dependencies = [ "encode_unicode", "libc", @@ -287,27 +287,27 @@ checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -397,9 +397,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "futures-core" @@ -425,17 +425,6 @@ dependencies = [ "slab", ] -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -468,9 +457,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -517,7 +506,7 @@ dependencies = [ "rustc-hash 2.1.3", "serde_core", "tempfile", - "thiserror 2.0.20", + "thiserror 2.0.21", "uuid", "windows", ] @@ -530,9 +519,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -541,9 +530,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "levenshtein" @@ -588,9 +577,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ "libc", ] @@ -656,9 +645,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -685,7 +674,7 @@ dependencies = [ "measureme", "mio", "nix", - "rand 0.10.2", + "rand 0.10.3", "serde", "smallvec", "socket2", @@ -736,9 +725,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "owo-colors" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" [[package]] name = "parking_lot" @@ -884,9 +873,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -929,13 +918,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -1008,9 +996,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -1068,7 +1056,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -1101,9 +1089,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "siphasher" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "slab" @@ -1113,9 +1101,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" @@ -1157,9 +1145,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -1190,11 +1178,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -1210,13 +1198,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.6", ] [[package]] @@ -1303,9 +1291,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-width" @@ -1321,9 +1309,9 @@ checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "uuid" -version = "1.25.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -1353,9 +1341,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -1366,9 +1354,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1376,22 +1364,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -1540,18 +1528,18 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index facfdee075bc4..7347118da591d 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bumpalo" @@ -22,9 +22,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cross-crate-items" @@ -93,7 +93,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] @@ -171,15 +171,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -200,9 +200,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", @@ -290,9 +290,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -335,20 +335,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.119" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -392,14 +381,14 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn", ] [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "wasi" @@ -424,9 +413,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -437,9 +426,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -447,22 +436,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] From b592ad4789301c2c84f5b9dc87861538d84a4f52 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 2 Oct 2026 12:23:20 +0200 Subject: [PATCH 12/28] pick more sensible path for current_exe --- src/tools/miri/src/bin/miri.rs | 5 ++++- src/tools/miri/src/eval.rs | 3 +++ src/tools/miri/src/machine.rs | 6 +++++- src/tools/miri/src/shims/unix/fs.rs | 4 ++-- src/tools/miri/src/shims/unix/macos/foreign_items.rs | 8 +++++--- src/tools/miri/src/shims/windows/foreign_items.rs | 6 +++--- src/tools/miri/test-cargo-miri/run.args.stderr.ref | 2 +- .../miri/test-cargo-miri/run.custom-target-dir.stderr.ref | 2 +- src/tools/miri/test-cargo-miri/run.default.stderr.ref | 2 +- src/tools/miri/tests/pass/shims/env/args.stdout | 2 +- src/tools/miri/tests/pass/shims/env/current_exe.rs | 5 +++-- src/tools/miri/tests/pass/shims/env/current_exe.stdout | 1 + 12 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 src/tools/miri/tests/pass/shims/env/current_exe.stdout diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 7b3f166b3bf87..a0f71a787a9f9 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -159,7 +159,10 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { // Obtain and complete the Miri configuration. let mut config = self.miri_config.take().expect("after_analysis must only be called once"); // Add filename to `miri` arguments. - config.args.insert(0, tcx.sess.io.input.filestem().to_string()); + let file_name = tcx.sess.io.input.file_name(tcx.sess); + config.args.insert(0, file_name.prefer_local_unconditionally().to_string()); + // And configure the executable. + config.current_exe = file_name.into_local_path().and_then(|p| p.canonicalize().ok()); // Adjust working directory for interpretation. if let Some(cwd) = env::var_os("MIRI_CWD") { diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 6e2f300e37ce0..6e51ff42367dc 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -111,6 +111,8 @@ pub struct MiriConfig { pub set_env_vars: FxHashMap, /// Command-line arguments passed to the interpreted program. pub args: Vec, + /// The binary we pretend to be. + pub current_exe: Option, /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`). pub seed: Option, /// The stacked borrows pointer ids to report about. @@ -184,6 +186,7 @@ impl Default for MiriConfig { forwarded_env_vars: vec![], set_env_vars: FxHashMap::default(), args: vec![], + current_exe: None, seed: None, tracked_pointer_tags: FxHashSet::default(), tracked_alloc_ids: FxHashSet::default(), diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index cba1df6b1d7d2..b1f816c92e6f9 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use std::cell::{Cell, RefCell}; use std::collections::BTreeMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{fmt, process}; @@ -537,6 +537,8 @@ pub struct MiriMachine<'tcx> { pub(crate) argc: Option, pub(crate) argv: Option, pub(crate) cmd_line: Option, + /// The binary we pretend to be. + pub(crate) current_exe: Option, /// TLS state. pub(crate) tls: TlsData<'tcx>, @@ -784,6 +786,7 @@ impl<'tcx> MiriMachine<'tcx> { argc: None, argv: None, cmd_line: None, + current_exe: config.current_exe.clone(), tls: TlsData::default(), isolated_op: config.isolated_op, validation: config.validation, @@ -1035,6 +1038,7 @@ impl VisitProvenance for MiriMachine<'_> { argc, argv, cmd_line, + current_exe: _, extern_statics, extern_statics_imports, extern_static_weak_import_default, diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 0bf28103d102f..9b5ed86013f70 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1724,10 +1724,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // On Linux, special case `/proc/self/exe` so that `std::env::current_exe` works // in cross-execution. - let result = if matches!(this.tcx.sess.target.os, Os::Linux) + let result = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) && pathname.to_str() == Some("/proc/self/exe") { - std::env::current_exe() + this.machine.current_exe.clone().ok_or(ErrorKind::NotFound.into()) } else { // We read `pathname` as `OsStr` above so we could do the /proc/self/exe check. // But now we need a (host) path. diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 25ff4fe43b659..95e21289e860d 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -150,9 +150,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let buf_ptr = this.read_pointer(buf)?; let bufsize = this.deref_pointer_as(bufsize, this.machine.layouts.u32)?; - // Using the host current_exe is a bit off, but consistent with Linux - // (where stdlib reads /proc/self/exe). - let path = std::env::current_exe().unwrap(); + let Some(path) = this.machine.current_exe.clone() else { + throw_unsup_format!( + "_NSGetExecutablePath only works when Miri is run on a file" + ) + }; let (written, size_needed) = this.write_path_to_c_str( &path, buf_ptr, diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index bb738621a4ad6..28fe4e86bb7b5 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -1106,9 +1106,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle"); } - // Using the host current_exe is a bit off, but consistent with Linux - // (where stdlib reads /proc/self/exe). - let path = std::env::current_exe().unwrap(); + let Some(path) = this.machine.current_exe.clone() else { + throw_unsup_format!("GetModuleFileNameW only works when Miri is run on a file") + }; let (all_written, size_needed) = this.write_path_to_wide_str_truncated(&path, filename, size.into())?; diff --git a/src/tools/miri/test-cargo-miri/run.args.stderr.ref b/src/tools/miri/test-cargo-miri/run.args.stderr.ref index 01bb8952322b6..89ff818875d9e 100644 --- a/src/tools/miri/test-cargo-miri/run.args.stderr.ref +++ b/src/tools/miri/test-cargo-miri/run.args.stderr.ref @@ -1,4 +1,4 @@ -main +src/main.rs hello world "hello world" he\\llo\"world diff --git a/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref index 4395ff8879b96..2ef4d5db3f414 100644 --- a/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref +++ b/src/tools/miri/test-cargo-miri/run.custom-target-dir.stderr.ref @@ -1,2 +1,2 @@ -main +src/main.rs --target-dir=target/custom-run diff --git a/src/tools/miri/test-cargo-miri/run.default.stderr.ref b/src/tools/miri/test-cargo-miri/run.default.stderr.ref index ba2906d0666cf..53af45d07e576 100644 --- a/src/tools/miri/test-cargo-miri/run.default.stderr.ref +++ b/src/tools/miri/test-cargo-miri/run.default.stderr.ref @@ -1 +1 @@ -main +src/main.rs diff --git a/src/tools/miri/tests/pass/shims/env/args.stdout b/src/tools/miri/tests/pass/shims/env/args.stdout index 9564f5a1aa056..4928237adc516 100644 --- a/src/tools/miri/tests/pass/shims/env/args.stdout +++ b/src/tools/miri/tests/pass/shims/env/args.stdout @@ -1 +1 @@ -args +tests/pass/shims/env/args.rs diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 3c8cf8bcceebd..36138bf9c4df7 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -3,6 +3,7 @@ use std::env; fn main() { - // The actual value we get is a bit odd: we get the Miri binary that interprets us. - env::current_exe().unwrap(); + let exe = env::current_exe().unwrap(); + assert!(exe.is_absolute()); + println!("{}", exe.file_name().unwrap().display()); } diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.stdout b/src/tools/miri/tests/pass/shims/env/current_exe.stdout new file mode 100644 index 0000000000000..316fe6d20b25d --- /dev/null +++ b/src/tools/miri/tests/pass/shims/env/current_exe.stdout @@ -0,0 +1 @@ +current_exe.rs From f04ffd943c5e148f882fd251757e227c3f19128c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 2 Oct 2026 15:51:06 +0200 Subject: [PATCH 13/28] we have to normalize stderr as well --- src/tools/miri/test-cargo-miri/run-test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py index f593a16df9869..925dc288f220a 100755 --- a/src/tools/miri/test-cargo-miri/run-test.py +++ b/src/tools/miri/test-cargo-miri/run-test.py @@ -35,7 +35,7 @@ def cargo_miri(cmd, quiet = True, targets = None): return args -def normalize_stdout(str): +def normalize_output(str): str = str.replace("src\\", "src/") # normalize paths across platforms str = re.sub("\\b\\d+\\.\\d+s\\b", "$TIME", str) # the time keeps changing, obviously return str @@ -71,8 +71,8 @@ def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env=None): env=p_env, ) (stdout, stderr) = p.communicate(input=stdin) - stdout = normalize_stdout(stdout.decode("UTF-8")) - stderr = stderr.decode("UTF-8") + stdout = normalize_output(stdout.decode("UTF-8")) + stderr = normalize_output(stderr.decode("UTF-8")) stdout_matches = check_output(stdout, stdout_ref, "stdout") stderr_matches = check_output(stderr, stderr_ref, "stderr") From ef66a06b77c31837ffb1ed912ca4d92c1e8b8e0b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 2 Oct 2026 17:21:02 +0200 Subject: [PATCH 14/28] run some more tests natively --- src/tools/miri/tests/pass/shims/env/current_dir.rs | 1 + src/tools/miri/tests/pass/shims/env/current_exe.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/tools/miri/tests/pass/shims/env/current_dir.rs b/src/tools/miri/tests/pass/shims/env/current_dir.rs index ca90912eabc54..e61ab32a9ea10 100644 --- a/src/tools/miri/tests/pass/shims/env/current_dir.rs +++ b/src/tools/miri/tests/pass/shims/env/current_dir.rs @@ -1,4 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::env; use std::io::ErrorKind; diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 36138bf9c4df7..1ba001094ff0c 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,5 +1,6 @@ //@ignore-target: freebsd illumos solaris # we don't have the shims this needs here //@compile-flags: -Zmiri-disable-isolation +//@run-native use std::env; fn main() { From 52e14c730d067040bfb545492bf982ba9e282288 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 3 Oct 2026 04:35:33 +0000 Subject: [PATCH 15/28] Prepare for merging from rust-lang/rust This updates the rust-version file to e0452e266c9a4d6522507318f8365741f0b12d29. --- 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 716d825e606bc..4f744e09d6877 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -837b18aae097503892d7aab6c7ddce332a991a21 +e0452e266c9a4d6522507318f8365741f0b12d29 From 5e524bb6c20e1573a1f1421b24446d233bccb9b6 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 3 Oct 2026 04:38:07 +0000 Subject: [PATCH 16/28] fmt --- src/tools/miri/src/bin/miri.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 64eed7b389a6f..0166b65ec4973 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -368,8 +368,9 @@ fn run_compiler_and_exit( rustc_driver::install_ctrlc_handler(); // Invoke compiler, catch any unwinding panics and handle return code. - let exit_code = - rustc_driver::catch_with_exit_code(move || rustc_driver::compiler_entrypoint(args, callbacks)); + let exit_code = rustc_driver::catch_with_exit_code(move || { + rustc_driver::compiler_entrypoint(args, callbacks) + }); exit(if exit_code == ExitCode::SUCCESS { rustc_driver::EXIT_SUCCESS } else { From bfbfe1e4834e9fefcd54d3ee18f3ef53b61dd49c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 18:35:56 +0200 Subject: [PATCH 17/28] unix open: support opening directories --- src/tools/miri/src/shims/files.rs | 55 ++++++- src/tools/miri/src/shims/unix/fs.rs | 118 +++++++++++--- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 153 ++++++++++++++---- src/tools/miri/tests/pass/shims/fs.rs | 49 +++++- 4 files changed, 306 insertions(+), 69 deletions(-) diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d842f2f69b713..92144d3aeb764 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -444,7 +444,13 @@ impl FileDescription for FileHandle { assert!(communicate_allowed, "isolation should have prevented even opening a file"); if !self.readable { - return finish.call(ecx, Err(ErrorKind::PermissionDenied.into())); + // Unix returns EBADF, Windows something that translates to `PermissionDenied`. + let err = if ecx.target_os_is_unix() { + LibcError("EBADF") + } else { + ErrorKind::PermissionDenied.into() + }; + return finish.call(ecx, Err(err)); } let mut file = &self.file; @@ -463,13 +469,13 @@ impl FileDescription for FileHandle { assert!(communicate_allowed, "isolation should have prevented even opening a file"); if !self.writable { - // Linux hosts return EBADF here which we can't translate via the platform-independent - // code since it does not map to any `io::ErrorKind` -- so if we don't do anything - // special, we'd throw an "unsupported error code" here. Windows returns something that - // gets translated to `PermissionDenied`. That seems like a good value so let's just use - // this everywhere, even if it means behavior on Unix targets does not match the real - // thing. - return finish.call(ecx, Err(ErrorKind::PermissionDenied.into())); + // Unix returns EBADF, Windows something that translates to `PermissionDenied`. + let err = if ecx.target_os_is_unix() { + LibcError("EBADF") + } else { + ErrorKind::PermissionDenied.into() + }; + return finish.call(ecx, Err(err)); } let result = ecx.write_to_host(&self.file, len, ptr)?; finish.call(ecx, result) @@ -546,6 +552,39 @@ impl FileDescription for DirHandle { #[cfg(not(bootstrap))] return interp_ok(Either::Left(self.dir.self_metadata())); } + + fn read<'tcx>( + self: FileDescriptionRef, + _communicate_allowed: bool, + _ptr: Pointer, + _len: usize, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + if ecx.target_os_is_unix() { + finish.call(ecx, Err(LibcError("EISDIR"))) + } else { + // No idea what this should do on Windows. + throw_unsup_format!("reading directories is not supported on this target"); + } + } + + fn write<'tcx>( + self: FileDescriptionRef, + _communicate_allowed: bool, + _ptr: Pointer, + _len: usize, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + if ecx.target_os_is_unix() { + // Directories are opened for reading, so writing returns EBADF. + finish.call(ecx, Err(LibcError("EBADF"))) + } else { + // No idea what this should do on Windows. + throw_unsup_format!("writing directories is not supported on this target"); + } + } } /// Internal type of a file-descriptor - this is what [`FdTable`] expects diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 9b5ed86013f70..2d1ee758f2de1 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -455,6 +455,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("unsupported access mode {:#x}", access_mode); } + if this.tcx.sess.target.os == Os::Linux { + let o_tmpfile = this.eval_libc_i32("O_TMPFILE"); + // Note that this overaps with O_DIRECTORY! + if flag & o_tmpfile == o_tmpfile { + // if the flag contains `O_TMPFILE` then we return a graceful error + return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP")); + } + } + let o_directory = this.eval_libc_i32("O_DIRECTORY"); + let mut want_directory = false; + if flag & o_directory == o_directory { + flag &= !o_directory; + want_directory = true; + } let o_append = this.eval_libc_i32("O_APPEND"); if flag & o_append == o_append { flag &= !o_append; @@ -468,6 +482,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let o_creat = this.eval_libc_i32("O_CREAT"); if flag & o_creat == o_creat { flag &= !o_creat; + if want_directory { + // O_CREAT + O_DIRECTORY is invalid. + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); + } // Get the mode. let ([mode], _) = this.check_varargs( if this.libc_ty_layout("mode_t").size.bytes() >= 4 { @@ -514,14 +532,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We do not need to do anything for this flag because `std` already sets it. // (Technically we do not support *not* setting this flag, but we ignore that.) } - if this.tcx.sess.target.os == Os::Linux { - 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_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP")); - } - } - let o_nofollow = this.eval_libc_i32("O_NOFOLLOW"); let mut nofollow = false; if flag & o_nofollow == o_nofollow { @@ -552,23 +562,81 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - let file = match options.open(path) { - Ok(file) => file, - Err(err) => return this.set_errno_and_return_neg1_i32(err), - }; - let metadata = file.metadata().expect("a just-opened file should have metadata"); - if metadata.is_dir() { - throw_unsup_format!("open: opening directories is not supported"); - } - if nofollow && !cfg!(unix) { - // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just - // opens the symlink rather than the target. Turn that into an error. - if metadata.is_symlink() { - return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); + // We start a retry loop to deal with the `is_dir` race, see below. + // We add a retry counter to avoid infinite loops when things go wrong. + let mut counter = 0u32; + loop { + if counter >= 100 { + panic!( + "open seems stuck in an infinite retry loop. \ + If you can reproduce this, please file a bug." + ); + } + counter = counter.strict_add(1); + + // We need to know if the file is a directory to correctly open directory handles. + // The standard library only lets us open something as a file or a directory, so + // we check for that and then retry if we end up with the wrong thing. + let is_dir = path.is_dir(); + + if is_dir { + // Directories cannot be opened for writing. + if access_mode != o_rdonly { + return this.set_errno_and_return_neg1_i32(LibcError("EISDIR")); + } + // All the other flags don't really do anything. + let dir = match DirHandle::open(&path) { + Ok(dir) => dir, + Err(e) => { + if e.kind() == io::ErrorKind::NotADirectory { + // This changed from a directory to a file. Retry. + continue; + } + return this.set_errno_and_return_neg1_i32(e); + } + }; + #[cfg(bootstrap)] + let metadata = dir.dir.metadata(); + #[cfg(not(bootstrap))] + let metadata = dir.dir.self_metadata(); + if !metadata.unwrap().is_dir() { + // This changed from a directory to a file. Retry. + continue; + } + + let fd = this.machine.fds.insert_new(dir); + return interp_ok(Scalar::from_i32(fd)); + } else { + let file = match options.open(&path) { + Ok(file) => file, + Err(e) => { + if e.kind() == io::ErrorKind::IsADirectory { + // This changed from a directory to a file. Retry. + continue; + } + return this.set_errno_and_return_neg1_i32(e); + } + }; + let metadata = file.metadata().expect("a just-opened file should have metadata"); + if metadata.is_dir() { + // This changed from a file to a directory. Retry. + continue; + } + + if nofollow && !cfg!(unix) { + // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just + // opens the symlink rather than the target. Turn that into an error. + if metadata.is_symlink() { + return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); + } + } + if want_directory { + return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); + } + let fd = this.machine.fds.insert_new(FileHandle { file, writable, readable }); + return interp_ok(Scalar::from_i32(fd)); } } - let fd = this.machine.fds.insert_new(FileHandle { file, writable, readable }); - interp_ok(Scalar::from_i32(fd)) } fn lseek( @@ -845,7 +913,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(dir) = fd.downcast::() else { - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); }; #[cfg(not(bootstrap))] @@ -937,7 +1005,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; let Some(dir) = fd.downcast::() else { - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); + return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); }; #[cfg(not(bootstrap))] diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 5e3f1ffc0c491..1142f1182f4bc 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -28,10 +28,11 @@ fn main() { test_ftruncate::(libc::ftruncate); #[cfg(target_os = "linux")] test_ftruncate::(libc::ftruncate64); - test_file_open_allow_two_args(); - test_file_open_needs_three_args(); - test_file_open_extra_third_arg(); + test_create_read_write(); + test_file_open_args(); test_file_open_nofollow(); + test_file_open_directory(); + test_file_open_exclusive(); #[cfg(target_os = "linux")] test_o_tmpfile_flag(); test_posix_mkstemp(); @@ -208,10 +209,13 @@ fn test_statx() { } // Relative to a dirfd. - // The only way to get a dirfd in Miri currently is via a `dirfd`. Slightly silly, but whatever. - let dirstream = unsafe { libc::opendir(utils::into_c_string(path.parent().unwrap()).as_ptr()) }; - assert!(!dirstream.is_null()); - let dirfd = unsafe { libc::dirfd(dirstream) }; + let dirfd = errno_result(unsafe { + libc::open( + utils::into_c_string(path.parent().unwrap()).as_ptr(), + libc::O_RDONLY | libc::O_DIRECTORY, + ) + }) + .unwrap(); unsafe { let mut stx = MaybeUninit::::zeroed(); errno_check(libc::statx( @@ -225,7 +229,7 @@ fn test_statx() { let stx = stx.assume_init(); assert_statx_matches_metadata(&stx, &meta, bytes.len() as u64); } - errno_check(unsafe { libc::closedir(dirstream) }); + errno_check(unsafe { libc::close(dirfd) }); remove_file(&path).unwrap(); } @@ -306,26 +310,12 @@ fn test_statx_on_empty_path() { } } -fn test_file_open_allow_two_args() { - let path = utils::prepare_with_content("miri_test_file_open_allow_two_args.txt", &[]); +fn test_file_open_args() { + let path = utils::prepare_with_content("miri_test_file_open_args.txt", &[]); let name = utils::into_c_string(path); + // Works with 2 or 3 arguments. let _fd = errno_result(unsafe { libc::open(name.as_ptr(), libc::O_RDONLY) }).unwrap(); -} - -fn test_file_open_needs_three_args() { - let path = utils::prepare_with_content("miri_test_file_open_needs_three_args.txt", &[]); - let name = utils::into_c_string(path); - - let _fd = - errno_result(unsafe { libc::open(name.as_ptr(), libc::O_CREAT | libc::O_RDWR, 0o666) }) - .unwrap(); -} - -fn test_file_open_extra_third_arg() { - let path = utils::prepare_with_content("miri_test_file_open_extra_third_arg.txt", &[]); - let name = utils::into_c_string(path); - let _fd = errno_result(unsafe { libc::open(name.as_ptr(), libc::O_RDONLY, 42) }).unwrap(); } @@ -358,6 +348,77 @@ fn test_file_open_nofollow() { } } +fn test_file_open_directory() { + let dir_path = utils::prepare_dir("miri_test_file_open_directory"); + create_dir(&dir_path).unwrap(); + let dir_name = utils::into_c_string(dir_path); + + // Opening it for read-write fails. + let err = errno_result(unsafe { libc::open(dir_name.as_ptr(), libc::O_RDWR) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EISDIR); + + // Opening it for reading succeeds, but then reading/writing fails. + let fd = errno_result(unsafe { libc::open(dir_name.as_ptr(), libc::O_RDONLY) }).unwrap(); + let mut buf = [0u8; 4]; + let err = + errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EISDIR, "unexpected errno: {err}"); + let err = errno_result(unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EBADF, "unexpected errno: {err}"); + errno_check(unsafe { libc::close(fd) }); + + // Ensure it errors on non-directories when we add the flag. + let path = utils::prepare_with_content("miri_test_fs_not_a_dir", &[]); + let err = errno_result(unsafe { + libc::open(utils::into_c_string(path).as_ptr(), libc::O_RDONLY | libc::O_DIRECTORY) + }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOTDIR); + let err = errno_result(unsafe { + libc::open(c"doesnotexist".as_ptr(), libc::O_RDONLY | libc::O_DIRECTORY) + }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT); +} + +fn test_file_open_exclusive() { + let path = utils::prepare("miri_test_file_open_exclusive.txt"); + let cpath = utils::into_c_string(path); + + let fd = errno_result(unsafe { + libc::open(cpath.as_ptr(), libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, 0o666) + }) + .unwrap(); + errno_check(unsafe { libc::close(fd) }); + + let exist_err = errno_result(unsafe { + libc::open(cpath.as_ptr(), libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, 0o666) + }) + .unwrap_err(); + assert_eq!(exist_err.raw_os_error().unwrap(), libc::EEXIST, "unexpected errno: {exist_err}"); +} + +fn test_create_read_write() { + let content = b"hello test"; + let path = utils::prepare("miri_test_create_read_write.txt"); + let cpath = utils::into_c_string(path); + + let fd = + errno_result(unsafe { libc::open(cpath.as_ptr(), libc::O_WRONLY | libc::O_CREAT, 0o666) }) + .unwrap(); + let read_err = libc_utils::read_exact_array::<4>(fd).unwrap_err(); + assert_eq!(read_err.raw_os_error().unwrap(), libc::EBADF, "unexpected errno: {read_err}"); + libc_utils::write_all(fd, content).unwrap(); + errno_check(unsafe { libc::close(fd) }); + + let fd = errno_result(unsafe { libc::open(cpath.as_ptr(), libc::O_RDONLY) }).unwrap(); + let write_err = libc_utils::write_all(fd, content).unwrap_err(); + assert_eq!(write_err.raw_os_error().unwrap(), libc::EBADF, "unexpected errno: {write_err}"); + let data = libc_utils::read_exact_array::<8>(fd).unwrap(); + assert!(content.starts_with(&data)); + errno_check(unsafe { libc::close(fd) }); +} + fn test_dup_stdout_stderr() { let bytes = b"hello dup fd\n"; unsafe { @@ -887,13 +948,9 @@ fn test_fstatat() { let cfilename = c"file.txt"; fs::write(testdir.join(filename), b"hello").unwrap(); let absfilename = testdir.join(filename); + let cabsfilename = utils::into_c_string(&absfilename); assert!(absfilename.is_absolute()); - // The only way to get a dirfd in Miri currently is via a `dirfd`. Slightly silly, but whatever. - let dirstream = unsafe { libc::opendir(utils::into_c_string(&testdir).as_ptr()) }; - assert!(!dirstream.is_null()); - let dirfd = unsafe { libc::dirfd(dirstream) }; - let checkstat = |stat: &libc::stat| { assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFREG); assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); @@ -914,7 +971,7 @@ fn test_fstatat() { errno_check(unsafe { libc::fstatat( 999, // dirfd - utils::into_c_string(&absfilename).as_ptr(), + cabsfilename.as_ptr(), stat.as_mut_ptr(), 0, // flags ) @@ -922,10 +979,27 @@ fn test_fstatat() { checkstat(unsafe { stat.assume_init_ref() }); // Relative to dirfd. + let dirfd = errno_result(unsafe { + libc::open(utils::into_c_string(&testdir).as_ptr(), libc::O_RDONLY | libc::O_DIRECTORY) + }) + .unwrap(); let mut stat = MaybeUninit::::uninit(); errno_check(unsafe { libc::fstatat(dirfd, cfilename.as_ptr(), stat.as_mut_ptr(), 0) }); checkstat(unsafe { stat.assume_init_ref() }); + // Relative to bogus FD that's not a dir. + let err = errno_result(unsafe { libc::fstatat(0, cfilename.as_ptr(), stat.as_mut_ptr(), 0) }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOTDIR, "unexpected errno: {err}"); + let fd = errno_result(unsafe { libc::open(cabsfilename.as_ptr(), libc::O_RDONLY) }).unwrap(); + let err = errno_result(unsafe { libc::fstatat(fd, cfilename.as_ptr(), stat.as_mut_ptr(), 0) }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOTDIR, "unexpected errno: {err}"); + errno_check(unsafe { libc::close(fd) }); + let err = errno_result(unsafe { libc::fstatat(fd, cfilename.as_ptr(), stat.as_mut_ptr(), 0) }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EBADF, "unexpected errno: {err}"); + if utils::have_symlink_permission() { // Symlink following. let linkname = testdir.join("link"); @@ -943,7 +1017,7 @@ fn test_fstatat() { check_stat_fields(stat); } - errno_check(unsafe { libc::closedir(dirstream) }); + errno_check(unsafe { libc::close(dirfd) }); } fn test_stat() { @@ -1219,9 +1293,22 @@ fn test_dirfd() { // Check that all fields are initialized. check_stat_fields(stat); + // We can also call fstatat on this. + let mut stat = MaybeUninit::::uninit(); + let err = + errno_result(unsafe { libc::fstatat(dirfd, c"missing".as_ptr(), stat.as_mut_ptr(), 0) }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT); + errno_check(unsafe { libc::fstatat(dirfd, c".".as_ptr(), stat.as_mut_ptr(), 0) }); + let stat = unsafe { stat.assume_init_ref() }; + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFDIR); + assert_ne!(stat.st_mode & !libc::S_IFMT, 0, "some permission should be set"); + check_stat_fields(stat); + + // Close the stream. errno_check(unsafe { libc::closedir(dir) }); - // `closedir` should also have closed the stream. + // `closedir` should also have closed the underlying FD. let err = errno_result(unsafe { libc::close(dirfd) }).unwrap_err(); assert_eq!(err.raw_os_error().unwrap(), libc::EBADF); } diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 7466a534b0ca4..90c7bdd2e8592 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -3,6 +3,7 @@ #![feature(io_error_more)] #![feature(io_error_uncategorized)] +#![feature(dirfd)] #![cfg_attr(unix, feature(unix_file_vectored_at))] #![allow(unused_features)] // feature use depends on target @@ -60,6 +61,8 @@ fn main() { test_pread_pwrite(); #[cfg(all(unix, not(target_os = "solaris")))] test_preadv_pwritev(); + + test_directory_handle(); } } @@ -93,9 +96,12 @@ fn test_file() { assert!(!file.is_terminal()); - // Writing to a file opened for reading should error (and not stop interpretation). std does not - // categorize the error so we don't check for details. - file.write(&[0]).unwrap_err(); + // Writing to a file opened for reading should error (and not stop interpretation). This + // produces EBADF on Unix but std does not categorize the error so we don't check for details. + let err = file.write(&[0]).unwrap_err(); + if cfg!(windows) { + assert_eq!(err.kind(), ErrorKind::PermissionDenied); + } // However, writing 0 bytes can succeed or fail. let _ignore = file.write(&[]); @@ -104,6 +110,26 @@ fn test_file() { // Removing file should succeed. remove_file(&path).unwrap(); + + // Opening a directory as a file has target-specific behavior. + let res = File::open(&path.parent().unwrap()); + if cfg!(unix) { + // On Unix this just works. + let mut file = res.unwrap(); + // But reading errors. + let err = file.read(&mut [0u8; 32]).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::IsADirectory); + } else { + // On Windows, it errors. + assert_eq!(res.unwrap_err().kind(), ErrorKind::PermissionDenied); + } + // Opening for writing has a target-specific error. + let err = OpenOptions::new().write(true).open(&path.parent().unwrap()).unwrap_err(); + if cfg!(unix) { + assert_eq!(err.kind(), ErrorKind::IsADirectory); + } else { + assert_eq!(err.kind(), ErrorKind::PermissionDenied); + } } fn test_file_partial_reads_writes() { @@ -619,3 +645,20 @@ fn test_hard_link() { remove_file(&source).unwrap(); remove_file(&link).unwrap(); } + +fn test_directory_handle() { + let filename = utils::prepare_with_content("miri_test_directory_handle.txt", b"hello"); + assert!(filename.is_absolute()); + let dir = fs::Dir::open(filename.parent().unwrap()).unwrap(); + assert!(dir.self_metadata().unwrap().is_dir()); + + let stat = dir.metadata(filename.file_name().unwrap()).unwrap(); + assert!(stat.is_file()); + assert!(stat.len() == 5); + let stat = dir.metadata(&filename).unwrap(); // absolute path + assert!(stat.is_file()); + assert!(stat.len() == 5); + + let err = fs::Dir::open(filename).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::NotADirectory); +} From 552ce44b3780cdb85be47aa01da4cd72d97332bd Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 18:59:23 +0200 Subject: [PATCH 18/28] slightly extend window-fs test --- src/tools/miri/src/shims/io_error.rs | 2 +- .../miri/tests/pass-dep/shims/windows-fs.rs | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 67b85ecb8985b..22a176c803819 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -202,8 +202,8 @@ const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { &[ ("WSAEADDRINUSE", AddrInUse), ("WSAEADDRNOTAVAIL", AddrNotAvailable), - ("ERROR_ALREADY_EXISTS", AlreadyExists), ("ERROR_FILE_EXISTS", AlreadyExists), + ("ERROR_ALREADY_EXISTS", AlreadyExists), ("ERROR_NO_DATA", BrokenPipe), ("WSAECONNABORTED", ConnectionAborted), ("WSAECONNREFUSED", ConnectionRefused), diff --git a/src/tools/miri/tests/pass-dep/shims/windows-fs.rs b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs index b012b7328eaf3..d53327341f1e0 100644 --- a/src/tools/miri/tests/pass-dep/shims/windows-fs.rs +++ b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs @@ -16,8 +16,9 @@ mod utils; use windows_sys::Wdk::Storage::FileSystem::{NtReadFile, NtWriteFile}; use windows_sys::Win32::Foundation::{ CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, - ERROR_IO_DEVICE, FALSE, GENERIC_READ, GENERIC_WRITE, GetLastError, RtlNtStatusToDosError, - STATUS_ACCESS_DENIED, STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError, + ERROR_FILE_EXISTS, ERROR_IO_DEVICE, FALSE, GENERIC_READ, GENERIC_WRITE, GetLastError, + RtlNtStatusToDosError, STATUS_ACCESS_DENIED, STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, + SetLastError, }; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW, @@ -74,6 +75,19 @@ unsafe fn test_create_dir_file() { if CloseHandle(handle) == 0 { panic!("Failed to close file") }; + + // Without the FILE_FLAG_BACKUP_SEMANTICS, this does not work. + let handle = CreateFileW( + raw_path.as_ptr(), + GENERIC_READ, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + ptr::null_mut(), + OPEN_EXISTING, + 0, + ptr::null_mut(), + ); + assert_eq!(handle.addr(), usize::MAX); + assert_eq!(GetLastError(), ERROR_ACCESS_DENIED); } unsafe fn test_create_normal_file() { @@ -89,6 +103,7 @@ unsafe fn test_create_normal_file() { ptr::null_mut(), ); assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError()); + assert_eq!(GetLastError(), 0); let mut info = std::mem::zeroed::(); if GetFileInformationByHandle(handle, &mut info) == 0 { panic!("Failed to get file information: {}", GetLastError()) @@ -110,6 +125,7 @@ unsafe fn test_create_normal_file() { ptr::null_mut(), ); assert_eq!(handle.addr(), usize::MAX, "CreateFileW did not fail"); + assert_eq!(GetLastError(), ERROR_FILE_EXISTS); // Test metadata-only handle let handle = CreateFileW( From ed592ea0f4c8fcefca4f497556292f9bd73ee7d4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 19:03:42 +0200 Subject: [PATCH 19/28] fix windows behavior for '.' relative path --- src/tools/miri/src/shims/unix/fs.rs | 133 +++++++++--------- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 6 + 2 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 2d1ee758f2de1..3ff5094392b66 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -899,37 +899,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let path = this.read_path_from_c_str(path)?.into_owned(); - let metadata = if path.is_empty() { - throw_unsup_format!("fstatat: empty path is not supported"); - } else if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") { - // Either absolute path (dirfd is ignored) or relative to working directory. - FileMetadata::from_host( - this, - if symlink_nofollow_flag { path.symlink_metadata() } else { path.metadata() }, - )? - } else { - // relative to dirfd, which must be a directory handle - let Some(fd) = this.machine.fds.get(dirfd) else { - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - }; - let Some(dir) = fd.downcast::() else { - return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); - }; - - #[cfg(not(bootstrap))] - let metadata = if symlink_nofollow_flag { - dir.dir.symlink_metadata(path) - } else { - dir.dir.metadata(path) - }; - #[cfg(bootstrap)] - let metadata = if symlink_nofollow_flag { - dir.fallback.join(path).symlink_metadata() - } else { - dir.fallback.join(path).metadata() - }; - FileMetadata::from_host(this, metadata)? - }; + // Resolve dirfd + path to metadata. + let metadata = FileMetadata::at( + this, + dirfd, + &path, + symlink_nofollow_flag, + /* empty_path_flag */ false, + )?; let metadata = match metadata { Ok(metadata) => metadata, @@ -985,43 +962,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); } - // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file - // represented by dirfd, whether it's a directory or otherwise. - let metadata = if path.is_empty() { - // no path: invalid by default, load metadata about dirfd with flag - if !empty_path_flag { - return this.set_errno_and_return_neg1_i32(LibcError("ENOENT")); - } - FileMetadata::from_fd_num(this, dirfd)? - } else if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") { - // Either absolute path (dirfd is ignored) or relative to working directory. - FileMetadata::from_host( - this, - if symlink_nofollow_flag { path.symlink_metadata() } else { path.metadata() }, - )? - } else { - // relative to dirfd, which must be a directory handle - let Some(fd) = this.machine.fds.get(dirfd) else { - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - }; - let Some(dir) = fd.downcast::() else { - return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); - }; - - #[cfg(not(bootstrap))] - let metadata = if symlink_nofollow_flag { - dir.dir.symlink_metadata(path) - } else { - dir.dir.metadata(path) - }; - #[cfg(bootstrap)] - let metadata = if symlink_nofollow_flag { - dir.fallback.join(path).symlink_metadata() - } else { - dir.fallback.join(path).metadata() - }; - FileMetadata::from_host(this, metadata)? - }; + // Resolve dirfd + path to metadata. + let metadata = + FileMetadata::at(this, dirfd, &path, symlink_nofollow_flag, empty_path_flag)?; let metadata = match metadata { Ok(metadata) => metadata, @@ -2091,9 +2034,61 @@ struct FileMetadata { } impl FileMetadata { + /// Implements the shared "metadata at" semantics of `fstatat` and `statx`. + fn at<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + dirfd: FdNum, + path: &Path, + symlink_nofollow_flag: bool, + empty_path_flag: bool, + ) -> InterpResult<'tcx, Result> { + // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file + // represented by dirfd, whether it's a directory or otherwise. + if path.is_empty() { + // no path: invalid by default, load metadata about dirfd with flag + if !empty_path_flag { + return interp_ok(Err(LibcError("ENOENT"))); + } + FileMetadata::from_fd_num(ecx, dirfd) + } else if path.is_absolute() || dirfd == ecx.eval_libc_i32("AT_FDCWD") { + // Either absolute path (dirfd is ignored) or relative to working directory. + FileMetadata::from_host( + ecx, + if symlink_nofollow_flag { path.symlink_metadata() } else { path.metadata() }, + ) + } else { + // relative to dirfd, which must be a directory handle + let Some(fd) = ecx.machine.fds.get(dirfd) else { + return interp_ok(Err(LibcError("EBADF"))); + }; + let Some(dir) = fd.downcast::() else { + return interp_ok(Err(LibcError("ENOTDIR"))); + }; + + // Windows does not by itself treat `.` correctly so we do that by hand. + #[cfg(not(bootstrap))] + let metadata = if cfg!(windows) && path.to_str() == Some(".") { + dir.dir.self_metadata() + } else if symlink_nofollow_flag { + dir.dir.symlink_metadata(path) + } else { + dir.dir.metadata(path) + }; + #[cfg(bootstrap)] + let metadata = if cfg!(windows) && path.to_str() == Some(".") { + dir.dir.metadata() + } else if symlink_nofollow_flag { + dir.fallback.join(path).symlink_metadata() + } else { + dir.fallback.join(path).metadata() + }; + FileMetadata::from_host(ecx, metadata) + } + } + fn from_fd_num<'tcx>( ecx: &mut MiriInterpCx<'tcx>, - fd_num: i32, + fd_num: FdNum, ) -> InterpResult<'tcx, Result> { let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 1142f1182f4bc..32f4b46d6d1dc 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -1000,6 +1000,12 @@ fn test_fstatat() { .unwrap_err(); assert_eq!(err.raw_os_error().unwrap(), libc::EBADF, "unexpected errno: {err}"); + // Empty path errors. (We do not support AT_EMPTY_PATH.) + let err = + errno_result(unsafe { libc::fstatat(libc::AT_FDCWD, c"".as_ptr(), stat.as_mut_ptr(), 0) }) + .unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT, "unexpected errno: {err}"); + if utils::have_symlink_permission() { // Symlink following. let linkname = testdir.join("link"); From e14ff840ee100651faa6b5e6f104492542b520b2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 20:53:36 +0200 Subject: [PATCH 20/28] fix forwarding O_NOFOLLOW to Dir::open --- src/tools/miri/src/shims/files.rs | 24 ++++++----- src/tools/miri/src/shims/unix/fs.rs | 41 ++++++++++++------- src/tools/miri/src/shims/windows/fs.rs | 8 ++-- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 32 +++++++++++++++ 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 92144d3aeb764..95a898609571b 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -522,20 +522,22 @@ pub struct DirHandle { pub(super) dir: Dir, #[cfg(bootstrap)] pub(super) fallback: std::path::PathBuf, - #[cfg(not(bootstrap))] - #[expect(unused)] - fallback: (), } impl DirHandle { - pub fn open(path: &std::path::Path) -> io::Result { - #[cfg(bootstrap)] - let fallback = path.canonicalize()?; - #[cfg(not(bootstrap))] - let fallback = (); - - let dir = Dir::open(path)?; - Ok(DirHandle { dir, fallback }) + pub fn new(dir: Dir, path: &std::path::Path) -> Self { + cfg_select! { + bootstrap => { + // Only stage 1 builds need the fallback so panicking is fine. + let fallback = + path.canonicalize().expect("canonicalizing directory fallback should succeed"); + DirHandle { dir, fallback } + } + _ => { + let _unused = path; + DirHandle { dir } + } + } } } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 3ff5094392b66..1943630d5108f 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{self, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; +use std::fs::{self, Dir, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{self, Path}; use std::time::SystemTime; @@ -577,15 +577,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We need to know if the file is a directory to correctly open directory handles. // The standard library only lets us open something as a file or a directory, so // we check for that and then retry if we end up with the wrong thing. - let is_dir = path.is_dir(); + let metadata = if nofollow { path.symlink_metadata() } else { path.metadata() }; + let is_dir = metadata.is_ok_and(|m| m.is_dir()); if is_dir { // Directories cannot be opened for writing. if access_mode != o_rdonly { return this.set_errno_and_return_neg1_i32(LibcError("EISDIR")); } - // All the other flags don't really do anything. - let dir = match DirHandle::open(&path) { + let dir = match Dir::open_with(&path, &options) { Ok(dir) => dir, Err(e) => { if e.kind() == io::ErrorKind::NotADirectory { @@ -596,24 +596,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }; #[cfg(bootstrap)] - let metadata = dir.dir.metadata(); + let metadata = dir.metadata().expect("a just-opened dir should have metadata"); #[cfg(not(bootstrap))] - let metadata = dir.dir.self_metadata(); - if !metadata.unwrap().is_dir() { + let metadata = dir.self_metadata().expect("a just-opened dir should have metadata"); + if !metadata.is_dir() { // This changed from a directory to a file. Retry. continue; } - let fd = this.machine.fds.insert_new(dir); + if nofollow && !cfg!(unix) { + // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just + // opens the symlink rather than the target. Turn that into an error. + if metadata.is_symlink() { + return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); + } + } + + let fd = this.machine.fds.insert_new(DirHandle::new(dir, &path)); return interp_ok(Scalar::from_i32(fd)); } else { let file = match options.open(&path) { Ok(file) => file, Err(e) => { - if e.kind() == io::ErrorKind::IsADirectory { + let kind = e.kind(); + if kind == io::ErrorKind::IsADirectory { // This changed from a directory to a file. Retry. continue; } + if want_directory && kind == io::ErrorKind::FilesystemLoop { + // This is reported as ENOTDIR. + return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); + } return this.set_errno_and_return_neg1_i32(e); } }; @@ -623,6 +636,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { continue; } + if want_directory { + return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); + } if nofollow && !cfg!(unix) { // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just // opens the symlink rather than the target. Turn that into an error. @@ -630,9 +646,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); } } - if want_directory { - return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); - } let fd = this.machine.fds.insert_new(FileHandle { file, writable, readable }); return interp_ok(Scalar::from_i32(fd)); } @@ -1233,12 +1246,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // in between above and here. One day, the standard library will support converting // between `Dir` and `ReadDir` (one of the two directions would suffice for our // needs), then we'll use that. - let Ok(dir) = DirHandle::open(&name) else { + let Ok(dir) = fs::Dir::open(&name) else { throw_unsup_format!( "cannot `opendir` this directory: failed to create directory handle" ); }; - let dir = this.machine.fds.new_ref(dir); + let dir = this.machine.fds.new_ref(DirHandle::new(dir, &name)); let dir_fd_id = dir.id(); let dir_fd_num = this.machine.fds.insert(dir); diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 6fc9d37070f67..5cf504c84eb85 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -213,7 +213,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Open this as a directory. // FIXME: shouldn't we check `creation_disposition` here? We do know that it already // exists. - let dir = match DirHandle::open(&file_name) { + let dir = match fs::Dir::open(&file_name) { Ok(dir) => dir, Err(e) => { if e.kind() == io::ErrorKind::NotADirectory { @@ -225,9 +225,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }; #[cfg(bootstrap)] - let metadata = dir.dir.metadata(); + let metadata = dir.metadata(); #[cfg(not(bootstrap))] - let metadata = dir.dir.self_metadata(); + let metadata = dir.self_metadata(); if !metadata.unwrap().is_dir() { // This changed from a directory to a file. Retry. continue; @@ -238,7 +238,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; } - let fd_num = this.machine.fds.insert_new(dir); + let fd_num = this.machine.fds.insert_new(DirHandle::new(dir, &file_name)); return interp_ok(Handle::File(fd_num)); } else { // Per the documentation: diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 32f4b46d6d1dc..a53598c1b1596 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -345,6 +345,27 @@ fn test_file_open_nofollow() { // while POSIX specifies returning ELOOP. Since this test is run on both native FreeBSD and native // Linux hosts, we just assert that its either of those error codes. assert!([libc::ELOOP, libc::EMLINK].contains(&err.raw_os_error().unwrap())); + + // Also check symlink to directory. + let symlink_path = utils::prepare("miri_test_open_nofollow_symlink_to_dir"); + // We make the symlink point to its parent directory. + std::os::unix::fs::symlink(&symlink_path.parent().unwrap(), &symlink_path).unwrap(); + let symlink_cpath = utils::into_c_string(symlink_path); + let err = errno_result(unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW) }) + .unwrap_err(); + assert!( + [libc::ELOOP, libc::EMLINK].contains(&err.raw_os_error().unwrap()), + "unexpected errno: {err}" + ); + // If we set O_DIRECTORY, we get a different error on Linux, but still EMLINK on FreeBSD. + let err = errno_result(unsafe { + libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_DIRECTORY) + }) + .unwrap_err(); + assert!( + [libc::ENOTDIR, libc::EMLINK].contains(&err.raw_os_error().unwrap()), + "unexpected errno: {err}" + ); } } @@ -379,6 +400,17 @@ fn test_file_open_directory() { }) .unwrap_err(); assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT); + + if utils::have_symlink_permission() { + // Also check symlink behavior. + let symlink_path = utils::prepare("miri_test_open_directory_symlink"); + // We make the symlink point to its parent directory. + std::os::unix::fs::symlink(&symlink_path.parent().unwrap(), &symlink_path).unwrap(); + let symlink_cpath = utils::into_c_string(symlink_path); + let fd = + errno_result(unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_DIRECTORY) }).unwrap(); + errno_check(unsafe { libc::close(fd) }); + } } fn test_file_open_exclusive() { From a1ab85a49d7466c35332e7c6ac7ef2a8de495f5c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 21:20:37 +0200 Subject: [PATCH 21/28] add test for opening dangling symlinks --- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index a53598c1b1596..a31839d116fd6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -31,6 +31,7 @@ fn main() { test_create_read_write(); test_file_open_args(); test_file_open_nofollow(); + test_file_open_dangling_symlink(); test_file_open_directory(); test_file_open_exclusive(); #[cfg(target_os = "linux")] @@ -369,6 +370,29 @@ fn test_file_open_nofollow() { } } +fn test_file_open_dangling_symlink() { + if !utils::have_symlink_permission() { + return; + } + + let symlink_path = utils::prepare("miri_test_open_dangling_symlink"); + std::os::unix::fs::symlink("does-not-exist", &symlink_path).unwrap(); + let symlink_cpath = utils::into_c_string(symlink_path); + + let err = + errno_result(unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_RDONLY) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT, "unexpected errno: {err}"); + let err = + errno_result(unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW) }).unwrap_err(); + assert!( + [libc::ELOOP, libc::EMLINK].contains(&err.raw_os_error().unwrap()), + "unexpected errno: {err}" + ); + let err = + errno_result(unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_DIRECTORY) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::ENOENT, "unexpected errno: {err}"); +} + fn test_file_open_directory() { let dir_path = utils::prepare_dir("miri_test_file_open_directory"); create_dir(&dir_path).unwrap(); From a836ca5eab894dcca18f600962b24db194512013 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 3 Oct 2026 21:42:33 +0100 Subject: [PATCH 22/28] solarish: support current_exe via /proc/self/path/a.out in readlink. Fixes #5374. --- src/tools/miri/src/shims/unix/fs.rs | 13 ++++++++----- src/tools/miri/tests/pass/shims/env/current_exe.rs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 9b5ed86013f70..b328f49d0e9a2 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1722,11 +1722,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(-1); } - // On Linux, special case `/proc/self/exe` so that `std::env::current_exe` works - // in cross-execution. - let result = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) - && pathname.to_str() == Some("/proc/self/exe") - { + // Special case the procfs link to the current executable so that + // `std::env::current_exe` works in cross-execution. + let self_exe = match this.tcx.sess.target.os { + Os::Linux | Os::Android => Some("/proc/self/exe"), + Os::Solaris | Os::Illumos => Some("/proc/self/path/a.out"), + _ => None, + }; + let result = if self_exe.is_some() && pathname.to_str() == self_exe { this.machine.current_exe.clone().ok_or(ErrorKind::NotFound.into()) } else { // We read `pathname` as `OsStr` above so we could do the /proc/self/exe check. diff --git a/src/tools/miri/tests/pass/shims/env/current_exe.rs b/src/tools/miri/tests/pass/shims/env/current_exe.rs index 1ba001094ff0c..49e382cc29567 100644 --- a/src/tools/miri/tests/pass/shims/env/current_exe.rs +++ b/src/tools/miri/tests/pass/shims/env/current_exe.rs @@ -1,4 +1,4 @@ -//@ignore-target: freebsd illumos solaris # we don't have the shims this needs here +//@ignore-target: freebsd # we don't have the shims this needs here //@compile-flags: -Zmiri-disable-isolation //@run-native use std::env; From 340f27671a51f8289a55cc30bb20891ac776b52a Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 4 Oct 2026 06:00:16 +0000 Subject: [PATCH 23/28] Prepare for merging from rust-lang/rust This updates the rust-version file to 56343b1a7d3fed3f349a5c2dc474f65b504f5221. --- 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 4f744e09d6877..b936d2cbc7976 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -e0452e266c9a4d6522507318f8365741f0b12d29 +56343b1a7d3fed3f349a5c2dc474f65b504f5221 From b4d2b85d68b735c4521fa16d4dc5a32e32fc2c65 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 4 Oct 2026 00:27:03 +0200 Subject: [PATCH 24/28] replace directory handling retry loop by more lower-level shenanigans --- src/tools/miri/src/helpers.rs | 4 +- src/tools/miri/src/shims/files.rs | 52 ++++++ src/tools/miri/src/shims/unix/fs.rs | 115 ++++--------- src/tools/miri/src/shims/windows/fs.rs | 225 +++++++++++-------------- 4 files changed, 184 insertions(+), 212 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index b0ff200c0614b..f923d5529c6b9 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -10,7 +10,7 @@ use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::ExportedSymbol; -use rustc_middle::ty::layout::{LayoutOf, MaybeResult, TyAndLayout}; +use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, FnSigKind, IntTy, Ty, TyCtxt, UintTy}; use rustc_span::{Span, Symbol}; use rustc_structures::CrateType; @@ -109,6 +109,8 @@ pub fn resolve_path<'tcx>( /// Gets the layout of a type at a path. #[track_caller] pub fn path_ty_layout<'tcx>(cx: &impl LayoutOf<'tcx>, path: &[&str]) -> TyAndLayout<'tcx> { + use rustc_middle::ty::layout::MaybeResult; + let ty = resolve_path(cx.tcx(), path, Namespace::TypeNS).ty(cx.tcx(), cx.typing_env()); cx.layout_of(ty).to_result().ok().unwrap() } diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 95a898609571b..55b2ce105e6e6 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -718,3 +718,55 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(result.map_err(IoError::HostError)) } } + +/// Open something for which we don't know ahead of time whether it is a file or a directory. +/// +/// Custom flags need to be passed separately since they cannot be read from `opts`... +pub fn open_file_or_dir( + path: &std::path::Path, + mut opts: fs::OpenOptions, + #[cfg(unix)] custom_flags: i32, + #[cfg(windows)] custom_flags: u32, +) -> io::Result> { + #[cfg(unix)] + use std::os::unix::fs::OpenOptionsExt; + #[cfg(windows)] + use std::os::windows::fs::OpenOptionsExt; + + // On Unix, `open` works for files and directories. + // On Windows, that needs FILE_FLAG_BACKUP_SEMANTICS, but we don't want to set that by default. + // So we only set it when needed. + let file = match opts.custom_flags(custom_flags).open(path) { + Ok(file) => file, + + #[cfg(windows)] + Err(err) if err.kind() == io::ErrorKind::PermissionDenied => { + // This can happen when the file is actually a directory. + // So retry with FILE_FLAG_BACKUP_SEMANTICS. + opts.custom_flags( + custom_flags | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, + ) + .open(path)? + } + + Err(err) => return Err(err), + }; + + let metadata = file.metadata().expect("just-opened file should have metadata"); + if metadata.is_dir() { + assert!(!metadata.is_symlink()); // Rust makes this mutually exclusive with `is_dir` + // Convert to dir. + cfg_select! { + unix => { + use std::os::fd::OwnedFd; + Ok(Either::Right(OwnedFd::from(file).into())) + } + windows => { + use std::os::windows::io::OwnedHandle; + Ok(Either::Right(OwnedHandle::from(file).into())) + } + } + } else { + Ok(Either::Left(file)) + } +} diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 1943630d5108f..f2ecfc04480ec 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fs::{self, Dir, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; +use std::fs::{self, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{self, Path}; use std::time::SystemTime; @@ -14,7 +14,7 @@ use rustc_target::spec::Os; use self::shims::time::system_time_to_duration; use crate::shims::FdId; -use crate::shims::files::{DirHandle, FdNum, FileHandle}; +use crate::shims::files::{DirHandle, FdNum, FileHandle, open_file_or_dir}; use crate::shims::os_str::{PathConversion, bytes_to_os_str}; use crate::shims::sig::Varargs; use crate::shims::unix::fd::{EvalContextExt as _, FlockOp, UnixFileDescription}; @@ -426,6 +426,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut flag = flag; let mut options = OpenOptions::new(); + let mut custom_flags = 0; let o_rdonly = this.eval_libc_i32("O_RDONLY"); let o_wronly = this.eval_libc_i32("O_WRONLY"); @@ -468,6 +469,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if flag & o_directory == o_directory { flag &= !o_directory; want_directory = true; + // On Unix we can ask the host to only open directories. That's helpful especially in + // combination with O_NOFOLLOW as it affects which error we get when the final component + // is a symlink. Fixing the error up ourselves is non-trivial so we make the host + // generate the right error. + #[cfg(unix)] + { + custom_flags |= libc::O_DIRECTORY; + } } let o_append = this.eval_libc_i32("O_APPEND"); if flag & o_append == o_append { @@ -539,14 +548,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { nofollow = true; cfg_select! { unix => { - use std::os::unix::fs::OpenOptionsExt; - options.custom_flags(libc::O_NOFOLLOW); + custom_flags |= libc::O_NOFOLLOW; } windows => { - use std::os::windows::fs::OpenOptionsExt; - options.custom_flags( - windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT, - ); + custom_flags |= + windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT; } } } @@ -562,92 +568,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - // We start a retry loop to deal with the `is_dir` race, see below. - // We add a retry counter to avoid infinite loops when things go wrong. - let mut counter = 0u32; - loop { - if counter >= 100 { - panic!( - "open seems stuck in an infinite retry loop. \ - If you can reproduce this, please file a bug." - ); - } - counter = counter.strict_add(1); - - // We need to know if the file is a directory to correctly open directory handles. - // The standard library only lets us open something as a file or a directory, so - // we check for that and then retry if we end up with the wrong thing. - let metadata = if nofollow { path.symlink_metadata() } else { path.metadata() }; - let is_dir = metadata.is_ok_and(|m| m.is_dir()); - - if is_dir { - // Directories cannot be opened for writing. - if access_mode != o_rdonly { + // Let's see what we get when we open this! + match open_file_or_dir(&path, options, custom_flags) { + Err(err) => this.set_errno_and_return_neg1_i32(err), + Ok(Either::Right(dir)) => { + // This means it cannot be a symlink, so `nofollow` is fine. + if writable { + // On Windows, opening a folder writable can succeed. + // But here we want it to always fail. return this.set_errno_and_return_neg1_i32(LibcError("EISDIR")); } - let dir = match Dir::open_with(&path, &options) { - Ok(dir) => dir, - Err(e) => { - if e.kind() == io::ErrorKind::NotADirectory { - // This changed from a directory to a file. Retry. - continue; - } - return this.set_errno_and_return_neg1_i32(e); - } - }; - #[cfg(bootstrap)] - let metadata = dir.metadata().expect("a just-opened dir should have metadata"); - #[cfg(not(bootstrap))] - let metadata = dir.self_metadata().expect("a just-opened dir should have metadata"); - if !metadata.is_dir() { - // This changed from a directory to a file. Retry. - continue; - } - - if nofollow && !cfg!(unix) { - // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just - // opens the symlink rather than the target. Turn that into an error. - if metadata.is_symlink() { - return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); - } - } let fd = this.machine.fds.insert_new(DirHandle::new(dir, &path)); - return interp_ok(Scalar::from_i32(fd)); - } else { - let file = match options.open(&path) { - Ok(file) => file, - Err(e) => { - let kind = e.kind(); - if kind == io::ErrorKind::IsADirectory { - // This changed from a directory to a file. Retry. - continue; - } - if want_directory && kind == io::ErrorKind::FilesystemLoop { - // This is reported as ENOTDIR. - return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); - } - return this.set_errno_and_return_neg1_i32(e); - } - }; - let metadata = file.metadata().expect("a just-opened file should have metadata"); - if metadata.is_dir() { - // This changed from a file to a directory. Retry. - continue; - } - + interp_ok(Scalar::from_i32(fd)) + } + Ok(Either::Left(file)) => { if want_directory { return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR")); } - if nofollow && !cfg!(unix) { - // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it just - // opens the symlink rather than the target. Turn that into an error. - if metadata.is_symlink() { + if file.metadata().unwrap().is_symlink() { + if nofollow { + // On Windows, FILE_FLAG_OPEN_REPARSE_POINT makes opening still succeed, it + // just opens the symlink rather than the target. Turn that into an error. return this.set_errno_and_return_neg1_i32(LibcError("ELOOP")); } + panic!("we should not get a symlink here"); } + let fd = this.machine.fds.insert_new(FileHandle { file, writable, readable }); - return interp_ok(Scalar::from_i32(fd)); + interp_ok(Scalar::from_i32(fd)) } } } diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 5cf504c84eb85..c967654a9c73a 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -6,7 +6,7 @@ use bitflags::bitflags; use rustc_abi::Size; use rustc_target::spec::Os; -use crate::shims::files::{DirHandle, FileHandle}; +use crate::shims::files::{DirHandle, FileHandle, open_file_or_dir}; use crate::shims::windows::handle::{EvalContextExt as _, Handle}; use crate::*; @@ -186,7 +186,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - // We start a retry loop to deal with the `is_dir` and `exists_already` race, see below. + // We start a retry loop to deal with the `exists_already` race, see below. // We add a retry counter to avoid infinite loops when things go wrong. let mut counter = 0u32; loop { @@ -198,145 +198,114 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } counter = counter.strict_add(1); - // We need to know if the file is a directory to correctly open directory handles. - // The standard library only lets us open something as a file or a directory, so - // we check for that and then retry if we end up with the wrong thing. - let is_dir = file_name.is_dir(); - - // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle. - if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir { - this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?; - return interp_ok(Handle::Invalid); - } - - if is_dir { - // Open this as a directory. - // FIXME: shouldn't we check `creation_disposition` here? We do know that it already - // exists. - let dir = match fs::Dir::open(&file_name) { - Ok(dir) => dir, - Err(e) => { - if e.kind() == io::ErrorKind::NotADirectory { - // This changed from a directory to a file. Retry. - continue; - } - this.set_last_error(e)?; - return interp_ok(Handle::Invalid); + // Per the documentation: + // If the specified file exists and is writable, the function truncates the file, + // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS. + // If the specified file does not exist and is a valid path, a new file is created, + // the function succeeds, and the last-error code is set to zero. + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + // + // We check whether it exists before trying to open it. This is racy, but there + // doesn't appear to be an std API that both succeeds whether or not a file already + // exists and tells us whether it is new. So instead we will open the file in a way + // that we can verify whether our guess is correct, and retry if it is not. + let exists_already = file_name.exists(); + + // Compute the OpenOptions. + let mut options = fs::OpenOptions::new(); + options.read(desired_read); + options.write(desired_write); + match creation_disposition { + CreateAlways | OpenAlways => { + // These two create the file if it is missing, but also succeed if it + // already exists. As explained above we cannot just always set `create_new` + // here, so we only do that if we think it is needed. + // We later verify our `exists_already` guess: if we expect it to already + // exist, we set no flag, thus failing if it doesn't exist. If we expect the + // file to not exist, we use `create_new` to fail if it does exist. + if !exists_already { + options.create_new(true); + } + if creation_disposition == CreateAlways { + options.truncate(true); } - }; - #[cfg(bootstrap)] - let metadata = dir.metadata(); - #[cfg(not(bootstrap))] - let metadata = dir.self_metadata(); - if !metadata.unwrap().is_dir() { - // This changed from a directory to a file. Retry. - continue; } - - // Windows communicates information via the error code on success. - if let CreateAlways | OpenAlways = creation_disposition { - this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; + CreateNew => { + options.create_new(true); + // Per `create_new` documentation: + // The file must be opened with write or append access in order to create a new file. + // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new + if !desired_write { + options.append(true); + } } - - let fd_num = this.machine.fds.insert_new(DirHandle::new(dir, &file_name)); - return interp_ok(Handle::File(fd_num)); - } else { - // Per the documentation: - // If the specified file exists and is writable, the function truncates the file, - // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS. - // If the specified file does not exist and is a valid path, a new file is created, - // the function succeeds, and the last-error code is set to zero. - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - // - // We check whether it exists before trying to open it. This is racy, but there - // doesn't appear to be an std API that both succeeds whether or not a file already - // exists and tells us whether it is new. So instead we will open the file in a way - // that we can verify whether our guess is correct, and retry if it is not. - let exists_already = file_name.exists(); - - // Open this as a standard file. - let mut options = fs::OpenOptions::new(); - options.read(desired_read); - options.write(desired_write); - match creation_disposition { - CreateAlways | OpenAlways => { - // These two create the file if it is missing, but also succeed if it - // already exists. As explained above we cannot just always set `create_new` - // here, so we only do that if we think it is needed. - // We later verify our `exists_already` guess: if we expect it to already - // exist, we set no flag, thus failing if it doesn't exist. If we expect the - // file to not exist, we use `create_new` to fail if it does exist. - if !exists_already { - options.create_new(true); - } - if creation_disposition == CreateAlways { - options.truncate(true); - } + OpenExisting => { + if !desired_read && !desired_write { + // Windows supports handles with no permissions. These allow things such as + // reading metadata, but not file content. This is used by `Path::metadata`. + // `std` does not support this. To ensure we behave correctly as often as + // possible, we open the file for reading and live with the fact that this + // might incorrectly return `PermissionDenied`. + // FIXME: We could probably use `OpenOptionsExt`? On a Unix host, + // `O_PATH` apparently can open files for metadata use only. + options.read(true); } - CreateNew => { - options.create_new(true); - // Per `create_new` documentation: - // The file must be opened with write or append access in order to create a new file. - // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new - if !desired_write { - options.append(true); - } + } + TruncateExisting => { + options.truncate(true); + } + } + + // Let's see what we get when we open this! + return match open_file_or_dir(&file_name, options, /* custom_flags */ 0) { + Err(err) => { + let kind = err.kind(); + if exists_already && kind == io::ErrorKind::NotFound { + // The file disappeared. Retry. + continue; } - OpenExisting => { - if !desired_read && !desired_write { - // Windows supports handles with no permissions. These allow things such as - // reading metadata, but not file content. This is used by `Path::metadata`. - // `std` does not support this. To ensure we behave correctly as often as - // possible, we open the file for reading and live with the fact that this - // might incorrectly return `PermissionDenied`. - // FIXME: We could probably use `OpenOptionsExt`? On a Unix host, - // `O_PATH` apparently can open files for metadata use only. - options.read(true); - } + if !exists_already && kind == io::ErrorKind::AlreadyExists { + // The file got created by something else. Retry. + continue; } - TruncateExisting => { - options.truncate(true); + + if kind == io::ErrorKind::IsADirectory && desired_write { + // This can happen on Unix hosts when write permissions are requested. + // Windows uses a different error code in that case. + this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?; + } else { + this.set_last_error(err)?; } + return interp_ok(Handle::Invalid); } - - let file = match options.open(&file_name) { - Ok(file) => file, - Err(e) => { - let kind = e.kind(); - if kind == io::ErrorKind::IsADirectory { - // This changed from a file to a directory. Retry. - continue; - } - if exists_already && kind == io::ErrorKind::NotFound { - // The file disappeared. Retry. - continue; - } - if !exists_already && kind == io::ErrorKind::AlreadyExists { - // The file got created by something else. Retry. - continue; - } - this.set_last_error(e)?; + Ok(Either::Right(dir)) => { + // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle. + if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) { + this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?; return interp_ok(Handle::Invalid); } - }; - if file.metadata().unwrap().is_dir() { - // This changed from a file to a directory. Retry. - continue; + // We excluded this above. + if matches!(creation_disposition, CreateAlways | OpenAlways) { + unreachable!() + } + + let fd_num = this.machine.fds.insert_new(DirHandle::new(dir, &file_name)); + interp_ok(Handle::File(fd_num)) } + Ok(Either::Left(file)) => { + // CreateAlways | OpenAlways update the status code even on success. + if matches!(creation_disposition, CreateAlways | OpenAlways) && exists_already { + this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; + } - // Windows communicates information via the error code on success. - if let CreateAlways | OpenAlways = creation_disposition - && exists_already - { - this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?; + let fd_num = this.machine.fds.insert_new(FileHandle { + file, + writable: desired_write, + readable: desired_read, + }); + interp_ok(Handle::File(fd_num)) } - let fd_num = this.machine.fds.insert_new(FileHandle { - file, - writable: desired_write, - readable: desired_read, - }); - return interp_ok(Handle::File(fd_num)); - } + }; } } From 4e604a98b65aa08b6355df75fdf072d6b4e6ee87 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 4 Oct 2026 10:30:38 +0200 Subject: [PATCH 25/28] support F_GETFL on files and directories this helps with File debug-printing on Linux (but not on other Unixes) --- src/tools/miri/src/shims/files.rs | 58 +++++---- src/tools/miri/src/shims/unix/fd.rs | 49 ++++---- src/tools/miri/src/shims/unix/fs.rs | 17 +++ .../miri/src/shims/unix/linux_like/epoll.rs | 5 +- .../miri/src/shims/unix/linux_like/eventfd.rs | 5 +- src/tools/miri/src/shims/unix/socket.rs | 22 ++-- src/tools/miri/src/shims/unix/tcp_socket.rs | 28 ++--- .../miri/src/shims/unix/virtual_socket.rs | 112 +++++++++--------- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 31 ++++- 9 files changed, 181 insertions(+), 146 deletions(-) diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 55b2ce105e6e6..c1a9bff07dec2 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -195,25 +195,10 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { false } - fn as_unix<'tcx>( - self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { - panic!("Not a unix file descriptor: {}", self.name()); - } - - /// Implementation of fcntl(F_GETFL) for this FD. - fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { - throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name()); - } - - /// Implementation of fcntl(F_SETFL) for this FD. - fn set_flags<'tcx>( - &self, - _flag: i32, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Scalar> { - throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name()); + /// Converts this FD into a unix file description. Must succeed for all FD types that can be + /// created on Unix targets! + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + panic!("Not a unix file description: {}", self.name()); } /// Get the `ReadinessWatched` of the file description. @@ -228,7 +213,7 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { } #[derive(Debug)] -struct Stdin { +pub struct Stdin { stdin: io::Stdin, watched: ReadinessWatched, } @@ -278,10 +263,14 @@ impl FileDescription for Stdin { readiness.writable = true; readiness } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } } #[derive(Debug)] -struct Stdout { +pub struct Stdout { stdout: io::Stdout, watched: ReadinessWatched, } @@ -331,10 +320,14 @@ impl FileDescription for Stdout { readiness.writable = true; readiness } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } } #[derive(Debug)] -struct Stderr { +pub struct Stderr { stderr: io::Stderr, watched: ReadinessWatched, } @@ -378,6 +371,10 @@ impl FileDescription for Stderr { readiness.writable = true; readiness } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } } /// Like /dev/null @@ -419,6 +416,10 @@ impl FileDescription for NullOutput { readiness.writable = true; readiness } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } } #[derive(Debug)] @@ -505,14 +506,7 @@ impl FileDescription for FileHandle { true } - fn as_unix<'tcx>( - self: FileDescriptionRef, - ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { - assert!( - ecx.target_os_is_unix(), - "unix file operations are only available for unix targets" - ); + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } } @@ -587,6 +581,10 @@ impl FileDescription for DirHandle { throw_unsup_format!("writing directories is not supported on this target"); } } + + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { + self + } } /// Internal type of a file-descriptor - this is what [`FdTable`] expects diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index c0278d539d877..e4dbe77240633 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -63,6 +63,20 @@ pub trait UnixFileDescription: FileDescription { throw_unsup_format!("cannot flock {}", self.name()); } + /// Implementation of fcntl(F_GETFL) for this FD. + fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name()); + } + + /// Implementation of fcntl(F_SETFL) for this FD. + fn set_flags<'tcx>( + &self, + _flag: i32, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name()); + } + /// Modifies device parameters. /// `op` is the device-dependent operation code. It's either a `c_long` or `c_int`, depending on /// the target and whether it uses glibc or musl. @@ -78,14 +92,18 @@ pub trait UnixFileDescription: FileDescription { } /// Returns this file description as a Unix socket, if it represents one. - fn as_socket<'tcx>( + fn as_socket( self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, ) -> Option> { None } } +impl UnixFileDescription for shims::files::Stdin {} +impl UnixFileDescription for shims::files::Stdout {} +impl UnixFileDescription for shims::files::Stderr {} +impl UnixFileDescription for shims::files::NullOutput {} + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn close(&mut self, fd_num: FdNum) -> InterpResult<'tcx, Scalar> { @@ -94,6 +112,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Some(fd) = this.machine.fds.remove(fd_num) else { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; + let fd = fd.as_unix(); // sanity-check if this.tcx.sess.target.os == Os::Illumos { // Illumos didn't like the Linux semantics of epoll tracking file *descriptions* // rather than file *descriptors*. So on Illumos, when a file description is closed, @@ -171,7 +190,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("unsupported flags {:#x}", op); }; - let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?; + let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?; // return `0` if flock is successful let result = result.map(|()| 0i32); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) @@ -202,7 +221,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Since some ioctl operations use the return value as an output parameter, we cannot strictly use the convention of // zero indicating success and -1 indicating an error. - let return_value = fd.as_unix(this).ioctl(op, varargs, this)?; + let return_value = fd.as_unix().ioctl(op, varargs, this)?; interp_ok(Scalar::from_i32(return_value)) } @@ -262,7 +281,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - fd.get_flags(this) + fd.as_unix().get_flags(this) } cmd if cmd == f_setfl => { // Check if this is a valid open file descriptor. @@ -286,7 +305,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | this.eval_libc_i32("O_NOCTTY") | this.eval_libc_i32("O_TRUNC"); - fd.set_flags(flag & !ignored_flags, this) + fd.as_unix().set_flags(flag & !ignored_flags, this) } cmd if this.tcx.sess.target.os == Os::MacOs && cmd == this.eval_libc_i32("F_FULLFSYNC") => @@ -699,14 +718,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Ok(offset) = u64::try_from(offset) else { return finish.call(this, Err(LibcError("EINVAL"))); }; - fd.as_unix(this).pread( - this.machine.communicate(), - offset, - ptr, - len, - this, - finish, - )? + fd.as_unix().pread(this.machine.communicate(), offset, ptr, len, this, finish)? } }; interp_ok(()) @@ -758,14 +770,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let Ok(offset) = u64::try_from(offset) else { return finish.call(this, Err(LibcError("EINVAL"))); }; - fd.as_unix(this).pwrite( - this.machine.communicate(), - ptr, - len, - offset, - this, - finish, - )? + fd.as_unix().pwrite(this.machine.communicate(), ptr, len, offset, this, finish)? } }; interp_ok(()) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index f2ecfc04480ec..e385f18e4cbcf 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -168,6 +168,22 @@ impl UnixFileDescription for FileHandle { }, } } + + fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + interp_ok(match (self.readable, self.writable) { + (true, true) => ecx.eval_libc("O_RDWR"), + (true, false) => ecx.eval_libc("O_RDONLY"), + (false, true) => ecx.eval_libc("O_WRONLY"), + _ => unreachable!(), + }) + } +} + +impl UnixFileDescription for DirHandle { + fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + // Directories are always readonly on Unix. + interp_ok(ecx.eval_libc("O_RDONLY")) + } } /// The table of open directories. @@ -666,6 +682,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { windows => { use std::os::windows::fs; // This is racy, but not much we can do about that. + // FIXME: maybe we can retry based on the error code? if src.is_dir() { fs::symlink_dir(src, dst) } else { 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 fa627f982ec79..482e7972a7364 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -28,10 +28,7 @@ impl FileDescription for Epoll { interp_ok(Either::Right("S_IFREG")) } - fn as_unix<'tcx>( - self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } } diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index 4b84906d71cd7..476f154722437 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -116,10 +116,7 @@ impl FileDescription for EventFd { } } - fn as_unix<'tcx>( - self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } } diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index ce1f5c06b6b2c..a7501326e378c 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -250,7 +250,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; @@ -271,7 +271,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; @@ -305,7 +305,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; @@ -392,7 +392,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; @@ -438,7 +438,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; @@ -517,7 +517,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; @@ -609,7 +609,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; @@ -646,7 +646,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; @@ -714,7 +714,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; @@ -746,7 +746,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest); }; @@ -789,7 +789,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(socket) = fd.as_unix(this).as_socket(this) else { + let Some(socket) = fd.as_unix().as_socket() else { return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK")); }; diff --git a/src/tools/miri/src/shims/unix/tcp_socket.rs b/src/tools/miri/src/shims/unix/tcp_socket.rs index 394f80a151f06..4275926260da9 100644 --- a/src/tools/miri/src/shims/unix/tcp_socket.rs +++ b/src/tools/miri/src/shims/unix/tcp_socket.rs @@ -198,13 +198,20 @@ impl FileDescription for TcpSocket { false } - fn as_unix<'tcx>( - self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) + } + + fn readiness(&self) -> Readiness { + *self.io_readiness.borrow() + } +} + +impl UnixFileDescription for TcpSocket { fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { let mut flags = ecx.eval_libc_i32("O_RDWR"); @@ -238,16 +245,6 @@ impl FileDescription for TcpSocket { interp_ok(Scalar::from_i32(0)) } - fn readiness_watched(&self) -> Option<&ReadinessWatched> { - Some(&self.watched) - } - - fn readiness(&self) -> Readiness { - *self.io_readiness.borrow() - } -} - -impl UnixFileDescription for TcpSocket { fn ioctl<'tcx>( &self, op: Scalar, @@ -283,9 +280,8 @@ impl UnixFileDescription for TcpSocket { throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket"); } - fn as_socket<'tcx>( + fn as_socket( self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, ) -> Option> { Some(self) } diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 04bc2ab8a890a..99d73fe9eb5cb 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -147,65 +147,10 @@ impl FileDescription for VirtualSocket { false } - fn as_unix<'tcx>( - self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, - ) -> FileDescriptionRef { + fn as_unix(self: FileDescriptionRef) -> FileDescriptionRef { self } - fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { - let mut flags = 0; - - // Get flag for file access mode. - // The flag for both socketpair and pipe will remain the same even when the peer - // fd is closed, so we need to look at the original type of this socket, not at whether - // the peer socket still exists. - match self.fd_type { - VirtualSocketType::Socketpair => { - flags |= ecx.eval_libc_i32("O_RDWR"); - } - VirtualSocketType::PipeRead => { - flags |= ecx.eval_libc_i32("O_RDONLY"); - } - VirtualSocketType::PipeWrite => { - flags |= ecx.eval_libc_i32("O_WRONLY"); - } - } - - // Get flag for blocking status. - if self.is_nonblock.get() { - flags |= ecx.eval_libc_i32("O_NONBLOCK"); - } - - interp_ok(Scalar::from_i32(flags)) - } - - fn set_flags<'tcx>( - &self, - mut flag: i32, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Scalar> { - let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK"); - - // O_NONBLOCK flag can be set / unset by user. - if flag & o_nonblock == o_nonblock { - self.is_nonblock.set(true); - flag &= !o_nonblock; - } else { - self.is_nonblock.set(false); - } - - // Throw error if there is any unsupported flag. - if flag != 0 { - throw_unsup_format!( - "fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes" - ) - } - - interp_ok(Scalar::from_i32(0)) - } - fn readiness_watched(&self) -> Option<&ReadinessWatched> { Some(&self.watched) } @@ -258,6 +203,58 @@ impl FileDescription for VirtualSocket { } impl UnixFileDescription for VirtualSocket { + fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> { + let mut flags = 0; + + // Get flag for file access mode. + // The flag for both socketpair and pipe will remain the same even when the peer + // fd is closed, so we need to look at the original type of this socket, not at whether + // the peer socket still exists. + match self.fd_type { + VirtualSocketType::Socketpair => { + flags |= ecx.eval_libc_i32("O_RDWR"); + } + VirtualSocketType::PipeRead => { + flags |= ecx.eval_libc_i32("O_RDONLY"); + } + VirtualSocketType::PipeWrite => { + flags |= ecx.eval_libc_i32("O_WRONLY"); + } + } + + // Get flag for blocking status. + if self.is_nonblock.get() { + flags |= ecx.eval_libc_i32("O_NONBLOCK"); + } + + interp_ok(Scalar::from_i32(flags)) + } + + fn set_flags<'tcx>( + &self, + mut flag: i32, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK"); + + // O_NONBLOCK flag can be set / unset by user. + if flag & o_nonblock == o_nonblock { + self.is_nonblock.set(true); + flag &= !o_nonblock; + } else { + self.is_nonblock.set(false); + } + + // Throw error if there is any unsupported flag. + if flag != 0 { + throw_unsup_format!( + "fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes" + ) + } + + interp_ok(Scalar::from_i32(0)) + } + fn ioctl<'tcx>( &self, op: Scalar, @@ -301,9 +298,8 @@ impl UnixFileDescription for VirtualSocket { throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket"); } - fn as_socket<'tcx>( + fn as_socket( self: FileDescriptionRef, - _ecx: &MiriInterpCx<'tcx>, ) -> Option> { match self.fd_type { VirtualSocketType::Socketpair => Some(self), diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index a31839d116fd6..39a7203c52318 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -29,6 +29,7 @@ fn main() { #[cfg(target_os = "linux")] test_ftruncate::(libc::ftruncate64); test_create_read_write(); + test_read_and_uninit(); test_file_open_args(); test_file_open_nofollow(); test_file_open_dangling_symlink(); @@ -61,7 +62,7 @@ fn main() { test_lstat(); test_futimens(); test_isatty(); - test_read_and_uninit(); + test_getfl(); #[cfg(target_os = "macos")] test_ioctl(); #[cfg(target_os = "linux")] @@ -1213,6 +1214,34 @@ fn test_isatty() { } } +fn test_getfl() { + let path = utils::prepare_with_content("miri-test-getfl.txt", b"hello"); + let cpath = utils::into_c_string(&path); + + for flag in [libc::O_RDONLY, libc::O_WRONLY, libc::O_RDWR] { + let fd = errno_result(unsafe { libc::open(cpath.as_ptr(), flag) }).unwrap(); + // These flags contain "junk" that the kernel adds, such as a O_LARGEFILE flag, even on + // x86-64 where glibc sets O_LARGEFILE to 0 (but the kernel still says it is 0x8000). + // So we do not check the full flag, just the access mode. + assert_eq!( + errno_result(unsafe { libc::fcntl(fd, libc::F_GETFL) }).unwrap() & libc::O_ACCMODE, + flag + ); + errno_check(unsafe { libc::close(fd) }); + } + + // Also test it on a directory. + let fd = errno_result(unsafe { + libc::open(utils::into_c_string(path.parent().unwrap()).as_ptr(), libc::O_RDONLY) + }) + .unwrap(); + assert_eq!( + errno_result(unsafe { libc::fcntl(fd, libc::F_GETFL) }).unwrap() & libc::O_ACCMODE, + libc::O_RDONLY + ); + errno_check(unsafe { libc::close(fd) }); +} + fn test_read_and_uninit() { { // We test that libc::read initializes its buffer. From 63c07bdb5352b561b1a362d01c328982c6a03712 Mon Sep 17 00:00:00 2001 From: Abhinav Srivastav Date: Sun, 4 Oct 2026 17:24:48 +0530 Subject: [PATCH 26/28] lower spans of error const args and test binder bounds --- compiler/rustc_ast_lowering/src/item.rs | 2 +- compiler/rustc_ast_lowering/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 6ec5903c5fb3b..5583287a832f5 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -2098,7 +2098,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::TestBinderBoundTypeConstraint { - span: *span, + span: self.lower_span(*span), hir_id: self.lower_node_id(*node_id), params: generics.params, lhs, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4b6e897da4c87..d331c8d732115 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -3363,7 +3363,7 @@ impl UnrepresentableConstArgError { ConstArg { hir_id: lowering_context.next_id(), kind: hir::ConstArgKind::Error(e), - span: self.span, + span: lowering_context.lower_span(self.span), } } } From 58eda6ec6775da8f47d119d73bc1d88a4b8f1cd1 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 2 Oct 2026 16:36:37 +1000 Subject: [PATCH 27/28] Use `LLVMGlobalAddMetadata` This function was added in LLVM 22. --- compiler/rustc_codegen_llvm/src/context.rs | 4 ++-- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 11 +++++------ compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp | 5 ----- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index d64c8de6ed7aa..1059de3c0aec0 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -1234,7 +1234,7 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { /// Helper method for the sequence of calls: /// - `LLVMMDNodeInContext2` (to create an `llvm::MDNode` from a list of metadata) - /// - `LLVMRustGlobalAddMetadata` (to set that node as metadata of `kind_id` for `global`) + /// - `LLVMGlobalAddMetadata` (to set that node as metadata of `kind_id` for `global`) pub(crate) fn global_add_metadata_node( &self, global: &'ll Value, @@ -1242,7 +1242,7 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { md_list: &[&'ll Metadata], ) { let md = self.md_node_in_context(md_list); - unsafe { llvm::LLVMRustGlobalAddMetadata(global, kind_id, md) }; + unsafe { llvm::LLVMGlobalAddMetadata(global, kind_id, md) }; } /// Helper method for the sequence of calls: diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 4c7d60a0cbf67..f576f29a17cca 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -993,6 +993,11 @@ unsafe extern "C" { KindID: MetadataKindId, Metadata: &'a Metadata, ); + pub(crate) fn LLVMGlobalAddMetadata<'ll>( + Global: &'ll Value, + Kind: MetadataKindId, + MD: &'ll Metadata, + ); pub(crate) safe fn LLVMValueAsMetadata(Node: &Value) -> &Metadata; // Operations on constants of any type @@ -1968,12 +1973,6 @@ unsafe extern "C" { pub(crate) fn LLVMRustDisableSystemDialogsOnCrash(); // Operations on all values - /// FIXME: After dropping LLVM 21, migrate to LLVM-C's `LLVMGlobalAddMetadata`. - pub(crate) fn LLVMRustGlobalAddMetadata<'a>( - Val: &'a Value, - KindID: MetadataKindId, - Metadata: &'a Metadata, - ); pub(crate) fn LLVMRustIsNonGVFunctionPointerTy(Val: &Value) -> bool; pub(crate) fn LLVMRustStripPointerCasts<'a>(Val: &'a Value) -> &'a Value; diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 4b14392db2c94..62aa8c5c57aae 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1002,11 +1002,6 @@ extern "C" void LLVMRustRemoveFnAttribute(LLVMValueRef Fn, const char *Name, } } -extern "C" void LLVMRustGlobalAddMetadata(LLVMValueRef Global, unsigned Kind, - LLVMMetadataRef MD) { - unwrap(Global)->addMetadata(Kind, *unwrap(MD)); -} - extern "C" LLVMMetadataRef LLVMRustDIBuilderCreateCompileUnit( LLVMDIBuilderRef Builder, unsigned Lang, LLVMMetadataRef FileRef, const char *Producer, size_t ProducerLen, bool isOptimized, From 9c4bef50e415950ae48f5b968746a36dcf3d7bf5 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 1 Oct 2026 17:25:13 +0200 Subject: [PATCH 28/28] optimize empty `NestedGoals::extend_from_child` --- .../rustc_type_ir/src/search_graph/mod.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 228b6c34b06ed..0a6e351dcdf8f 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -531,12 +531,25 @@ impl NestedGoals { /// to all nested goals of that nested goal are also inductive. Otherwise the paths are /// the same as for the child. fn extend_from_child(&mut self, step_kind: PathKind, nested_goals: &NestedGoals) { - // Each nested goal is updated independently, and `insert` only unions paths for that - // goal, so traversal order cannot affect the result. - #[allow(rustc::potential_query_instability)] - for (input, paths_to_nested) in nested_goals.iter() { - let paths_to_nested = paths_to_nested.extend_with(step_kind); - self.insert(input, paths_to_nested); + if nested_goals.is_empty() { + return; + } + + // Each nested goal is updated independently, so traversal order cannot + // affect the result. + #[expect(rustc::potential_query_instability)] + if self.is_empty() { + // Manually avoid HashMap lookups for every input if there aren't + // any preexisting entries. + self.nested_goals.clone_from(&nested_goals.nested_goals); + for paths_to_nested in self.nested_goals.values_mut() { + *paths_to_nested = paths_to_nested.extend_with(step_kind); + } + } else { + for (input, paths_to_nested) in nested_goals.iter() { + let paths_to_nested = paths_to_nested.extend_with(step_kind); + self.insert(input, paths_to_nested); + } } }